├── .gitignore ├── Common ├── ApproxNum.ml ├── Data.ml ├── Syntax.ml ├── Terms.ml └── dune ├── Normalizers ├── AbstractMachine.ml ├── Compiled_Evalapply.ml ├── Compiled_HOAS.ml ├── NBE_Closure.ml ├── NBE_HOAS.ml ├── NBE_HashConsing.ml ├── NBE_Lazy.ml ├── NBE_Memo.ml ├── NBE_Named.ml ├── NBE_Pushenter.ml ├── Normalizer.ml ├── Normalizers.ml ├── Subst.ml └── dune ├── README.md ├── baseline.ml ├── bench.ml ├── bench.sh ├── compile.sh ├── count_terms.ml ├── data ├── AM-variants │ ├── church_add.dat │ ├── church_add.png │ ├── church_mul.dat │ ├── church_mul.png │ ├── exponential.dat │ ├── exponential.png │ ├── parigot_add.dat │ ├── parigot_add.png │ ├── random.dat │ └── random.png ├── DBI-named │ ├── church_add.dat │ ├── church_add.png │ ├── church_mul.dat │ ├── church_mul.png │ ├── exponential.dat │ ├── exponential.png │ ├── parigot_add.dat │ ├── parigot_add.png │ ├── random.dat │ ├── random.png │ ├── self_interp_size.dat │ └── self_interp_size.png ├── NBE-hashcons │ ├── church_add.dat │ ├── church_add.png │ ├── church_mul.dat │ ├── church_mul.png │ ├── exponential.dat │ ├── exponential.png │ ├── parigot_add.dat │ ├── parigot_add.png │ ├── random.dat │ ├── random.png │ ├── self_interp_size.dat │ └── self_interp_size.png ├── NBE-memo │ ├── church_add.dat │ ├── church_add.png │ ├── church_mul.dat │ ├── church_mul.png │ ├── exponential.dat │ ├── exponential.png │ ├── parigot_add.dat │ ├── parigot_add.png │ ├── random.dat │ ├── random.png │ ├── self_interp_size.dat │ └── self_interp_size.png ├── NBE-variants │ ├── church_add.dat │ ├── church_add.png │ ├── church_mul.dat │ ├── church_mul.png │ ├── exponential.dat │ ├── exponential.png │ ├── parigot_add.dat │ ├── parigot_add.png │ ├── random.dat │ ├── random.png │ ├── self_interp_size.dat │ └── self_interp_size.png ├── compiled-compile │ ├── church_mul.dat │ ├── church_mul.png │ ├── exponential.dat │ ├── exponential.png │ ├── parigot_add.dat │ ├── parigot_add.png │ ├── random.dat │ └── random.png ├── compiled-normalize │ ├── church_mul.dat │ ├── church_mul.png │ ├── exponential.dat │ ├── exponential.png │ ├── parigot_add.dat │ ├── parigot_add.png │ ├── random.dat │ └── random.png └── subst-NBE │ ├── church_add.dat │ ├── church_add.png │ ├── church_mul.dat │ ├── church_mul.png │ ├── exponential.dat │ ├── exponential.png │ ├── parigot_add.dat │ ├── parigot_add.png │ ├── random.dat │ ├── random.png │ ├── self_interp_size.dat │ └── self_interp_size.png ├── dune ├── dune-project ├── gen_random_terms.ml └── plot.sh /.gitignore: -------------------------------------------------------------------------------- 1 | _build 2 | data/randterm* 3 | data/term_counts 4 | -------------------------------------------------------------------------------- /Common/ApproxNum.ml: -------------------------------------------------------------------------------- 1 | 2 | (* (mantissa, exponent) *) 3 | type t = float * int 4 | 5 | let of_int i = Float.frexp (Float.of_int i) 6 | 7 | let (<+>) (m1, e1) (m2, e2) = 8 | let (m1, m2, e) = 9 | match Int.compare e1 e2 with 10 | | 0 -> (m1, m2, e1) 11 | | -1 -> (Float.ldexp m1 (e1 - e2), m2, e2) 12 | | _ -> (m1, Float.ldexp m2 (e2 - e1), e1) 13 | in 14 | let m' = m1 +. m2 in 15 | if m' >= 1. || m' <= -1. 16 | then (m' *. 0.5, e + 1) 17 | else if -0.5 < m' && m' < 0.5 18 | then (m' *. 2., e - 1) 19 | else (m', e) 20 | 21 | let (<->) (m1, e1) (m2, e2) = 22 | let (m1, m2, e) = 23 | match Int.compare e1 e2 with 24 | | 0 -> (m1, m2, e1) 25 | | -1 -> (Float.ldexp m1 (e1 - e2), m2, e2) 26 | | _ -> (m1, Float.ldexp m2 (e2 - e1), e1) 27 | in 28 | let m' = m1 -. m2 in 29 | if m' >= 1. || m' <= -1. 30 | then (m' *. 0.5, e + 1) 31 | else if -0.5 < m' && m' < 0.5 32 | then (m' *. 2., e - 1) 33 | else (m', e) 34 | 35 | let (<*>) (m1, e1) (m2, e2) = 36 | let m' = m1 *. m2 in 37 | if -0.5 < m' && m' < 0.5 38 | then (m' *. 2., e1 + e2 - 1) 39 | else (m', e1 + e2) 40 | 41 | let ( *> ) f (m, e) = 42 | let ff, fe = Float.frexp f in 43 | let m' = ff *. m in 44 | if -0.5 < m' && m' < 0.5 45 | then (m' *. 2., fe + e - 1) 46 | else (m', fe + e) 47 | 48 | let compare (m1, e1) (m2, e2) = 49 | match Int.compare e1 e2 with 50 | | 0 -> Float.compare m1 m2 51 | | o -> o 52 | 53 | 54 | let pp fmt (m, e) = 55 | let r, k = Float.modf (Float.of_int e *. Float.log 2. /. Float.log 10.) in 56 | Format.fprintf fmt "%fe%d" 57 | (m *. Float.exp (r *. Float.log 10.)) (Float.to_int k) 58 | -------------------------------------------------------------------------------- /Common/Data.ml: -------------------------------------------------------------------------------- 1 | 2 | module TMap = struct 3 | open Map.Make(Int) 4 | 5 | type nonrec 'a t = 6 | { data : 'a t 7 | ; len : int } 8 | 9 | let empty = { data = empty; len = 0 } 10 | 11 | let push value tmap = 12 | { data = add tmap.len value tmap.data 13 | ; len = tmap.len + 1 } 14 | 15 | let get idx tmap = 16 | find (tmap.len - idx - 1) tmap.data 17 | end 18 | 19 | module ArrayStack = struct 20 | type 'a t = 21 | { mutable data : 'a Array.t 22 | ; mutable len : int 23 | ; garbage : 'a } 24 | 25 | exception Stack_Overflow 26 | 27 | 28 | let create ?(init_size=200) ~garbage () = 29 | { data = Array.make init_size garbage 30 | ; len = 0 31 | ; garbage = garbage } 32 | 33 | let push value vec = 34 | if vec.len >= Array.length vec.data then 35 | raise Stack_Overflow; 36 | Array.unsafe_set vec.data vec.len value; 37 | vec.len <- vec.len + 1 38 | 39 | let get idx vec = vec.data.(vec.len - idx - 1) 40 | let last vec = vec.data.(vec.len - 1) 41 | 42 | let pop vec = 43 | vec.len <- vec.len - 1 44 | 45 | let copy vec = { vec with data = Array.sub vec.data 0 vec.len } 46 | 47 | let to_array vec = Array.init vec.len (fun i -> vec.data.(i)) 48 | end 49 | 50 | 51 | 52 | module SkewList = struct 53 | type 'a tree = 54 | | Leaf of 'a 55 | | Branch of 'a tree * 'a * 'a tree 56 | 57 | type 'a t = (int * 'a tree) list 58 | 59 | 60 | let empty = [] 61 | 62 | 63 | let push elem t = 64 | match t with 65 | | (m, l) :: (n, r) :: t' when m = n -> 66 | (2 * m + 1, Branch(l, elem, r)) :: t' 67 | | _ -> 68 | (1, Leaf elem) :: t 69 | 70 | let pop t = 71 | match t with 72 | | [] -> 73 | failwith "SkewList.pop" 74 | | (_, Leaf _) :: t' -> 75 | t' 76 | | (m, Branch(l, _, r)) :: t' -> 77 | let m' = (m - 1) / 2 in 78 | (m', l) :: (m', r) :: t' 79 | 80 | 81 | let rec get_tree idx (m, tree) = 82 | match tree with 83 | | Leaf e 84 | | Branch(_, e, _) when idx = 0 -> 85 | e 86 | | Branch(l, _, r) -> 87 | let m' = (m - 1) / 2 in 88 | if idx - 1 < m' 89 | then get_tree (idx - 1) (m', l) 90 | else get_tree (idx - 1 - m') (m', r) 91 | | _ -> 92 | failwith "SkewList.get" 93 | 94 | let rec get idx t = 95 | match t with 96 | | [] -> 97 | failwith "SkewList.get" 98 | | (m, tree) :: _ when idx < m -> 99 | get_tree idx (m, tree) 100 | | (m, _) :: t' -> 101 | get (idx - m) t' 102 | end 103 | -------------------------------------------------------------------------------- /Common/Syntax.ml: -------------------------------------------------------------------------------- 1 | 2 | type term = 3 | | Idx of int 4 | | Lam of term 5 | | App of term * term 6 | 7 | 8 | let rec term_size tm = 9 | match tm with 10 | | Idx _ -> 0 11 | | Lam tm' -> 1 + term_size tm' 12 | | App(f, a) -> 1 + term_size f + term_size a 13 | 14 | 15 | 16 | let rec pp_term ctx fmt tm = 17 | let open Format in 18 | match ctx, tm with 19 | | _ , Idx idx -> fprintf fmt "%d" idx 20 | | `Arg, _ -> fprintf fmt "(%a)" (pp_term `Any) tm 21 | | `Fun, App(f, a) -> fprintf fmt "%a@ %a" 22 | (pp_term `Fun) f (pp_term `Arg) a 23 | | _ , App(f, a) -> fprintf fmt "@[%a@ %a@]" 24 | (pp_term `Fun) f (pp_term `Arg) a 25 | | `Lam, Lam body -> fprintf fmt "lam.@ %a" (pp_term `Lam) body 26 | | `Any, Lam body -> fprintf fmt "@[lam.@ %a@]" (pp_term `Lam) body 27 | | _ , Lam _ -> fprintf fmt "(%a)" (pp_term `Any) tm 28 | 29 | let pp_term = pp_term `Any 30 | 31 | 32 | 33 | 34 | 35 | 36 | let serialize tm = 37 | let open Printf in 38 | let buf = Buffer.create 20 in 39 | let rec loop tm = 40 | match tm with 41 | | Idx idx -> bprintf buf "%08x" idx 42 | | Lam tm' -> Buffer.add_char buf 'l'; loop tm' 43 | | App(f, a) -> Buffer.add_char buf '@'; loop f; loop a 44 | in 45 | loop tm; buf 46 | 47 | let deserialize src = 48 | let digit_to_int c = 49 | match c with 50 | | '0'..'9' -> Char.code c - Char.code '0' 51 | | 'a'..'f' -> Char.code c - Char.code 'a' + 10 52 | | _ -> failwith("Syntax.deserialize: invalid char " ^ String.make 1 c) 53 | in 54 | let rec loop () = 55 | match input_char src with 56 | | 'l' -> Lam(loop ()) 57 | | '@' -> 58 | let tm1 = loop () in 59 | let tm2 = loop () in 60 | App(tm1, tm2) 61 | | c0 -> 62 | let d0 = digit_to_int c0 in 63 | let d1 = digit_to_int (input_char src) in 64 | let d2 = digit_to_int (input_char src) in 65 | let d3 = digit_to_int (input_char src) in 66 | let d4 = digit_to_int (input_char src) in 67 | let d5 = digit_to_int (input_char src) in 68 | let d6 = digit_to_int (input_char src) in 69 | let d7 = digit_to_int (input_char src) in 70 | Idx(d0 lsl 28 + d1 lsr 24 + d2 lsr 20 + d3 lsr 16 71 | + d4 lsr 12 + d5 lsr 8 + d6 lsr 4 + d7) 72 | in 73 | loop () 74 | -------------------------------------------------------------------------------- /Common/Terms.ml: -------------------------------------------------------------------------------- 1 | 2 | open Syntax 3 | 4 | let apply f args = List.fold_left (fun f a -> App(f, a)) f args 5 | 6 | let id = Lam(Idx 0) 7 | 8 | 9 | let combine_terms tms = Lam(apply (Idx 0) tms) 10 | 11 | 12 | let rec church_aux = function 13 | | 0 -> Idx 0 14 | | n -> App(Idx 1, church_aux (n - 1)) 15 | 16 | let church n = Lam(Lam(church_aux n)) 17 | 18 | let church_zero = Lam(Lam(Idx 0)) 19 | let church_succ = 20 | Lam( (* 2 = n *) 21 | Lam( (* 1 = succ *) 22 | Lam( (* 0 = zero *) 23 | apply (Idx 1) 24 | [apply (Idx 2) [Idx 1; Idx 0]] 25 | ) 26 | ) 27 | ) 28 | 29 | let church_add = 30 | Lam( (* m = 3 *) 31 | Lam( (* n = 2 *) 32 | Lam( (* succ = 1 *) 33 | Lam( (* zero = 0 *) 34 | apply (Idx 3) 35 | [Idx 1; apply (Idx 2) [Idx 1; Idx 0]] 36 | ) 37 | ) 38 | ) 39 | ) 40 | 41 | let church_mul = 42 | Lam( (* m = 3 *) 43 | Lam( (* n = 2 *) 44 | Lam( (* succ = 1 *) 45 | Lam( (* zero = 0 *) 46 | apply (Idx 3) 47 | [apply (Idx 2) [Idx 1]; Idx 0] 48 | ) 49 | ) 50 | ) 51 | ) 52 | 53 | 54 | 55 | 56 | let parigot_zero = Lam(Lam(Idx 0)) 57 | let parigot_succ = 58 | Lam( (* n = 2 *) 59 | Lam( (* case_succ = 1 *) 60 | Lam( (* case_zero = 0 *) 61 | apply (Idx 1) 62 | [Idx 2; apply (Idx 2) [Idx 1; Idx 0]] 63 | ) 64 | ) 65 | ) 66 | 67 | 68 | let rec parigot_aux n = 69 | match n with 70 | | 0 -> Idx 0 71 | | _ -> 72 | let n = parigot_aux (n - 1) in 73 | apply (Idx 1) [Lam(Lam n); n] 74 | 75 | let parigot n = Lam(Lam(parigot_aux n)) 76 | 77 | 78 | let rec parigot_shared n = 79 | match n with 80 | | 0 -> parigot_zero 81 | | _ -> App(parigot_succ, parigot_shared (n - 1)) 82 | 83 | 84 | 85 | let parigot_add = 86 | Lam( (* m = 1 *) 87 | Lam( (* n = 0 *) 88 | apply (Idx 1) 89 | [ Lam(Lam(apply parigot_succ [Idx 0])) 90 | ; Idx 0 ] 91 | ) 92 | ) 93 | 94 | let parigot_mul = 95 | Lam( (* m = 1 *) 96 | Lam( (* n = 0 *) 97 | apply (Idx 1) 98 | [ Lam(apply parigot_add [Idx 1]) 99 | ; Idx 0 ] 100 | ) 101 | ) 102 | 103 | 104 | 105 | 106 | let idx n = 107 | Lam( (* case_idx = 2 *) 108 | Lam( (* case_lam = 1 *) 109 | Lam( (* case_app = 0 *) 110 | apply (Idx 2) [n] 111 | ) 112 | ) 113 | ) 114 | 115 | let lam t = 116 | Lam( (* case_idx = 2 *) 117 | Lam( (* case_lam = 1 *) 118 | Lam( (* case_app = 0 *) 119 | apply (Idx 1) [apply t [Idx 2; Idx 1; Idx 0]] 120 | ) 121 | ) 122 | ) 123 | 124 | let app f a = 125 | Lam( (* case_idx = 2 *) 126 | Lam( (* case_lam = 1 *) 127 | Lam( (* case_app = 0 *) 128 | apply (Idx 0) 129 | [ apply f [Idx 2; Idx 1; Idx 0] 130 | ; apply a [Idx 2; Idx 1; Idx 0] ] 131 | ) 132 | ) 133 | ) 134 | 135 | let church_lam_size = 136 | Lam( (* tm = 0 *) 137 | apply (Idx 0) 138 | [ Lam(church 1) 139 | ; church_succ 140 | ; Lam(Lam(apply church_succ 141 | [apply church_add [Idx 0; Idx 1]])) ] 142 | ) 143 | 144 | let rec encode_term tm = 145 | match tm with 146 | | Idx n -> idx (church n) 147 | | Lam tm' -> lam (encode_term tm') 148 | | App(f, a) -> app (encode_term f) (encode_term a) 149 | -------------------------------------------------------------------------------- /Common/dune: -------------------------------------------------------------------------------- 1 | 2 | (library 3 | (name common)) 4 | -------------------------------------------------------------------------------- /Normalizers/AbstractMachine.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | 5 | module ListStack = struct 6 | type value = 7 | | VLvl of int 8 | | VClo of (value list * term) 9 | | VNF of term 10 | 11 | type stack_frame = 12 | | UseArg of value 13 | | QuoteLam 14 | | QuoteFun of term 15 | 16 | let rec run (level, value, stack) = 17 | match value, stack with 18 | | VClo(env, Idx idx), _ -> 19 | run (level, List.nth env idx, stack) 20 | | VClo(env, App(f, a)), _ -> 21 | run (level, VClo(env, f), UseArg(VClo(env, a)) :: stack) 22 | | VClo(env, Lam tm'), UseArg v :: stack' -> 23 | run (level, VClo(v :: env, tm'), stack') 24 | | VClo(env, Lam tm'), _ -> 25 | run (level + 1, VClo(VLvl level :: env, tm'), QuoteLam :: stack) 26 | 27 | | VLvl lvl, _ -> 28 | run (level, VNF(Idx(level - 1 - lvl)), stack) 29 | | VNF f, UseArg a :: stack' -> 30 | run (level, a, QuoteFun f :: stack') 31 | | VNF t, QuoteLam :: stack' -> 32 | run (level - 1, VNF(Lam t), stack') 33 | | VNF a, QuoteFun f :: stack' -> 34 | run (level, VNF(App(f, a)), stack') 35 | | VNF t, [] -> 36 | t 37 | 38 | let normalize tm = run (0, VClo([], tm), []) 39 | end 40 | 41 | 42 | module ADTStack = struct 43 | type value = 44 | | VLvl of int 45 | | VClo of (value list * term) 46 | | VNF of term 47 | 48 | type stack = 49 | | Empty 50 | | UseArg of (value list * term) * stack 51 | | QuoteFun of term * stack 52 | | QuoteLam of stack 53 | 54 | let rec run (level, value, stack) = 55 | match value, stack with 56 | | VClo(env, Idx idx ), _ -> 57 | run (level, List.nth env idx, stack) 58 | | VClo(env, App(f, a)), _ -> 59 | run (level, VClo(env, f), UseArg((env, a), stack)) 60 | | VClo(env, Lam body), UseArg(clo, stack') -> 61 | run (level, VClo(VClo clo :: env, body), stack') 62 | | VClo(env, Lam body), _ -> 63 | run (level + 1, VClo(VLvl level :: env, body), QuoteLam stack) 64 | | VLvl lvl, _ -> 65 | run (level, VNF(Idx(level - lvl - 1)), stack) 66 | | VNF f, UseArg(clo, stack') -> run (level, VClo clo, QuoteFun(f, stack')) 67 | | VNF a, QuoteFun(f, stack') -> run (level, VNF(App(f, a)), stack') 68 | | VNF t, QuoteLam stack' -> run (level - 1, VNF(Lam t), stack') 69 | | VNF t, Empty -> t 70 | 71 | let normalize tm = run (0, VClo([], tm), Empty) 72 | end 73 | 74 | 75 | module ArrayStack = struct 76 | type value = 77 | | VLvl of int 78 | | VClo of (value list * term) 79 | | VNF of term 80 | 81 | type stack_frame = 82 | | UseArg of (value list * term) 83 | | QuoteLam 84 | | QuoteFun of term 85 | | Halt 86 | 87 | module Stack = Common.Data.ArrayStack 88 | 89 | let garbage = Halt 90 | 91 | let rec run (level, value, stack) = 92 | match value, Stack.last stack with 93 | | VClo(env, Idx idx ), _ -> 94 | run (level, List.nth env idx, stack) 95 | | VClo(env, App(f, a)), _ -> 96 | Stack.push (UseArg(env, a)) stack; 97 | run (level, VClo(env, f), stack) 98 | | VClo(env, Lam body), UseArg clo -> 99 | Stack.pop stack; 100 | run (level, VClo(VClo clo :: env, body), stack) 101 | | VClo(env, Lam body), _ -> 102 | Stack.push QuoteLam stack; 103 | run (level + 1, VClo(VLvl level :: env, body), stack) 104 | | VLvl lvl, _ -> 105 | run (level, VNF(Idx(level - lvl - 1)), stack) 106 | | VNF f, UseArg clo -> 107 | Stack.pop stack; 108 | Stack.push (QuoteFun f) stack; 109 | run (level, VClo clo, stack) 110 | | VNF a, QuoteFun f -> Stack.pop stack; run (level, VNF(App(f, a)), stack) 111 | | VNF t, QuoteLam -> Stack.pop stack; run (level - 1, VNF(Lam t), stack) 112 | | VNF t, Halt -> t 113 | 114 | 115 | let make_normalizer init_size = 116 | let stack = Stack.create ~init_size ~garbage:Halt () in 117 | Stack.push Halt stack; 118 | object 119 | method normalize tm = 120 | run (0, VClo([], tm), stack) 121 | end 122 | end 123 | 124 | 125 | module CBV = struct 126 | type value = 127 | | VLvl of int 128 | | VClo of (value list * term) 129 | | VNF of term 130 | 131 | type stack = 132 | | Empty 133 | | CallFn of value * stack 134 | | UseArg of value * stack 135 | | QuoteLam of stack 136 | | QuoteFun of term * stack 137 | 138 | let rec run (level, value, stack) = 139 | match value, stack with 140 | | VClo(env, Idx idx), _ -> 141 | run (level, List.nth env idx, stack) 142 | | VClo(env, App(f, a)), _ -> 143 | run (level, VClo(env, a), CallFn(VClo(env, f), stack)) 144 | | VClo(env, Lam tm'), UseArg(v, stack') -> 145 | run (level, VClo(v :: env, tm'), stack') 146 | | value, CallFn(func, stack') -> 147 | run (level, func, UseArg(value, stack')) 148 | | VClo(env, Lam tm'), _ -> 149 | run (level + 1, VClo(VLvl level :: env, tm'), QuoteLam stack) 150 | 151 | | VLvl lvl, _ -> 152 | run (level, VNF(Idx(level - 1 - lvl)), stack) 153 | | VNF f, UseArg(a, stack') -> 154 | run (level, a, QuoteFun(f, stack')) 155 | | VNF t, QuoteLam stack' -> 156 | run (level - 1, VNF(Lam t), stack') 157 | | VNF a, QuoteFun(f, stack') -> 158 | run (level, VNF(App(f, a)), stack') 159 | | VNF t, Empty -> 160 | t 161 | 162 | let normalize tm = run (0, VClo([], tm), Empty) 163 | end 164 | 165 | 166 | let normalizer_list = Normalizer.simple_normalizer ListStack.normalize 167 | let normalizer_adt = Normalizer.simple_normalizer ADTStack.normalize 168 | let normalizer_arr = Normalizer.object_normalizer 169 | ArrayStack.make_normalizer 1000000 170 | let normalizer_cbv = Normalizer.simple_normalizer CBV.normalize 171 | -------------------------------------------------------------------------------- /Normalizers/Compiled_Evalapply.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | let prelude = " 5 | type value = 6 | | VLvl of int 7 | | VApp of value * value 8 | | VLam1 of (value -> value) 9 | | VLam2 of (value -> value -> value) 10 | | VLam3 of (value -> value -> value -> value) 11 | | VLam4 of (value -> value -> value -> value -> value) 12 | | VLam5 of (value -> value -> value -> value -> value -> value) 13 | let rec quote level value = 14 | match value with 15 | | VLvl lvl -> Idx(level - 1 - lvl) 16 | | VApp(f, a) -> App(quote level f, quote level a) 17 | | VLam1 f -> Lam(quote (level + 1) @@ f (VLvl level)) 18 | | VLam2 f -> Lam(Lam(quote (level + 2) @@ f (VLvl level) (VLvl (level + 1)))) 19 | | VLam3 f -> Lam(Lam(Lam(quote (level + 3) @@ f (VLvl level) (VLvl (level + 1)) (VLvl (level + 2))))) 20 | | VLam4 f -> Lam(Lam(Lam(Lam(quote (level + 4) @@ f (VLvl level) (VLvl (level + 1)) (VLvl (level + 2)) (VLvl (level + 3)))))) 21 | | VLam5 f -> Lam(Lam(Lam(Lam(Lam(quote (level + 5) @@ f (VLvl level) (VLvl (level + 1)) (VLvl (level + 2)) (VLvl (level + 3)) (VLvl (level + 4))))))) 22 | let app1 vf va = 23 | match vf with 24 | | VLam1 f -> f va 25 | | VLam2 f -> VLam1(fun x -> f va x) 26 | | VLam3 f -> VLam2(fun x y -> f va x y) 27 | | VLam4 f -> VLam3(fun x y z -> f va x y z) 28 | | VLam5 f -> VLam4(fun x y z w -> f va x y z w) 29 | | _ -> VApp(vf, va) 30 | [@@inline] 31 | let app2 vf va vb = 32 | match vf with 33 | | VLam1 f -> app1 (f va) vb 34 | | VLam2 f -> f va vb 35 | | VLam3 f -> VLam1(fun x -> f va vb x) 36 | | VLam4 f -> VLam2(fun x y -> f va vb x y) 37 | | VLam5 f -> VLam3(fun x y z -> f va vb x y z) 38 | | _ -> VApp(VApp(vf, va), vb) 39 | [@@inline] 40 | let app3 vf va vb vc = 41 | match vf with 42 | | VLam1 f -> app2 (f va) vb vc 43 | | VLam2 f -> app1 (f va vb) vc 44 | | VLam3 f -> f va vb vc 45 | | VLam4 f -> VLam1(fun x -> f va vb vc x) 46 | | VLam5 f -> VLam2(fun x y -> f va vb vc x y) 47 | | _ -> VApp(VApp(VApp(vf, va), vb), vc) 48 | [@@inline] 49 | let app4 vf va vb vc vd = 50 | match vf with 51 | | VLam1 f -> app3 (f va) vb vc vd 52 | | VLam2 f -> app2 (f va vb) vc vd 53 | | VLam3 f -> app1 (f va vb vc) vd 54 | | VLam4 f -> f va vb vc vd 55 | | VLam5 f -> VLam1(fun x -> f va vb vc vd x) 56 | | _ -> VApp(VApp(VApp(VApp(vf, va), vb), vc), vd) 57 | [@@inline] 58 | let app5 vf va vb vc vd ve = 59 | match vf with 60 | | VLam1 f -> app4 (f va) vb vc vd ve 61 | | VLam2 f -> app3 (f va vb) vc vd ve 62 | | VLam3 f -> app2 (f va vb vc) vd ve 63 | | VLam4 f -> app1 (f va vb vc vd) ve 64 | | VLam5 f -> f va vb vc vd ve 65 | | _ -> VApp(VApp(VApp(VApp(VApp(vf, va), vb), vc), vd), ve) 66 | [@@inline] 67 | ;; 68 | " 69 | 70 | 71 | type ctx = 72 | | CtxRoot 73 | | CtxApp of int * (out_channel -> unit) list 74 | | CtxLam of int * int 75 | 76 | let dump_ctx out ctx content = 77 | match ctx with 78 | | CtxRoot -> 79 | content out 80 | | CtxLam(n, level) -> 81 | output_string out "(VLam"; 82 | output_string out (string_of_int n); 83 | output_string out "(fun"; 84 | for i = 0 to n - 1 do 85 | output_string out " x"; 86 | output_string out (string_of_int (level + i)) 87 | done; 88 | output_string out " -> "; 89 | content out; 90 | output_string out "))" 91 | | CtxApp(n, args) -> 92 | output_string out "(app"; 93 | output_string out (string_of_int n); 94 | output_string out " "; 95 | content out; 96 | args |> List.iter begin fun arg -> 97 | output_string out " "; 98 | arg out 99 | end; 100 | output_string out ")" 101 | 102 | 103 | let rec compile out level ctx tm = 104 | match tm, ctx with 105 | | Idx idx, _ -> 106 | dump_ctx out ctx 107 | (fun out -> 108 | output_string out "x"; 109 | output_string out (string_of_int (level - 1 - idx))) 110 | | Lam tm', CtxLam(n, l0) when n < 5 -> 111 | compile out (level + 1) (CtxLam(n + 1, l0)) tm' 112 | | Lam tm', CtxRoot -> 113 | compile out (level + 1) (CtxLam(1, level)) tm' 114 | | App(f, a), CtxApp(n, args) when n < 5 -> 115 | let arg out = compile out level CtxRoot a in 116 | compile out level (CtxApp(n + 1, arg :: args)) f 117 | | App(f, a), CtxRoot -> 118 | let arg out = compile out level CtxRoot a in 119 | compile out level (CtxApp(1, [arg])) f 120 | | _, _ -> 121 | dump_ctx out ctx (fun out -> compile out level CtxRoot tm) 122 | 123 | let compile out tm = 124 | output_string out "let v = "; 125 | compile out 0 CtxRoot tm; 126 | output_string out ";;\nlet nf = quote 0 v;;" 127 | 128 | let normalizer mode = Normalizer.compiled_normalizer ~mode 129 | (fun out -> output_string out prelude) 130 | compile 131 | 132 | let (byte_compile , byte_normalize ) = normalizer "byte" 133 | let (native_compile, native_normalize) = normalizer "native" 134 | let (o2_compile , o2_normalize ) = normalizer "O2" 135 | -------------------------------------------------------------------------------- /Normalizers/Compiled_HOAS.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | let prelude = " 5 | type value = 6 | | VLvl of int 7 | | VLam of (value -> value) 8 | | VApp of value * value 9 | ;; 10 | let rec quote level value = 11 | match value with 12 | | VLvl lvl -> Idx(level - 1 - lvl) 13 | | VLam f -> Lam(quote (level + 1) @@ f (VLvl level)) 14 | | VApp(f, a) -> App(quote level f, quote level a) 15 | ;; 16 | let app vf va = 17 | match vf with 18 | | VLam f -> f va 19 | | _ -> VApp(vf, va) 20 | [@@inline] 21 | ;; 22 | " 23 | 24 | let rec compile out level tm = 25 | match tm with 26 | | Idx idx -> 27 | output_string out "x"; 28 | output_string out (string_of_int (level - 1 - idx)) 29 | | Lam tm' -> 30 | output_string out "(VLam(fun x"; 31 | output_string out (string_of_int level); 32 | output_string out " -> "; 33 | compile out (level + 1) tm'; 34 | output_string out "))" 35 | | App(f, a) -> 36 | output_string out "(app "; 37 | compile out level f; 38 | output_string out " "; 39 | compile out level a; 40 | output_string out ")" 41 | 42 | let compile out tm = 43 | output_string out "let v = "; 44 | compile out 0 tm; 45 | output_string out ";;\nlet nf = quote 0 v;;" 46 | 47 | let normalizer mode = Normalizer.compiled_normalizer ~mode 48 | (fun out -> output_string out prelude) 49 | compile 50 | 51 | let (byte_compile , byte_normalize ) = normalizer "byte" 52 | let (native_compile, native_normalize) = normalizer "native" 53 | let (o2_compile , o2_normalize ) = normalizer "O2" 54 | -------------------------------------------------------------------------------- /Normalizers/NBE_Closure.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | open Common.Data 4 | 5 | module ListEnv = struct 6 | type value = 7 | | VLvl of int 8 | | VLam of value list * term 9 | | VApp of value * value 10 | 11 | 12 | let rec eval env tm = 13 | match tm with 14 | | Idx idx -> List.nth env idx 15 | | Lam tm' -> VLam(env, tm') 16 | | App(f, a) -> apply_val (eval env f) (eval env a) 17 | 18 | and apply_val vf va = 19 | match vf with 20 | | VLam(env, body) -> eval (va :: env) body 21 | | _ -> VApp(vf, va) 22 | [@@inline] 23 | 24 | let rec quote level value = 25 | match value with 26 | | VLvl lvl -> Idx(level - lvl - 1) 27 | | VLam(env, body) -> Lam(quote (level + 1) @@ eval (VLvl level :: env) body) 28 | | VApp(vf, va) -> App(quote level vf, quote level va) 29 | end 30 | 31 | module TMapEnv = struct 32 | type value = 33 | | VLvl of int 34 | | VLam of value TMap.t * term 35 | | VApp of value * value 36 | 37 | 38 | let rec eval env tm = 39 | match tm with 40 | | Idx idx -> TMap.get idx env 41 | | Lam tm' -> VLam(env, tm') 42 | | App(f, a) -> apply_val (eval env f) (eval env a) 43 | 44 | and apply_val vf va = 45 | match vf with 46 | | VLam(env, body) -> eval (TMap.push va env) body 47 | | _ -> VApp(vf, va) 48 | [@@inline] 49 | 50 | let rec quote level value = 51 | match value with 52 | | VLvl lvl -> Idx(level - lvl - 1) 53 | | VLam(env, body) -> Lam(quote (level + 1) @@ eval (TMap.push (VLvl level) env) body) 54 | | VApp(vf, va) -> App(quote level vf, quote level va) 55 | end 56 | 57 | 58 | let normalizer_list = Normalizer.simple_normalizer 59 | (fun tm -> ListEnv.(quote 0 @@ eval [] tm)) 60 | let normalizer_tree = Normalizer.simple_normalizer 61 | (fun tm -> TMapEnv.(quote 0 @@ eval TMap.empty tm)) 62 | -------------------------------------------------------------------------------- /Normalizers/NBE_HOAS.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | open Common.Data 4 | 5 | type value = 6 | | VLvl of int 7 | | VLam of (value -> value) 8 | | VApp of value * value 9 | 10 | let apply_val vf va = 11 | match vf with 12 | | VLam f -> f va 13 | | _ -> VApp(vf, va) 14 | [@@inline] 15 | 16 | 17 | let rec quote level value = 18 | match value with 19 | | VLvl lvl -> Idx(level - lvl - 1) 20 | | VLam f -> Lam(quote (level + 1) (f (VLvl level))) 21 | | VApp(f, a) -> App(quote level f, quote level a) 22 | 23 | 24 | 25 | let rec eval_list env tm = 26 | match tm with 27 | | Idx idx -> List.nth env idx 28 | | Lam tm' -> VLam(fun vx -> eval_list (vx :: env) tm') 29 | | App(f, a) -> apply_val (eval_list env f) (eval_list env a) 30 | 31 | 32 | let rec eval_map env tm = 33 | match tm with 34 | | Idx idx -> TMap.get idx env 35 | | Lam tm' -> VLam(fun vx -> eval_map (TMap.push vx env) tm') 36 | | App(f, a) -> apply_val (eval_map env f) (eval_map env a) 37 | 38 | 39 | let rec eval_skew env tm = 40 | match tm with 41 | | Idx idx -> SkewList.get idx env 42 | | Lam tm' -> VLam(fun vx -> eval_skew (SkewList.push vx env) tm') 43 | | App(f, a) -> apply_val (eval_skew env f) (eval_skew env a) 44 | 45 | 46 | let normalizer_list = Normalizer.simple_normalizer 47 | (fun tm -> quote 0 (eval_list [] tm)) 48 | let normalizer_tree = Normalizer.simple_normalizer 49 | (fun tm -> quote 0 (eval_map TMap.empty tm)) 50 | let normalizer_skew = Normalizer.simple_normalizer 51 | (fun tm -> quote 0 (eval_skew SkewList.empty tm)) 52 | -------------------------------------------------------------------------------- /Normalizers/NBE_HashConsing.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | module CacheIndex = struct 5 | 6 | let indicies = Array.init 10000 (fun idx -> Idx idx) 7 | 8 | let rec of_term = function 9 | | Idx idx -> indicies.(idx) 10 | | Lam tm' -> Lam(of_term tm') 11 | | App(f, a) -> App(of_term f, of_term a) 12 | 13 | let to_term tm = tm 14 | 15 | 16 | 17 | type value = 18 | | VLvl of int 19 | | VLam of value list * term 20 | | VApp of value * value 21 | 22 | 23 | 24 | let rec eval env tm = 25 | match tm with 26 | | Idx idx -> List.nth env idx 27 | | Lam tm' -> VLam(env, tm') 28 | | App(f, a) -> apply_val (eval env f) (eval env a) 29 | 30 | and apply_val vf va = 31 | match vf with 32 | | VLam(env, body) -> eval (va :: env) body 33 | | _ -> VApp(vf, va) 34 | [@@inline] 35 | 36 | let rec quote level value = 37 | match value with 38 | | VLvl lvl -> indicies.(level - lvl - 1) 39 | | VLam(env, body) -> Lam(quote (level + 1) @@ eval (VLvl level :: env) body) 40 | | VApp(vf, va) -> App(quote level vf, quote level va) 41 | end 42 | 43 | 44 | 45 | module CacheLevelIndex = struct 46 | 47 | let indicies = Array.init 10000 (fun idx -> Idx idx) 48 | 49 | let rec of_term = function 50 | | Idx idx -> indicies.(idx) 51 | | Lam tm' -> Lam(of_term tm') 52 | | App(f, a) -> App(of_term f, of_term a) 53 | 54 | let to_term tm = tm 55 | 56 | 57 | type value = 58 | | VLvl of int 59 | | VLam of value list * term 60 | | VApp of value * value 61 | 62 | 63 | let rec eval env tm = 64 | match tm with 65 | | Idx idx -> List.nth env idx 66 | | Lam tm' -> VLam(env, tm') 67 | | App(f, a) -> apply_val (eval env f) (eval env a) 68 | 69 | and apply_val vf va = 70 | match vf with 71 | | VLam(env, body) -> eval (va :: env) body 72 | | _ -> VApp(vf, va) 73 | [@@inline] 74 | 75 | 76 | let levels = Array.init 10000 (fun lvl -> VLvl lvl) 77 | 78 | let rec quote level value = 79 | match value with 80 | | VLvl lvl -> indicies.(level - lvl - 1) 81 | | VLam(env, body) -> Lam(quote (level + 1) @@ eval (levels.(level) :: env) body) 82 | | VApp(vf, va) -> App(quote level vf, quote level va) 83 | end 84 | 85 | 86 | type hc_term = 87 | { shape : term_shape 88 | ; hash : int } 89 | 90 | and term_shape = 91 | | HC_Idx of int 92 | | HC_Lam of hc_term 93 | | HC_App of hc_term * hc_term 94 | 95 | 96 | let shape_equal s1 s2 = 97 | match s1, s2 with 98 | | HC_Idx idx1 , HC_Idx idx2 -> idx1 = idx2 99 | | HC_Lam tm1 , HC_Lam tm2 -> tm1 == tm2 100 | | HC_App(f1, a1), HC_App(f2, a2) -> f1 == f2 && a1 == a2 101 | | _ -> false 102 | 103 | let hash_shape shape = 104 | match shape with 105 | | HC_Idx idx -> idx 106 | | HC_Lam tm' -> 19 * tm'.hash + 1 107 | | HC_App(f, a) -> 19 * (19 * f.hash + a.hash) + 2 108 | 109 | 110 | module HT = Hashtbl.Make(struct 111 | type t = hc_term 112 | let equal htm1 htm2 = shape_equal htm1.shape htm2.shape 113 | let hash htm = htm.hash 114 | end) 115 | 116 | let tbl = HT.create 10000 117 | 118 | 119 | let hashcons shape = 120 | let tm = { shape; hash = hash_shape shape } in 121 | match HT.find tbl tm with 122 | | tm -> tm 123 | | exception Not_found -> HT.add tbl tm tm; tm 124 | 125 | 126 | let idx idx = hashcons (HC_Idx idx) 127 | let lam tm' = hashcons (HC_Lam tm') 128 | let app f a = hashcons (HC_App(f, a)) 129 | 130 | 131 | 132 | let rec of_term = function 133 | | Idx i -> idx i 134 | | Lam tm' -> lam (of_term tm') 135 | | App(f, a) -> app (of_term f) (of_term a) 136 | 137 | let rec to_term tm = 138 | match tm.shape with 139 | | HC_Idx idx -> Idx idx 140 | | HC_Lam tm' -> Lam(to_term tm') 141 | | HC_App(f, a) -> App(to_term f, to_term a) 142 | 143 | 144 | 145 | module HashCons = struct 146 | type value = 147 | | VLvl of int 148 | | VLam of value list * hc_term 149 | | VApp of value * value 150 | 151 | let rec eval env tm = 152 | match tm.shape with 153 | | HC_Idx idx -> List.nth env idx 154 | | HC_Lam tm' -> VLam(env, tm') 155 | | HC_App(f, a) -> apply_val (eval env f) (eval env a) 156 | 157 | and apply_val vf va = 158 | match vf with 159 | | VLam(env, body) -> eval (va :: env) body 160 | | _ -> VApp(vf, va) 161 | [@@inline] 162 | 163 | 164 | let rec quote level value = 165 | match value with 166 | | VLvl lvl -> idx (level - lvl - 1) 167 | | VLam(env, body) -> lam (quote (level + 1) @@ eval (VLvl level :: env) body) 168 | | VApp(vf, va) -> app (quote level vf) (quote level va) 169 | end 170 | 171 | 172 | let normalizer_cache_level_index = Normalizer.normalizer_with_alt_term_rep 173 | ~of_term:CacheLevelIndex.of_term ~to_term:CacheLevelIndex.to_term 174 | ~normalize:(fun tm -> CacheLevelIndex.(quote 0 @@ eval [] tm)) 175 | 176 | let normalizer_cache_level_index_o = Normalizer.simple_normalizer 177 | (fun tm -> CacheLevelIndex.(quote 0 @@ eval [] tm)) 178 | 179 | 180 | let normalizer_cache_index = Normalizer.normalizer_with_alt_term_rep 181 | ~of_term:CacheIndex.of_term ~to_term:CacheIndex.to_term 182 | ~normalize:(fun tm -> CacheIndex.(quote 0 @@ eval [] tm)) 183 | 184 | let normalizer_cache_index_o = Normalizer.simple_normalizer 185 | (fun tm -> CacheIndex.(quote 0 @@ eval [] tm)) 186 | 187 | 188 | let normalizer_hashcons = Normalizer.normalizer_with_alt_term_rep 189 | ~of_term:of_term ~to_term:to_term 190 | ~normalize:(fun htm -> HashCons.(quote 0 @@ eval [] htm)) 191 | -------------------------------------------------------------------------------- /Normalizers/NBE_Lazy.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | type value = 5 | | VLvl of int 6 | | VLam of (value Lazy.t -> value Lazy.t) 7 | | VApp of value Lazy.t * value Lazy.t 8 | 9 | let rec quote level value = 10 | match Lazy.force value with 11 | | VLvl lvl -> Idx(level - lvl - 1) 12 | | VLam f -> Lam(quote (level + 1) @@ f @@ lazy(VLvl level)) 13 | | VApp(f, a) -> App(quote level f, quote level a) 14 | 15 | let apply_val vf va = 16 | match Lazy.force vf with 17 | | VLam f -> Lazy.force (f va) 18 | | _ -> VApp(vf, va) 19 | [@@inline] 20 | 21 | let rec eval env tm = 22 | match tm with 23 | | Idx idx -> List.nth env idx 24 | | Lam tm' -> Lazy.from_val @@ VLam(fun vx -> eval (vx :: env) tm') 25 | | App(f, a) -> lazy(apply_val (eval env f) (eval env a)) 26 | 27 | 28 | let normalizer = Normalizer.simple_normalizer 29 | (fun tm -> quote 0 (eval [] tm)) 30 | -------------------------------------------------------------------------------- /Normalizers/NBE_Memo.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | module V1 = struct 5 | type value = 6 | { rep : value_rep 7 | ; mutable syn : (int * term) option } 8 | 9 | and value_rep = 10 | | VLvl of int 11 | | VLam of (value list * term) 12 | | VApp of value * value 13 | 14 | 15 | let mkv rep = { rep; syn = None } [@@inline] 16 | 17 | let rec eval env tm = 18 | match tm with 19 | | Idx idx -> List.nth env idx 20 | | Lam tm' -> mkv @@ VLam(env, tm') 21 | | App(f, a) -> 22 | match eval env f with 23 | | {rep=VLam clo; _} -> apply_closure clo (eval env a) 24 | | vf -> mkv @@ VApp(vf, eval env a) 25 | 26 | and apply_closure (env, tm') va = eval (va :: env) tm' [@@inline] 27 | 28 | 29 | let rec quote level v = 30 | match v.syn with 31 | | Some(level', tm) when level' = level -> 32 | tm 33 | | _ -> 34 | let tm = quote_rep level v.rep in 35 | v.syn <- Some(level, tm); 36 | tm 37 | 38 | and quote_rep level rep = 39 | match rep with 40 | | VLvl lvl -> Idx(level - 1 - lvl) 41 | | VLam clo -> Lam(quote (level + 1) @@ apply_closure clo @@ mkv (VLvl level)) 42 | | VApp(f, a) -> App(quote level f, quote level a) 43 | [@@inline] 44 | 45 | let normalize tm = quote 0 (eval [] tm) 46 | end 47 | 48 | 49 | module V2 = struct 50 | type value = 51 | { rep : value_rep 52 | ; mutable lvl : int 53 | ; mutable syn : term } 54 | 55 | and value_rep = 56 | | VLvl of int 57 | | VLam of (value list * term) 58 | | VApp of value * value 59 | 60 | 61 | let mkv rep = { rep; lvl = -1; syn = Idx 0 } [@@inline] 62 | 63 | let rec eval env tm = 64 | match tm with 65 | | Idx idx -> List.nth env idx 66 | | Lam tm' -> mkv @@ VLam(env, tm') 67 | | App(f, a) -> 68 | match eval env f with 69 | | {rep=VLam clo; _} -> apply_closure clo (eval env a) 70 | | vf -> mkv @@ VApp(vf, eval env a) 71 | 72 | and apply_closure (env, tm') va = eval (va :: env) tm' [@@inline] 73 | 74 | 75 | let rec quote level v = 76 | if level = v.lvl 77 | then v.syn 78 | else 79 | let syn = quote_rep level v.rep in 80 | v.lvl <- level; v.syn <- syn; 81 | syn 82 | 83 | and quote_rep level rep = 84 | match rep with 85 | | VLvl lvl -> Idx(level - 1 - lvl) 86 | | VLam clo -> Lam(quote (level + 1) @@ apply_closure clo @@ mkv (VLvl level)) 87 | | VApp(f, a) -> App(quote level f, quote level a) 88 | [@@inline] 89 | 90 | let normalize tm = quote 0 (eval [] tm) 91 | end 92 | 93 | 94 | module V3 = struct 95 | type value = 96 | | VLvl of 97 | { mutable lvl : int 98 | ; mutable syn : term 99 | ; value : int } 100 | | VLam of 101 | { mutable lvl : int 102 | ; mutable syn : term 103 | ; env : value list 104 | ; body : term } 105 | | VApp of 106 | { mutable lvl : int 107 | ; mutable syn : term 108 | ; func : value 109 | ; arg : value } 110 | 111 | 112 | let vlvl value = VLvl { lvl = -1; syn = Idx 0; value } [@@inline] 113 | let vlam env body = VLam { lvl = -1; syn = Idx 0; env; body } [@@inline] 114 | let vapp func arg = VApp { lvl = -1; syn = Idx 0; func; arg } [@@inline] 115 | 116 | let rec eval env tm = 117 | match tm with 118 | | Idx idx -> List.nth env idx 119 | | Lam tm' -> vlam env tm' 120 | | App(f, a) -> 121 | match eval env f with 122 | | VLam{env=env'; body; _} -> eval (eval env a :: env') body 123 | | vf -> vapp vf (eval env a) 124 | 125 | 126 | let rec quote level v = 127 | match v with 128 | | VLvl r -> 129 | if level = r.lvl 130 | then r.syn 131 | else 132 | let syn = Idx(level - 1 - r.value) in 133 | r.lvl <- level; r.syn <- syn; 134 | syn 135 | | VLam r -> 136 | if level = r.lvl 137 | then r.syn 138 | else 139 | let syn = Lam(quote (level + 1) @@ eval (vlvl level :: r.env) r.body) in 140 | r.lvl <- level; r.syn <- syn; 141 | syn 142 | | VApp r -> 143 | if level = r.lvl 144 | then r.syn 145 | else 146 | let syn = App(quote level r.func, quote level r.arg) in 147 | r.lvl <- level; r.syn <- syn; 148 | syn 149 | 150 | let normalize tm = quote 0 (eval [] tm) 151 | end 152 | 153 | 154 | module V4 = struct 155 | type value = 156 | | VLvl of int 157 | | VLam of 158 | { mutable lvl : int 159 | ; mutable syn : term 160 | ; env : value list 161 | ; body : term } 162 | | VApp of 163 | { mutable lvl : int 164 | ; mutable syn : term 165 | ; func : value 166 | ; arg : value } 167 | 168 | 169 | let vlam env body = VLam { lvl = -1; syn = Idx 0; env; body } [@@inline] 170 | let vapp func arg = VApp { lvl = -1; syn = Idx 0; func; arg } [@@inline] 171 | 172 | let rec eval env tm = 173 | match tm with 174 | | Idx idx -> List.nth env idx 175 | | Lam tm' -> vlam env tm' 176 | | App(f, a) -> 177 | match eval env f with 178 | | VLam{env=env'; body; _} -> eval (eval env a :: env') body 179 | | vf -> vapp vf (eval env a) 180 | 181 | 182 | let rec quote level v = 183 | match v with 184 | | VLvl lvl -> Idx(level - 1 - lvl) 185 | | VLam r -> 186 | if level = r.lvl 187 | then r.syn 188 | else 189 | let syn = Lam(quote (level + 1) @@ eval (VLvl level :: r.env) r.body) in 190 | r.lvl <- level; r.syn <- syn; 191 | syn 192 | | VApp r -> 193 | if level = r.lvl 194 | then r.syn 195 | else 196 | let syn = App(quote level r.func, quote level r.arg) in 197 | r.lvl <- level; r.syn <- syn; 198 | syn 199 | 200 | let normalize tm = quote 0 (eval [] tm) 201 | end 202 | 203 | 204 | module Named = struct 205 | type named_term = 206 | | NVar of int 207 | | NLam of int * named_term 208 | | NApp of named_term * named_term 209 | 210 | let seed = ref (-1) 211 | let fresh_var () = incr seed; !seed 212 | 213 | let rec get_idx var vars = 214 | match vars with 215 | | var' :: _ when var' = var -> 0 216 | | _ :: vars' -> get_idx var vars' + 1 217 | | _ -> failwith "NBE_Memo.Named.get_idx" 218 | 219 | let rec of_term vars tm = 220 | match tm with 221 | | Idx idx -> NVar(List.nth vars idx) 222 | | Lam tm' -> 223 | let v = fresh_var () in 224 | NLam(v, of_term (v :: vars) tm') 225 | | App(f, a) -> 226 | NApp(of_term vars f, of_term vars a) 227 | 228 | let rec to_term vars ntm = 229 | match ntm with 230 | | NVar var -> Idx(get_idx var vars) 231 | | NLam(var, body) -> Lam(to_term (var :: vars) body) 232 | | NApp(func, arg) -> App(to_term vars func, to_term vars arg) 233 | 234 | 235 | type value = 236 | | VVar of 237 | { mutable syn : named_term 238 | ; var : int } 239 | | VLam of 240 | { mutable syn : named_term 241 | ; env : (int * value) list 242 | ; var : int 243 | ; body : named_term } 244 | | VApp of 245 | { mutable syn : named_term 246 | ; func : value 247 | ; arg : value } 248 | 249 | let garbage = NVar (-1) 250 | let vvar var = VVar { syn = garbage; var } 251 | let vlam env var body = VLam { syn = garbage; env; var; body } [@@inline] 252 | let vapp func arg = VApp { syn = garbage; func; arg } [@@inline] 253 | 254 | let rec eval env ntm = 255 | match ntm with 256 | | NVar var -> List.assoc var env 257 | | NLam(var, body) -> vlam env var body 258 | | NApp(func, arg) -> 259 | match eval env func with 260 | | VLam { env = env'; var; body } -> 261 | eval ((var, eval env arg) :: env') body 262 | | vfunc -> 263 | vapp vfunc (eval env arg) 264 | 265 | let rec quote v = 266 | match v with 267 | | VVar r -> 268 | if r.syn == garbage 269 | then 270 | let syn = NVar r.var in 271 | ( r.syn <- syn; syn ) 272 | else 273 | r.syn 274 | | VLam r -> 275 | if r.syn == garbage 276 | then 277 | let var = fresh_var () in 278 | let syn = NLam(var, quote @@ eval ((r.var, vvar var) :: r.env) r.body) in 279 | ( r.syn <- syn; syn ) 280 | else 281 | r.syn 282 | | VApp r -> 283 | if r.syn == garbage 284 | then 285 | let syn = NApp(quote r.func, quote r.arg) in 286 | ( r.syn <- syn; syn ) 287 | else 288 | r.syn 289 | 290 | let of_term = of_term [] 291 | let to_term = to_term [] 292 | let normalize tm = quote (eval [] tm) 293 | end 294 | 295 | 296 | let normalizer_v1 = Normalizer.simple_normalizer V1.normalize 297 | let normalizer_v2 = Normalizer.simple_normalizer V2.normalize 298 | let normalizer_v3 = Normalizer.simple_normalizer V3.normalize 299 | let normalizer_v4 = Normalizer.simple_normalizer V4.normalize 300 | let normalizer_named = 301 | let open Named in 302 | Normalizer.normalizer_with_alt_term_rep ~of_term ~normalize ~to_term 303 | -------------------------------------------------------------------------------- /Normalizers/NBE_Named.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | type named_term = 5 | | NVar of int 6 | | NLam of int * named_term 7 | | NApp of named_term * named_term 8 | 9 | let seed = ref (-1) 10 | let fresh_var () = incr seed; !seed 11 | 12 | let rec get_idx var vars = 13 | match vars with 14 | | var' :: _ when var' = var -> 0 15 | | _ :: vars' -> get_idx var vars' + 1 16 | | _ -> failwith "NBE_Named.get_idx" 17 | 18 | let rec of_term vars tm = 19 | match tm with 20 | | Idx idx -> NVar(List.nth vars idx) 21 | | Lam tm' -> 22 | let v = fresh_var () in 23 | NLam(v, of_term (v :: vars) tm') 24 | | App(f, a) -> 25 | NApp(of_term vars f, of_term vars a) 26 | 27 | let rec to_term vars ntm = 28 | match ntm with 29 | | NVar var -> Idx(get_idx var vars) 30 | | NLam(var, body) -> Lam(to_term (var :: vars) body) 31 | | NApp(func, arg) -> App(to_term vars func, to_term vars arg) 32 | 33 | 34 | let of_term = of_term [] 35 | let to_term = to_term [] 36 | 37 | 38 | module ListEnv = struct 39 | type value = 40 | | VVar of int 41 | | VLam of (int * value) list * int * named_term 42 | | VApp of value * value 43 | 44 | let rec eval env ntm = 45 | match ntm with 46 | | NVar var -> List.assoc var env 47 | | NLam(var, body) -> VLam(env, var, body) 48 | | NApp(func, arg) -> 49 | match eval env func with 50 | | VLam(env', var, body) -> 51 | eval ((var, eval env arg) :: env') body 52 | | vfunc -> 53 | VApp(vfunc, eval env arg) 54 | 55 | let rec quote v = 56 | match v with 57 | | VVar var -> NVar var 58 | | VLam(env, var, body) -> 59 | let var' = fresh_var () in 60 | NLam(var', quote @@ eval ((var, VVar var') :: env) body) 61 | | VApp(func, arg) -> 62 | NApp(quote func, quote arg) 63 | 64 | let normalize tm = quote (eval [] tm) 65 | end 66 | 67 | module TreeEnv = struct 68 | module Env = Map.Make(Int) 69 | type value = 70 | | VVar of int 71 | | VLam of value Env.t * int * named_term 72 | | VApp of value * value 73 | 74 | let rec eval env ntm = 75 | match ntm with 76 | | NVar var -> Env.find var env 77 | | NLam(var, body) -> VLam(env, var, body) 78 | | NApp(func, arg) -> 79 | match eval env func with 80 | | VLam(env', var, body) -> 81 | eval (Env.add var (eval env arg) env') body 82 | | vfunc -> 83 | VApp(vfunc, eval env arg) 84 | 85 | let rec quote v = 86 | match v with 87 | | VVar var -> NVar var 88 | | VLam(env, var, body) -> 89 | let var' = fresh_var () in 90 | NLam(var', quote @@ eval (Env.add var (VVar var') env) body) 91 | | VApp(func, arg) -> 92 | NApp(quote func, quote arg) 93 | 94 | let normalize tm = quote (eval Env.empty tm) 95 | end 96 | 97 | module ADTEnv = struct 98 | type value = 99 | | VVar of int 100 | | VLam of env * int * named_term 101 | | VApp of value * value 102 | 103 | and env = 104 | | Nil 105 | | Cons of int * value * env 106 | 107 | 108 | let rec lookup k env = 109 | match env with 110 | | Cons(k', v, _) when k' = k -> v 111 | | Cons(_, _, env') -> lookup k env' 112 | | Nil -> failwith "NBE_Named.ADTEnv.lookup" 113 | 114 | let rec eval env ntm = 115 | match ntm with 116 | | NVar var -> lookup var env 117 | | NLam(var, body) -> VLam(env, var, body) 118 | | NApp(func, arg) -> 119 | match eval env func with 120 | | VLam(env', var, body) -> 121 | eval (Cons(var, eval env arg, env')) body 122 | | vfunc -> 123 | VApp(vfunc, eval env arg) 124 | 125 | let rec quote v = 126 | match v with 127 | | VVar var -> NVar var 128 | | VLam(env, var, body) -> 129 | let var' = fresh_var () in 130 | NLam(var', quote @@ eval (Cons(var, VVar var', env)) body) 131 | | VApp(func, arg) -> 132 | NApp(quote func, quote arg) 133 | 134 | let normalize tm = quote (eval Nil tm) 135 | end 136 | 137 | let normalizer_list = Normalizer.normalizer_with_alt_term_rep 138 | ~of_term ~normalize:ListEnv.normalize ~to_term 139 | 140 | let normalizer_tree = Normalizer.normalizer_with_alt_term_rep 141 | ~of_term ~normalize:TreeEnv.normalize ~to_term 142 | 143 | let normalizer_adt = Normalizer.normalizer_with_alt_term_rep 144 | ~of_term ~normalize:ADTEnv.normalize ~to_term 145 | -------------------------------------------------------------------------------- /Normalizers/NBE_Pushenter.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | type value = 5 | | VLvl of int 6 | | VLam of value list * term 7 | | VApp of value * value list 8 | 9 | let rec eval (env, args) tm = 10 | match tm, args with 11 | | Idx idx, [] -> List.nth env idx 12 | | Idx idx, _ -> apply_val (List.nth env idx) args 13 | | Lam tm', [] -> VLam(env, tm') 14 | | Lam tm', arg :: args' -> 15 | eval (arg :: env, args') tm' 16 | | App(f, a), _ -> 17 | eval (env, (eval (env, []) a) :: args) f 18 | 19 | and apply_val vf args = 20 | match vf, args with 21 | | VLam(env, tm), arg :: args' -> eval (arg :: env, args') tm 22 | | _ -> VApp(vf, args) 23 | [@@inline] 24 | 25 | 26 | let rec quote level value = 27 | match value with 28 | | VLvl lvl -> Idx(level - lvl - 1) 29 | | VLam(env, tm) -> 30 | Lam(quote (level + 1) (eval (VLvl level :: env, []) tm)) 31 | | VApp(f, args) -> 32 | List.fold_left (fun f a -> App(f, quote level a)) 33 | (quote level f) args 34 | 35 | 36 | 37 | let normalizer = Normalizer.simple_normalizer 38 | (fun tm -> quote 0 (eval ([], []) tm)) 39 | -------------------------------------------------------------------------------- /Normalizers/Normalizer.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common 3 | 4 | type normalizer = 5 | { run : Syntax.term -> Syntax.term option -> unit } 6 | 7 | exception Wrong_Answer 8 | let simple_normalizer f = 9 | let run tm expected = 10 | let t0 = Sys.time () in 11 | let nf = f tm in 12 | let t1 = Sys.time () in 13 | match expected with 14 | | Some nf' when Hashtbl.hash nf <> Hashtbl.hash nf' -> 15 | raise Wrong_Answer 16 | | _ -> 17 | Printf.printf "%f\n" (t1 -. t0) 18 | in { run } 19 | 20 | let object_normalizer constructor param = 21 | let run tm expected = 22 | let normalizer = constructor param in 23 | (simple_normalizer (fun tm -> normalizer#normalize tm)).run tm expected 24 | in { run } 25 | 26 | let normalizer_with_alt_term_rep ~of_term ~normalize ~to_term = 27 | let run tm expected = 28 | let tm' = of_term tm in 29 | let t0 = Sys.time () in 30 | let nf' = normalize tm' in 31 | let t1 = Sys.time () in 32 | let nf = to_term nf' in 33 | match expected with 34 | | Some nf' when Hashtbl.hash nf <> Hashtbl.hash nf' -> 35 | raise Wrong_Answer 36 | | _ -> 37 | Printf.printf "%f\n" (t1 -. t0) 38 | in { run } 39 | 40 | 41 | let compiled_normalizer ?(check=false) ~mode prelude compile = 42 | let rec output_term out tm = 43 | match tm with 44 | | Common.Syntax.Idx idx -> 45 | output_string out "(Idx "; 46 | output_string out (string_of_int idx); 47 | output_string out ")" 48 | | Common.Syntax.Lam tm' -> 49 | output_string out "(Lam "; 50 | output_term out tm'; 51 | output_string out ")" 52 | | Common.Syntax.App(f, a) -> 53 | output_string out "(App("; 54 | output_term out f; 55 | output_string out ", "; 56 | output_term out a; 57 | output_string out "))" 58 | in 59 | let run test_target tm expected = 60 | let target_file = "_build/tmp.ml" in 61 | let target = open_out target_file in 62 | let t0 = Sys.time () in 63 | output_string target 64 | "type term = Idx of int | Lam of term | App of term * term;;\n"; 65 | prelude target; 66 | output_string target "let t0 = Sys.time ();;\n"; 67 | compile target tm; 68 | begin match expected with 69 | | Some expected when check -> 70 | output_string target "let expected = "; 71 | output_term target expected; 72 | output_string target ";;\n"; 73 | output_string target "if Hashtbl.hash nf <> Hashtbl.hash expected"; 74 | output_string target " then (print_endline \"wrong answer\"; exit 1);;\n"; 75 | | _ -> 76 | () 77 | end; 78 | output_string target "let t1 = Sys.time ();;\n"; 79 | output_string target "print_float (t1 -. t0);;"; 80 | close_out target; 81 | let t1 = Sys.time () in 82 | ignore @@ Sys.command @@ String.concat " " [ 83 | "./compile.sh"; mode; 84 | test_target; string_of_float (t1 -. t0) 85 | ]; 86 | (* Printf.printf " (gen=%.6f)\n" (t1 -. t0) *) 87 | in 88 | ( { run = run "compile" }, { run = run "normalize" } ) 89 | -------------------------------------------------------------------------------- /Normalizers/Normalizers.ml: -------------------------------------------------------------------------------- 1 | 2 | let normalizers = [ 3 | "subst.naive" , Subst.normalizer_naive; 4 | "subst.whead" , Subst.normalizer_whead; 5 | "NBE.HOAS.list" , NBE_HOAS.normalizer_list; 6 | "NBE.HOAS.tree" , NBE_HOAS.normalizer_tree; 7 | "NBE.HOAS.skew" , NBE_HOAS.normalizer_skew; 8 | "NBE.closure.list", NBE_Closure.normalizer_list; 9 | "NBE.closure.tree", NBE_Closure.normalizer_tree; 10 | "NBE.lazy" , NBE_Lazy.normalizer; 11 | "NBE.pushenter" , NBE_Pushenter.normalizer; 12 | "NBE.named.list" , NBE_Named.normalizer_list; 13 | "NBE.named.tree" , NBE_Named.normalizer_tree; 14 | "NBE.named.ADT" , NBE_Named.normalizer_adt; 15 | "NBE.memo.v1" , NBE_Memo.normalizer_v1; 16 | "NBE.memo.v2" , NBE_Memo.normalizer_v2; 17 | "NBE.memo.v3" , NBE_Memo.normalizer_v3; 18 | "NBE.memo.v4" , NBE_Memo.normalizer_v4; 19 | "NBE.memo.named" , NBE_Memo.normalizer_named; 20 | "NBE.HC.idx" , NBE_HashConsing.normalizer_cache_index; 21 | "NBE.HC.idx.o" , NBE_HashConsing.normalizer_cache_index_o; 22 | "NBE.HC.lvlidx" , NBE_HashConsing.normalizer_cache_level_index; 23 | "NBE.HC.lvlidx.o" , NBE_HashConsing.normalizer_cache_level_index_o; 24 | "NBE.HC.hashcons" , NBE_HashConsing.normalizer_hashcons; 25 | "AM.Crégut.list" , AbstractMachine.normalizer_list; 26 | "AM.Crégut.ADT" , AbstractMachine.normalizer_adt; 27 | "AM.Crégut.arr" , AbstractMachine.normalizer_arr; 28 | "AM.Crégut.CBV" , AbstractMachine.normalizer_cbv; 29 | "compiled.HOAS.byte.N" , Compiled_HOAS.byte_normalize; 30 | "compiled.HOAS.native.N", Compiled_HOAS.native_normalize; 31 | "compiled.HOAS.O2.N" , Compiled_HOAS.o2_normalize; 32 | "compiled.HOAS.byte.C" , Compiled_HOAS.byte_compile; 33 | "compiled.HOAS.native.C", Compiled_HOAS.native_compile; 34 | "compiled.HOAS.O2.C" , Compiled_HOAS.o2_compile; 35 | "compiled.evalapply.byte.N" , Compiled_Evalapply.byte_normalize; 36 | "compiled.evalapply.native.N", Compiled_Evalapply.native_normalize; 37 | "compiled.evalapply.O2.N" , Compiled_Evalapply.o2_normalize; 38 | "compiled.evalapply.byte.C" , Compiled_Evalapply.byte_compile; 39 | "compiled.evalapply.native.C", Compiled_Evalapply.native_compile; 40 | "compiled.evalapply.O2.C" , Compiled_Evalapply.o2_compile; 41 | ] 42 | -------------------------------------------------------------------------------- /Normalizers/Subst.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | 4 | let rec shift (base, dist) tm = 5 | match tm with 6 | | Idx idx -> 7 | if idx < base 8 | then Idx idx 9 | else Idx(idx + dist) 10 | | Lam tm' -> 11 | Lam(shift (base + 1, dist) tm') 12 | | App(tf, ta) -> 13 | App(shift (base, dist) tf, shift (base, dist) ta) 14 | 15 | let rec subst (level, target) tm = 16 | match tm with 17 | | Idx idx -> 18 | begin match Int.compare idx level with 19 | | -1 -> Idx idx 20 | | 0 -> shift (0, level) target 21 | | _ -> Idx(idx - 1) 22 | end 23 | | Lam tm' -> 24 | Lam(subst (level + 1, target) tm') 25 | | App(tf, ta) -> 26 | App(subst (level, target) tf, subst (level, target) ta) 27 | 28 | 29 | let rec normalize_naive tm = 30 | match tm with 31 | | Idx idx -> Idx idx 32 | | Lam tm' -> Lam(normalize_naive tm') 33 | | App(tf, ta) -> 34 | match normalize_naive tf with 35 | | Lam tm' -> normalize_naive (subst (0, normalize_naive ta) tm') 36 | | tf' -> App(tf', normalize_naive ta) 37 | 38 | let rec normalize_head tm = 39 | match tm with 40 | | Idx _ | Lam _ -> 41 | tm 42 | | App(f, a) -> 43 | match normalize_head f with 44 | | Lam tm' -> normalize_head (subst (0, normalize_head a) tm') 45 | | f' -> App(f', normalize a) 46 | 47 | and normalize tm = 48 | match tm with 49 | | Idx idx -> Idx idx 50 | | Lam tm' -> Lam(normalize tm') 51 | | App(f, a) -> 52 | match normalize_head f with 53 | | Idx idx -> App(Idx idx, normalize a) 54 | | Lam tm' -> normalize (subst (0, normalize_head a) tm') 55 | | f' -> App(f', normalize a) 56 | 57 | let normalizer_naive = Normalizer.simple_normalizer normalize_naive 58 | let normalizer_whead = Normalizer.simple_normalizer normalize 59 | -------------------------------------------------------------------------------- /Normalizers/dune: -------------------------------------------------------------------------------- 1 | 2 | (library 3 | (wrapped false) 4 | (name normalizers) 5 | (libraries common)) 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Benchmarking Normalization Algorithms 2 | 3 | ## Table of Contents 4 | 5 | 1. [Test Framework](#Framework) 6 | 1. [Normalizers](#Normalizers) 7 | 1. [Benchmarks](#Benchmarks) 8 | 1. [Results](#Results) 9 | 1. [Personal Conclusion](#Conclusion) 10 | 1. [References](#References) 11 | 12 | ## Framework 13 | 14 | Lambda terms are represented using de Bruijn index. 15 | Time used to normalize a term is measured. 16 | Some algorithms may use an alternative term representation. 17 | In this case, the time used to convert between different term representations 18 | is not counted. 19 | Note that intermediate data structure like values in NBE 20 | does not count as an alternative term representation 21 | because they cannot be examined. 22 | 23 | In the future, maybe the time needed to test conversion directly 24 | on the algorithm's internal rep. may be added. 25 | 26 | The definition of syntax and common data structures lies in `Common`. 27 | 28 | The `bench.ml` executable can be used to run the various test benches. 29 | You should provide it with the name of normalizer, 30 | the name of benchmark and size parameter to the benchmark. 31 | 32 | To exclude the effect of previous runs on GC, 33 | every test run should be executed with a fresh process. 34 | This is done through the `bench.sh` script. 35 | It also handles timeout of benchmarks. 36 | 37 | Since there are a lot of normalizer variants, 38 | and different normalizers may have very diversed performance on different benchmarks, 39 | not all normalizers are tested and compared together. 40 | Instead, normalizers are tested in various different combinations, 41 | each comparing one aspect of different normalizers. 42 | The `NBE.closure.list` normalizer is used as a reference baseline in 43 | all combinations. 44 | Also, different combinations are tested using exactly the same terms 45 | in the same environment, except for the `random` benchmark, 46 | where the fresh random terms are generated for each combination. 47 | 48 | ## Normalizers 49 | 50 | Various normalization algorithms sit in `Normalizers`. 51 | You can find more detailed explanation in the [Conclusion](#Conclusion). 52 | 53 | - `subst.naive` (in `Subst.ml`): 54 | naive normal-order capture-avoiding substitution 55 | - `subst.whead` (in `Subst.ml`): 56 | capture-avoiding substitution, but reduce redex only to weak head first 57 | before substitution. 58 | - `NBE.HOAS.X` (in `NBE_HOAS.ml`): 59 | Normalization By Evaluation (NBE), using HOAS to represent closures. 60 | `X` is the data structure used to implement the environment, 61 | which includes: 62 | - `list`: plain list 63 | - `tree`: `Map` in OCaml's standard library (AVL tree) 64 | - `skew`: skew binary random access list from Chris Okasaki's 65 | Purely Functional Data structure [[9]](#9) 66 | - `NBE.closure.list|tree` (in `NBE_Closure.ml`): 67 | NBE, using raw lambda terms to represent closures. 68 | Come with two flavors of environment data structure too 69 | - `NBE.named.list|tree|ADT` (in `NBE_Named.ml`): 70 | NBE, but using a named term representation instead of de Brujin index. 71 | Uses a global counter for fresh variable generation. 72 | Environment is represented as association list, 73 | OCaml's AVL tree (`Map`), 74 | or a specialized ADT with the pairs in association list inlined. 75 | - `NBE.memo.v1|v2|v3|v4` (in `NBE_Memo.ml`) 76 | Same as `NBE.closure.list`, 77 | but each value memorizes the term it quotes back to (at some level). 78 | There's only one memorization slot, to reduce constant overhead. 79 | `v1`, `v2` and `v3` uses three different ways to store the extra memorization slot. 80 | `v1` uses a fat pointer storing a mutable `(level * term) option`, 81 | `v2` also uses fat pointer, but uses two separate mutable slots 82 | for the level and the term respectively (the term slot holds garbage initiallly). 83 | `v3` stores the memorization slot inside the block for each case of the value ADT, 84 | and hence has only one layer of indirection. 85 | `v4` is the same as `v3`, but do not cache leaf nodes (variables). 86 | Turns out that this dirty memory layout optimizations have a significnat effect. 87 | - `NBE.memo.named` (in `NBE_Memo.ml`) 88 | Same as `NBE.memo.v3`, but uses a named term representation. 89 | Don't have cache-miss issue. 90 | - `NBE.pushenter` (in `NBE_Pushenter.ml`) 91 | NBE with a push/enter style uncurrying. 92 | A separate argument stack is maintained, 93 | and closures are only allocated when the argument stack is empty. 94 | (In interpreted normalizers closure allocation is cheap, 95 | so this one is not added to the result. 96 | The `compiled.evalapply` below is perhaps more interesting) 97 | - `NBE.lazy` (in `NBE_Lazy.ml`): 98 | a variant of `NBE.HOAS.list` with lazy evaluation everywhere. 99 | - `AM.Crégut.X` (in `AbstractMachine.ml`): 100 | The strongly reducing krivine abstract machine of Pierre Crégut. 101 | I found it in [[1]](#1), 102 | and the original paper is [[2]](#2). 103 | The variants are: 104 | 105 | - `list`: use lists as stack 106 | - `ADT`: inline the definition of each stack frame into the definition list 107 | - `arr`: use a large array as stack with initial size `size` 108 | - `CBV`: I try to implement a CBV version of the machine here 109 | (the original one is CBN). Use inlined ADT as stack. 110 | 111 | - `compiled.HOAS.byte|native|O2.N|C` (in `Compiled_HOAS.ml`): 112 | compile the given term to a OCaml program 113 | that performs normalization directly by tagged HOAS. 114 | The generated OCaml program can be compiled in bytecode, native, 115 | or optimized native mode. 116 | I got the idea from [[10]](#10) and [[11]](#11). 117 | `N` is for normalization time and `C` is for compilation time 118 | (OCaml source code generation + compilation of generated code) 119 | - `compiled.evalapply.byte|native|O2.N|C` (in `Compiled_Evalapply.ml`): 120 | compile the given term to a OCaml program 121 | that performs normalization directly by tagged HOAS, 122 | with eval/apply style n-ary function optimization for functions with 1~5 params. 123 | The generated OCaml program can be compiled in bytecode, native, 124 | or optimized native mode. 125 | I got the idea from [[12]](#12). 126 | 127 | - `NBE.HC.X`: basic NBE normalizers with hash-consing or other sharing techniques. 128 | The variants are: 129 | - `NBE.HC.idx.o`: cache de Brujin index terms in the output term, 130 | via pre-allocating an array of all DBI nodes of size smaller than 10000, 131 | and reuse these pre-allocated terms. 132 | - `NBE.HC.idx`: similar to `NBE.HC.idx.o`, but input terms are cached as well. 133 | - `NBE.HC.lvlidx.o`: cache de Brujin index terms in the output term 134 | as well as de Brujin levels in values, also via pre-allocating an array of all DBI/DBL nodes. 135 | - `NBE.HC.lvlidx`: similar to `NBE.HC.lvl.o`, but input terms are cached as well. 136 | - `NBE.HC.hashcons`: complete hash-consing term representation. 137 | Structurally equal terms are always equal physically. 138 | The implementation strategy comes from [[14]](#14), 139 | but I use OCaml's builtin hash table, so the implementation may be slower than [[14]](#14). 140 | 141 | - (TODO) some bytecode based approaches. 142 | For example the modified ZAM used in Coq [[3]](#3) 143 | 144 | ### Normalizers I know but don't plan to implement 145 | 146 | - fully lazy, in-place, graph reduction, found in [[4]](#4). 147 | The algorithm is quite complex, and NBE doesn't have the 148 | "search for variable to substitute" inefficiency. 149 | Also, this algorithm is rewriting based and I doubt the efficiency 150 | of such approaches. 151 | Still, it is worth being implemented and tested. 152 | But I am currently not very passionate on this. 153 | - the suspension lambda calculus in [[5]](#5). 154 | For pretty much the same reason. 155 | The calculus is complex, and I am in doubt with rewriting based approaches. 156 | - The Fireball calculus and different variants of GLAMOUr abstract machines in [[14]](#14). 157 | The theoretic complexity study seem interesting, 158 | The abstract machines require a 159 | "generate alpha-equivalent, capture avoiding term" operation, 160 | and I am not sure how this should be implemented. 161 | Also, to achieve the complexity bound in the paper requires using 162 | graph/explicit substitution based term representation altogether 163 | (otherwise quoting may result in an exponential blow up), 164 | but implementing the whole story together with conversion check seems complex. 165 | I wonder if the `NBE.memo` normalizers is related to some favor of the 166 | GLAMOUr machines. 167 | 168 | ## Benchmarks 169 | 170 | - `church_add`: adding two church numerals. 171 | - `church_mul`: multiplying two church numerals 172 | - `exponential`: an artificial benchmark of terms whose normal forms' 173 | sizes grow exponentially and have a lot of sharing. 174 | Let `t(0) = x`, `t(n+1) = (\y. y y) t(n)`, 175 | this benchmark normalizes `\x. t(n)`. 176 | Let `r(0) = x`, `r(n+1) = r(n) r(n)`, 177 | `\x. t(n)` will normalize to `\x. r(n)`. 178 | - `parigot_add`: adding two numerals in parigot encoding [[7]](#7). 179 | I read about parigot encoding in [[8]](#8). 180 | This is perhaps a practical example of terms of exponentially growing size. 181 | - `random`: randomly generated (uniformly distributed) lambda terms. 182 | The generation algorithm comes from [[6]](#6). 183 | First, a table of the total number of lambda terms of different sizes 184 | can be generated using `count_terms.ml` (`dune exec ./count_terms.exe`) 185 | (WARNING: very very costly even for modest sizes (< 10000)). 186 | 187 | Then `gen_random_terms.ml` (`dune exec ./gen_random_terms.ml`) 188 | can be used to randomly generate uniformly distributed lambda terms 189 | of different sizes and number of free variables 190 | 191 | Due to the high cost of generating large term count table, 192 | 50 terms are generated for each size to make the result term large enough. 193 | - `self_interp_size`: encode lambda terms using lambda terms with 194 | scott encoding, then calculate the size (in church numeral) of terms 195 | by structural recursion 196 | - (TODO) operations on other inductive types 197 | - (TODO) self interpreter of lambda calculus 198 | - (TODO) maybe make some type-erasured programs from existing code bases? 199 | 200 | 201 | ## Results 202 | The experimental data can be found in `data//.dat` 203 | (all time measured in seconds). 204 | Here I present the result with gnuplot to give a more straightforward comparison. 205 | There is a 20 second timeout on every run. 206 | Results of runs that exceed this timeout are considered missing. 207 | 208 | ### subst v.s. NBE 209 | The first combination compares Capture Avoiding Substitution (CAS) 210 | with `NBE.closure.list`. 211 | CAS normalizers come in two different normalization orders: 212 | `subst.naive` is "strongly" CBV in that it always normalize functions 213 | and their arguments to normal form before emitting a beta reduction. 214 | `subst.whead` instead only normalizes sub terms to weak head NF before 215 | emitting a beta reduction. 216 | 217 | ![](data/subst-NBE/church_add.png) 218 | ![](data/subst-NBE/church_mul.png) 219 | ![](data/subst-NBE/exponential.png) 220 | ![](data/subst-NBE/parigot_add.png) 221 | ![](data/subst-NBE/random.png) 222 | ![](data/subst-NBE/self_interp_size.png) 223 | 224 | 225 | ### NBE variants 226 | Different variants of the untyped NBE algorithm is compared. 227 | Note that since the call-by-value abstract machine `AM.Crégut.CBV` 228 | has essentially the same runtime behavior as untyped NBE 229 | (I conjecture so, this can perhaps be proved via some distillation), 230 | it is tested here too. 231 | 232 | ![](data/NBE-variants/church_add.png) 233 | ![](data/NBE-variants/church_mul.png) 234 | ![](data/NBE-variants/exponential.png) 235 | ![](data/NBE-variants/parigot_add.png) 236 | ![](data/NBE-variants/random.png) 237 | ![](data/NBE-variants/self_interp_size.png) 238 | 239 | 240 | ### DBI v.s. named 241 | `NBE.closure.list` is compared against two normalizers 242 | using term with named variable instead of de Brujin index. 243 | These two normalizers use association list and AVL tree to represent 244 | environment in respect: 245 | 246 | ![](data/DBI-named/church_add.png) 247 | ![](data/DBI-named/church_mul.png) 248 | ![](data/DBI-named/exponential.png) 249 | ![](data/DBI-named/parigot_add.png) 250 | ![](data/DBI-named/random.png) 251 | ![](data/DBI-named/self_interp_size.png) 252 | 253 | 254 | ## memorized NBE 255 | Three variants of NBE with memorization for the quoted term is tested, 256 | compared against `NBE.closure.list`. 257 | See the conclusion part for more details on this three variants. 258 | 259 | ![](data/NBE-memo/church_add.png) 260 | ![](data/NBE-memo/church_mul.png) 261 | ![](data/NBE-memo/exponential.png) 262 | ![](data/NBE-memo/parigot_add.png) 263 | ![](data/NBE-memo/random.png) 264 | ![](data/NBE-memo/self_interp_size.png) 265 | 266 | 267 | ## hash-consing term representation 268 | Variants of `NBE.closure.list` with different degrees of structure-sharing is tested here. 269 | No memorization is done via the sharing, 270 | so the main benefit of these sharing is reducing memory requirement and allocation. 271 | 272 | ![](data/NBE-hashcons/church_add.png) 273 | ![](data/NBE-hashcons/church_mul.png) 274 | ![](data/NBE-hashcons/exponential.png) 275 | ![](data/NBE-hashcons/parigot_add.png) 276 | ![](data/NBE-hashcons/random.png) 277 | ![](data/NBE-hashcons/self_interp_size.png) 278 | 279 | 280 | ### abstract machine variants 281 | Three variants of the CBN strongly reducin Krivine machine is tested. 282 | The difference lies in the use of different data structures 283 | to represent the control stack. 284 | 285 | ![](data/AM-variants/church_add.png) 286 | ![](data/AM-variants/church_mul.png) 287 | ![](data/AM-variants/exponential.png) 288 | ![](data/AM-variants/parigot_add.png) 289 | ![](data/AM-variants/random.png) 290 | 291 | 292 | 293 | ## compiled NBE 294 | Variants of untyped NBE implemented via compilation to OCaml is tested here. 295 | UTLC terms are compiled to OCaml terms using some tagged HOAS. 296 | In essence, the OCaml compiler is reused to derive a bytecode/native normalizer. 297 | Since compilation time is very long, 298 | benchmarks with large initial term sizes are not tested here. 299 | 300 | The time for normalization is as follows 301 | (note that the 20 second timeout applies to normalization time + compilation time, 302 | so some lines may cut off early): 303 | 304 | ![](data/compiled-normalize/church_mul.png) 305 | ![](data/compiled-normalize/exponential.png) 306 | ![](data/compiled-normalize/parigot_add.png) 307 | ![](data/compiled-normalize/random.png) 308 | 309 | The time for compilation to OCaml (generate source code + compile source code) 310 | is as follows: 311 | 312 | ![](data/compiled-compile/church_mul.png) 313 | ![](data/compiled-compile/exponential.png) 314 | ![](data/compiled-compile/parigot_add.png) 315 | ![](data/compiled-compile/random.png) 316 | 317 | 318 | 319 | ## Analysis and Conclusion 320 | 321 | ### The imprecision of the results here 322 | Before proceeding with drawing my personal conclusion, 323 | it must be mentioned first that the result here suffers from various 324 | imprecision, and some may has a significnat effect. 325 | 326 | First of all, the effect of the host system is not isolated. 327 | The results above is tested on a laptop with GUI, etc. running. 328 | All the benchmarks use only one core, 329 | and the system load is kept low while benchmarking. 330 | Nevertheless, the status of the system may still has an effect on the result. 331 | 332 | Second, the effect of OCaml GC is not measured or tuned. 333 | This is perhaps the most vital problem of all the results here. 334 | To understand the reason behind the performance differences 335 | of different normalizers on different benchmarks, 336 | The amount of time spent on GC should really be measured. 337 | 338 | Third, as will be mentioned in detail later, 339 | the benchmarks here cannot represent the realworld workload 340 | of a normalization algorithm in type system implementations. 341 | For example, all benchmarks here are standalone lambda terms, 342 | while in practice calling defined functions is much, much more common. 343 | 344 | Given all these imprecisions, 345 | the result and conclusion here is **not solid at all**. 346 | However, I think some of the results here, 347 | in particular the very significant differences in performances, 348 | are to some extent trustworthy. 349 | Also, while imprecise, 350 | I think the result here may shed some light on further investigations, 351 | where more precise, more detailed experiments 352 | on a smaller number of normalizers are carried out. 353 | 354 | ### subst v.s. NBE 355 | Unsurprisingly, NBE outperforms CAS by orders of magnitude. 356 | IMO the reason behind is that CAS creates too many intermediate terms 357 | during substitution, 358 | while NBE avoid these unnecessary allocations altogether 359 | by passing an environment, 360 | deferring and merging substitutions. 361 | 362 | 363 | ### NBE variants 364 | The various different NBE variants have very similar performance: 365 | the difference is much smaller than that between NBE and CAS. 366 | However, some small but stable difference can still be observed. 367 | 368 | Let's first focus on the representation of environment. 369 | Comparing `NBE.HOAS.list|tree|skew`, 370 | one find that, surprisingly, `list` is always the most performant. 371 | For `church_add|mul` and `exponetial`, 372 | the enviroment is always very shallow, so this result makes sense. 373 | But the difference persists in `parigot_add`, `random` and `self_interp_size`. 374 | My interpretation of this result is: 375 | 376 | - adding new entries to the environment is a very common operation. 377 | So the `log(N)` cost of `tree`, or even the constant overhead of `skew` 378 | has a significant effect. 379 | - environment lookup is also a very common operation, 380 | but for shallow environments, 381 | or under the conjecture that "recently defined bound variables are accessed most frequently", 382 | DBI may need less steps on average to lookup bound variables. 383 | (which IMO is also the most common case in practice, 384 | if top-level defined variables are stored separatedly), 385 | the `O(N)` cost of `list` is not that terrible, 386 | and the constant overhead of `tree` and `skew` is not negligible. 387 | 388 | Next, let's focus on `HOAS` v.s. `closure`. 389 | The difference is small and alternating, 390 | The runtime behavior of using HOAS and using closure is actually almost identical. 391 | If any difference exists at all it would probably lie in 392 | the pervasive existence of indirect function calls in `HOAS`. 393 | I think it is fine to consider the two equally performant in practice. 394 | 395 | Next, let's look at the lazy varinat `NBE.lazy`. 396 | In the benchmark here it is outperformed by its strict companions. 397 | Of course, this may be due to the characteristic of the benchmarks here. 398 | And a runtime system with better support for laziness, such as GHC, 399 | may make a difference too. 400 | 401 | Finally, `AM.Crégut.CBV`, 402 | while conjectured having the same runtime behavior as untyped NBE, 403 | has a stable constant overhead. 404 | IMO this overhead can be seen as an instance of 405 | stack-allocated activation record (untyped NBE) 406 | v.s. heap-allocated activation record (abstract machine), 407 | as the abstract machine is always tail recursive, 408 | and evaluation context is heap allocated using ADT. 409 | 410 | A bytecode version of the abstract machine would be very interesting. 411 | But this strongly reducing machine seems more difficult to "bytecode-ize" 412 | than weak machines 413 | (for example, Coq's bytecode VM is a weak reduction machine 414 | with additional support for open terms). 415 | I think investigating in this direction would be very interesting. 416 | 417 | ### DBI v.s. named 418 | From the results, 419 | de Brujin index is slightly faster than named terms. 420 | This is probably due to caused by faster environment lookup. 421 | The association list in `NBE.named.list` 422 | and the list in `NBE.closure.list` should always have the same order. 423 | So the steps needed to access each variable should be identical. 424 | However, `List.nth` is faster than `List.assoc` in terms of constant overhead. 425 | 426 | When using named term representation, 427 | linked list is still more performant than balanced trees. 428 | This is probably due to smaller constant overhead 429 | and better complexity on insertion. 430 | Again, the length of the environment of bound variables is small in the benchmarks here. 431 | So the setting of the benchmarks may be unfair to balanced trees. 432 | But in practice, the number of bound variables is usually small, too. 433 | 434 | Inlining pairs in association list should in theory reduces 435 | some indirectons when looking up variables. 436 | And the results shows that it indeed brings some slight boost. 437 | This is in agreement with the results of abstract machine variants below. 438 | 439 | 440 | ### memorized NBE 441 | Three variants of untyped NBE with memorization is tested here, 442 | compared against `NBE.closure.list`. 443 | The evaluation part of the NBE algorithms is unchanged. 444 | However, each value additionally memorizes the term it quotes back to. 445 | So during the quoting phase of NBE, 446 | some quotations may be avoided by memorization. 447 | 448 | Since terms are represented using DBI, 449 | the same value quotes to different terms under different levels. 450 | So the cached term will hit only when its level is equal to the requested 451 | level. 452 | To avoid overly large space and time overhead, 453 | here each value only holds one memorization slot. 454 | When a cache miss happens (i.e. requested level different from cached level), 455 | the actual quoting algorithm must be run, 456 | and the result together with the new value is memorized. 457 | 458 | This memorization will work, largely because the evaluation phase 459 | already performs a lot of sharing. 460 | Consider the term `(\x. x x) M`, it will reduces to `M M` if `M` is neutral. 461 | However, the two `M`s are actually shared, 462 | since the evaluation phase does not copy values when extracting them from the environment. 463 | So the memory representation of values in NBE is already a acyclic graph with sharing, 464 | and the memorization simply make use of this sharing to avoid repeated computations. 465 | 466 | The benchmarks in this combination can be divided into two parts: 467 | `exponential` and `parigot_add` have a lot of term sharing in their results. 468 | The size of the result would be exponential if no sharing is performed. 469 | Unsurprisingly, the algorithms with memorizations is orders of magnitude faster 470 | in these two benchmarks. 471 | 472 | The rest of the benchmarks don't have such pervasive sharing, 473 | and can be seen as a test of the overhead of memorization. 474 | Before inspecting the result, 475 | let's first discuss the difference between `v1`, `v2`, `v3` and `v4` of the memorized algorithms. 476 | The three differs only in the representation of values: 477 | 478 | (* v1 *) 479 | type value1 = 480 | { rep : value1_rep 481 | ; mutable syn : (int * term) option } 482 | 483 | and value1_rep = 484 | | VLvl of int 485 | | VLam of (value1 list * term) 486 | | VApp of value1 * value1 487 | 488 | (* v2 *) 489 | (* When there's no cache available, 490 | [lvl = -1] and [syn] holds some garbage value *) 491 | type value2 = 492 | { rep : value2_rep 493 | ; mutable lvl : int 494 | ; mutable syn : term } 495 | 496 | and value2_rep = 497 | | VLvl of int 498 | | VLam of (value2 list * term) 499 | | VApp of value2 * value2 500 | 501 | (* v3 *) 502 | (* When there's no cache available, 503 | [lvl = -1] and [syn] holds some garbage value *) 504 | type value3 = 505 | | VLvl of 506 | { mutable lvl : int 507 | ; mutable syn : term 508 | ; value : int } 509 | | VLam of 510 | { mutable lvl : int 511 | ; mutable syn : term 512 | ; env : value3 list 513 | ; body : term } 514 | | VApp of 515 | { mutable lvl : int 516 | ; mutable syn : term 517 | ; func : value3 518 | ; arg : value3 } 519 | 520 | (* v4 *) 521 | (* When there's no cache available, 522 | [lvl = -1] and [syn] holds some garbage value *) 523 | type value4 = 524 | | VLvl of int (* leaf nodes not cached *) 525 | | VLam of 526 | { mutable lvl : int 527 | ; mutable syn : term 528 | ; env : value4 list 529 | ; body : term } 530 | | VApp of 531 | { mutable lvl : int 532 | ; mutable syn : term 533 | ; func : value4 534 | ; arg : value4 } 535 | 536 | 537 | The three are equivalent in terms of functionality. 538 | However, in OCaml, all records and ADT with extra data is stored as blocks, 539 | which means a level of indirection. 540 | So in `v1`, the access pattern on values is: 541 | 542 | 1. fetch the record `value`, one indirection 543 | 1. inspect the cache stored in `option`, one more indirection 544 | 1. on cache miss, inspect `value1_rep`, one more indirection 545 | 546 | In `v2`, the access pattern is: 547 | 548 | 1. fetch the record `value2`, one indirection 549 | 1. inspect the cache, no indirection since it is stored in `value2` directly 550 | 1. on cache miss, inspect `value2_rep`, one indirection 551 | 552 | In `v3` and `v4` (take `v3` as an example), the access pattern is: 553 | 554 | 1. inspect `value3` and fetch attached data, one indirection 555 | 1. inspect the cache, no indirection since it is stored in each branch 556 | of `value3` directly 557 | 1. on cache miss, perform normal quoting, no indirection 558 | since `value3` is already inspected 559 | 560 | So counting the number of indirections when accessing a value on quoting, 561 | the result is `v3 = v4 (1) < v2(1 ~ 2) < v1(2 ~ 3)`. 562 | And this is in exactly agreement with the actual time consumed by these algorithms. 563 | `v3` is always the fastest, `v1` is always the slowest, with `v2` in the middle. 564 | 565 | From the results, `v1` and `v2` has a significant constant overhead 566 | compared to `NBE.closure.list`. 567 | Given that terms with tons of sharing is not very common in practice, 568 | this makes them much less attractive. 569 | However, `v3`, with its optimized data layout, 570 | has a very small constant overhead compared to `NBE.closure.list`. 571 | Now its dramatic speedup in extreme cases become much more attractive. 572 | 573 | Next, let's compare `v3` and `v4`. 574 | `v3` is faster than `v4` in general, 575 | this indicate that caching leaf nodes can actually improve performance, 576 | although it seems to require more work (maintaining the cache) 577 | when processing leaf nodes. 578 | I think `v4` is slower because it requires more allocation: 579 | the leaf nodes are not shared. 580 | The result of normalizers with hash-consing term representation also support this explanation. 581 | 582 | 583 | 584 | ## hash-consing term representation 585 | The complete hash-consing normalizer has a significant overhead, 586 | except in benchmarks with extreme sharing (`exponential` and `parigot-add`). 587 | So it is probably not suitable for general purpose NBE. 588 | 589 | However, the variants with sharing on only leaf nodes (DBI/DBL) are more interesting. 590 | `NBE.HC.idx|idx.o|lvlidx|lvlidx.o` outperform `NBE.closure.list` by a small fraction 591 | in all benchmarks except for `self_interp_size`. 592 | This indicates that sharing leaf nodes is quite a reliable measure to slightly improve 593 | the performance of NBE. 594 | This is probably due to the low (almost none) overhead of sharing leaf nodes. 595 | 596 | Next, the different degrees of leaf node sharing seem to have very close performance. 597 | However, variants with or without sharing on input terms 598 | (`NBE.HC.idx|lvlidx` or `NBE.HC.(idx|lvlidx).o`) have closer performance. 599 | Notice that sometimes *more* sharing makes normalization *slower*, 600 | I conjectured that this is due to locality: 601 | pre-allocated leaf nodes are further away from non-leaf nodes allocated later, 602 | resulting in lower cache-hit rate when accessing leaf and non-leaf nodes alternatingly. 603 | This may also explain why `NBE.closure.list` and `NBE.HC.idx.o` are faster in `self_interp_size`: 604 | when inspecting values, normalizers with shared DBL nodes are slower due to worse locality. 605 | 606 | There is one approach that will dominate all these leaf-sharing approaches though: 607 | the best performance can be delivered if the compiler can natively represent leaf nodes as integers. 608 | However, I am not sure whether this is possible in OCaml. 609 | In other languages where pointers and integers cannot be distinguished at runtime, 610 | this may be even harder. 611 | 612 | 613 | ### abstract machine variants 614 | The three tested abstract machine variants are CBN, 615 | strongly reducing Krivine machine. 616 | Since they are CBN, they are not intended to be used in practice. 617 | However, I think the result here scale to other abstract machines, 618 | such as the strongly reducing CBV machine above. 619 | 620 | Before looking at the performance of the results, 621 | I would like to first explain the details of the three abstract machines. 622 | All three abstract machines are the CBN, strongly reducing Krivine machine. 623 | This abstract machine contains three components, 624 | a code representing current control, 625 | an environment for bound variables that may be captured, 626 | and a global stack holding continuations. 627 | The environment is represented as lists in all three machines. 628 | The difference lies in the representation of the continuation stack. 629 | In `AM.Crégut.list`, it is represented as a list of continuations. 630 | In `AM.Crégut.arr`, it is represented as a large, fixed size array of continuations. 631 | In `AM.Crégut.ADT`, it is represented as a ADT, 632 | with the "tail" part of the continuation stack inlined into every type of continuation. 633 | 634 | Now, the access pattern on the stack during the execution of the abstract machine 635 | is also worth mentioning. 636 | Basically, the machine will look at the first frame in the continuation 637 | stack (or its absence) on every transition, 638 | and decide its behavior accordingly. 639 | 640 | With the above information in mind, 641 | it is now easy to see why `ADT` is the most performant in most cases. 642 | In `list`, each transition requires inspecting whether the list is empty 643 | (one indirection) + inspecting the first frame of the stack when it is present 644 | (one indirection). 645 | In `arr`, each transition requires a bound check plus one indirection 646 | inspecting the first stack frame. 647 | Finally, in `ADT`, all these can be done by inspecting the ADT 648 | (one indirection). 649 | 650 | Given that the above operation must be repeated on every machine transition, 651 | I think the ADT implementation of stack 652 | should be favored for abstract machine based normalizers. 653 | 654 | 655 | ### compiled NBE 656 | Normalizers derived by reusing the OCaml compiler is tested here. 657 | Note that the benchmarks here is really unfair to these normalizers, 658 | because one very important optimization, known function call, 659 | has on effect at all in the benchmarks here. 660 | However, the result can still be seen as a measure of performance 661 | on plain lambda terms of a bytecode or native code compilation normalizer. 662 | 663 | In these normalizers, lambda terms are compiled to OCaml terms 664 | using HOAS, and lambdas are compiled to OCaml functions. 665 | In the `compiled.evalappl.*` normalizers, 666 | instead of currying all functions, 667 | specialized constructors for functions of arity 1 to 5, 668 | as well as specialized application functions of arity 1 to 5, 669 | is defined. 670 | In the multi-ary constructors, native n-ary functions of OCaml is used. 671 | 672 | First, let's look at normalization speed. 673 | `NBE.closure.list` outperforms bytecode normalizers, 674 | This is probably due to the inefficiency of bytecode interpretation, 675 | as `NBE.closure.list` is natively compiled itself, 676 | and compilation to OCaml only save up the "source syntax tree traversing" part of NBE. 677 | 678 | `NBE.closure.list` is competible with native normalizers, 679 | only slightly slower in some benchmarks. 680 | Also, O2 optimization seems irrelevant on the generated OCaml source code. 681 | This is reasonable, as the generated code has very simple structure 682 | and little room for optimization. 683 | The difference of the `evalapply` optimization seems small, 684 | but this is very likely due to the characteristic of the benchmarks here. 685 | The benchmark that will most likely favor `evalapply`, `random`, 686 | fail to give enough data due to overly long compilation time. 687 | 688 | Next, let's look at the compilation speed. 689 | Unsurprisingly, bytecode compilation is much faster than native code compilation. 690 | The O2 switch has no effect on compilation time, 691 | perhaps because there's nothing to optimize during compilation. 692 | Surprisingly, the `evalapply` optimization has a significant effect on compilation speed. 693 | In the `random` benchmark, 694 | which has the most multi-ary application, 695 | this difference is very obvious. 696 | 697 | I would consider the approach of reusing the OCaml compiler, 698 | or any other existing compiler impractical, 699 | due to the long compilation time. 700 | Directly generating OCaml bytecode or typed AST may be better, 701 | as unnecessary parsing and type checking can be avoided, 702 | and would be very interesting to investigate. 703 | However, such approach also makes implementation harder. 704 | 705 | 706 | 707 | ### Personal thoughts on choice of normalizer 708 | Normalization is one of the most important parts of a dependent type checker. 709 | So which normalizer to choose for your next dependently typed language? 710 | 711 | Some type theories, such as the Cubical Type Theory, 712 | don't have known NBE algorithms. 713 | For these theories, CAS is perhaps the only choice. 714 | However, as the results above shows, CAS is ordes of magnitude slower than NBE. 715 | So a NBE algorithm is highly desirable for any implementation 716 | with type checking efficiency under concern. 717 | 718 | In fortunate cases where a NBE algorithm is available, 719 | the result above shows that plain untyped NBE is already blazing fast. 720 | `NBE.closure.list` is among the fastest in almost all benchmarks, 721 | beaten only on some extreme cases (`exponential` and `parigot_add`). 722 | So I think plain untyped NBE is already good enough for most cases, 723 | and there's no need for a very complex algorithm. 724 | 725 | However, in some cases where heavy proof-by-reflection is used, 726 | such as the proof of the four color theorem, untyped NBE may be inadequate. 727 | In this case, more sophiscated compilation scheme, 728 | to bytecode or nativecode, may be more desirable. 729 | However, the overly long compilation time renders these efficient approaches 730 | impractical for most everyday type checking, 731 | where the amount of computation is small but term sizes are large. 732 | 733 | In turns of optimizing untyped NBE, 734 | data layout in memory seems to have a very significant effect. 735 | This is not unexpected, 736 | as most of the time spent in NBE is manipulating values and terms, 737 | and one less indirection means more performance. 738 | This can be observed from the benchmarks of 739 | NBE with different environment representation, 740 | different value layouts of memorized NBE, 741 | and different stack representations of strongly reducing abstract machine. 742 | Also, term representation with sharing can reduce allocation and improve performance too. 743 | However, a full hash-consing implementation's overhead can be high. 744 | Caching only leaf nodes seem to be a good balance. 745 | 746 | In spite of the efficiency of `NBE.closure.list`, 747 | some normalizers do have unique characteristics that may be desirable. 748 | For example `NBE.memo` in `exponential` and `parigot_add`. 749 | Besides, abstract machine based approaches, such as `AM.Crégut.CBV`, 750 | allows the easy integration of a notion of "budget" for evaluation. 751 | This can be useful for non-terminating type theories, 752 | where the type checker can abort after certain number of transitions 753 | instead of looping forever. 754 | 755 | Finally, given that the simplest NBE algorithm is already so fast, 756 | I wonder if the bottle neck of dependent type checking lies somewhere else, 757 | for example in meta solving and unification. 758 | Also, the effect of representation of global definitions is not considered 759 | in this benchmark. 760 | Further investigation on the factors behind the performance of 761 | dependent type checking is definitely a very interesting topic. 762 | 763 | 764 | ## References 765 | 766 | [1] 767 | 768 | 769 | [2] 770 | 771 | 772 | [3] 773 | 774 | 775 | [4] 776 | 777 | 778 | [5] 779 | 780 | 781 | [6] 782 | 783 | 784 | [7] 785 | 786 | 787 | [8] 788 | 789 | 790 | [9] 791 | 792 | 793 | [10] 794 | 795 | 796 | [11] 797 | 798 | 799 | [12] 800 | 801 | 802 | [13] 803 | 804 | 805 | [14] 806 | 807 | -------------------------------------------------------------------------------- /baseline.ml: -------------------------------------------------------------------------------- 1 | 2 | open Syntax 3 | 4 | let rec shift (base, dist) tm = 5 | match tm with 6 | | Idx idx -> 7 | if idx < base 8 | then Idx idx 9 | else Idx(idx + dist) 10 | | Lam tm' -> 11 | Lam(shift (base + 1, dist + 1) tm') 12 | | App(tf, ta) -> 13 | App(shift (base, dist) tf, shift (base, dist) ta) 14 | 15 | let rec subst (level, target) tm = 16 | match tm with 17 | | Idx idx -> 18 | if idx = level 19 | then shift (0, level) target 20 | else Idx idx 21 | | Lam tm' -> 22 | Lam(subst (level + 1, target) tm') 23 | | App(tf, ta) -> 24 | App(subst (level, target) tf, subst (level, target) ta) 25 | 26 | 27 | exception NonTerminating 28 | let normalize size tm = 29 | let max_steps = size * 10 in 30 | let steps = ref 0 in 31 | let rec loop tm = 32 | if !steps < max_steps then 33 | raise NonTerminating; 34 | match tm with 35 | | Idx idx -> Idx idx 36 | | Lam tm' -> Lam(loop tm') 37 | | App(tf, ta) -> 38 | match loop tf with 39 | | Lam tm' -> 40 | incr steps; 41 | subst (0, loop ta) tm' 42 | | tf' -> App(tf', loop ta) 43 | in 44 | loop tm 45 | 46 | 47 | let terminating size tm = 48 | try ignore (normalize size tm); false with 49 | NonTerminating -> true 50 | -------------------------------------------------------------------------------- /bench.ml: -------------------------------------------------------------------------------- 1 | 2 | open Common.Syntax 3 | open Common.Terms 4 | 5 | type property = CBN | NoLargeTerm 6 | 7 | let combinations = [ 8 | ( "subst-NBE" 9 | , [] 10 | , [ "subst.naive"; "subst.whead"; "NBE.closure.list" ] ); 11 | ( "NBE-variants" 12 | , [] 13 | , [ "NBE.HOAS.list"; "NBE.HOAS.tree"; "NBE.HOAS.skew" 14 | ; "NBE.closure.list" 15 | ; "NBE.lazy" 16 | ; "AM.Crégut.CBV" ] ); 17 | ( "DBI-named" 18 | , [] 19 | , [ "NBE.closure.list" 20 | ; "NBE.named.list" 21 | ; "NBE.named.tree" 22 | ; "NBE.named.ADT" ] ); 23 | ( "AM-variants" 24 | , [CBN] 25 | , [ "AM.Crégut.list" 26 | ; "AM.Crégut.ADT" 27 | ; "AM.Crégut.arr" ] ); 28 | ( "NBE-memo" 29 | , [] 30 | , [ "NBE.closure.list" 31 | ; "NBE.memo.v1" 32 | ; "NBE.memo.v2" 33 | ; "NBE.memo.v3" 34 | ; "NBE.memo.v4" 35 | ; "NBE.memo.named" ] ); 36 | ( "compiled-normalize" 37 | , [NoLargeTerm] 38 | , [ "NBE.closure.list" 39 | ; "compiled.HOAS.byte.N" 40 | ; "compiled.HOAS.native.N" 41 | ; "compiled.HOAS.O2.N" 42 | ; "compiled.evalapply.byte.N" 43 | ; "compiled.evalapply.native.N" 44 | ; "compiled.evalapply.O2.N" ] ); 45 | ( "compiled-compile" 46 | , [NoLargeTerm] 47 | , [ "compiled.HOAS.byte.C" 48 | ; "compiled.HOAS.native.C" 49 | ; "compiled.HOAS.O2.C" 50 | ; "compiled.evalapply.byte.C" 51 | ; "compiled.evalapply.native.C" 52 | ; "compiled.evalapply.O2.C" ] ); 53 | ( "NBE-hashcons" 54 | , [] 55 | , [ "NBE.closure.list" 56 | ; "NBE.HC.idx" 57 | ; "NBE.HC.idx.o" 58 | ; "NBE.HC.lvlidx" 59 | ; "NBE.HC.lvlidx.o" 60 | ; "NBE.HC.hashcons" ] ); 61 | ] 62 | 63 | let benches = [ 64 | ( "church_add" 65 | , [NoLargeTerm] 66 | , [10000; 25000; 50000; 75000; 100000; 200000; 300000; 400000] 67 | , fun size -> 68 | ( apply church_add [church size; church size] 69 | , Option.some @@ church (size + size) ) 70 | ); 71 | ( "church_mul" 72 | , [] 73 | , [120; 140; 160; 180; 200; 220; 240; 260; 280; 300; 320] 74 | , fun size -> 75 | ( apply church_mul [church size; church size] 76 | , Option.some @@ church (size * size) ) 77 | ); 78 | ( "parigot_add" 79 | , [] 80 | , [5; 6; 7; 8; 9; 10; 11; 12] 81 | , fun size -> 82 | ( App(App(parigot_add, parigot_shared size), parigot_shared size) 83 | , Option.some @@ parigot (size + size) ) 84 | ); 85 | ( "exponential" 86 | , [] 87 | , [18; 19; 20; 21; 22; 23; 24] 88 | , let rec src size = 89 | if size <= 0 90 | then Idx 0 91 | else App(Lam(App(Idx 0, Idx 0)), src (size - 1)) 92 | in 93 | let rec expected size = 94 | if size <= 0 95 | then Idx 0 96 | else 97 | let tm = expected (size - 1) in 98 | App(tm, tm) 99 | in 100 | fun size -> (Lam(src size), Some(Lam(expected size))) 101 | ); 102 | ( "random" 103 | , [] 104 | , [1000; 2000; 3000; 4000; 5000; 6000; 7000; 8000] 105 | , fun size -> 106 | let file = open_in ("data/randterm" ^ string_of_int size) in 107 | (Common.Terms.combine_terms (List.init 50 (fun _ -> deserialize file)), None) 108 | ); 109 | ( "self_interp_size" 110 | , [CBN; NoLargeTerm] 111 | , [1000; 2000; 3000; 4000; 5000; 6000] 112 | , fun size -> 113 | let tm = church size in 114 | ( App(church_lam_size, encode_term tm) 115 | , Some(church (term_size tm)) ) 116 | ); 117 | ] 118 | 119 | 120 | let _ = 121 | match Sys.argv.(1) with 122 | | "list-combinations" -> 123 | List.iter (fun (name, _, _) -> Printf.printf "%s " name) 124 | combinations; 125 | Printf.printf "\n" 126 | | "list-benches" -> 127 | let combi = Sys.argv.(2) in 128 | let (_, props, _) = 129 | List.find (fun (name, _, _) -> name = combi) combinations 130 | in 131 | benches |> List.iter begin fun (name, exclude, _, _) -> 132 | if List.for_all (fun prop -> not (List.mem prop exclude)) props then 133 | Printf.printf "%s " name 134 | end; 135 | Printf.printf "\n" 136 | | "list-normalizers" -> 137 | let combi = Sys.argv.(2) in 138 | let (_, _, normalizers) = 139 | List.find (fun (name, _, _) -> name = combi) combinations 140 | in 141 | List.iter (Printf.printf "%s ") normalizers; 142 | Printf.printf "\n" 143 | | "list-sizes" -> 144 | let bench = Sys.argv.(2) in 145 | let (_, _, sizes, _) = 146 | List.find (fun (name, _, _, _) -> name = bench) benches 147 | in 148 | List.iter (Printf.printf "%d ") sizes; 149 | Printf.printf "\n" 150 | | _ -> 151 | let normalizer = List.assoc Sys.argv.(1) Normalizers.normalizers in 152 | let bench = Sys.argv.(2) in 153 | let (_, _, _, terms) = 154 | List.find (fun (name, _, _, _) -> name = bench) benches 155 | in 156 | let size = int_of_string Sys.argv.(3) in 157 | let tm, expected = terms size in 158 | normalizer.run tm expected 159 | -------------------------------------------------------------------------------- /bench.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | timeout=20 4 | cmd="dune exec ./bench.exe" 5 | 6 | for combi in $($cmd list-combinations); do 7 | echo "==== combination $combi" 8 | if [ ! -d data/$combi ]; then 9 | mkdir data/$combi 10 | fi 11 | for bench in $($cmd list-benches $combi); do 12 | echo "=== benchmark $bench" 13 | echo "size $($cmd list-normalizers $combi)" >data/$combi/$bench.dat 14 | for size in $($cmd list-sizes $bench); do 15 | if [ "$bench" = "random" ]; then 16 | echo "generating random terms of size $size" 17 | dune exec ./gen_random_terms.exe $size 0 51 data/randterm$size data/term_counts 18 | fi 19 | echo "size $size" 20 | echo -n "$size" >>data/$combi/$bench.dat 21 | for normalizer in $($cmd list-normalizers $combi); do 22 | msg=$(timeout -s KILL $timeout $cmd $normalizer $bench $size 2>&1) 23 | if [ "$?" != "0" ]; then 24 | echo "> $normalizer: failed ($msg)" 25 | echo -n " MISSING" >>data/$combi/$bench.dat 26 | else 27 | echo "> $normalizer: $msg" 28 | echo -n " $msg" >>data/$combi/$bench.dat 29 | fi 30 | sleep 1 31 | done 32 | echo "" >>data/$combi/$bench.dat 33 | done 34 | done 35 | done 36 | -------------------------------------------------------------------------------- /compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mode=$1 4 | target=$2 5 | tgen=$3 6 | t1=0 7 | t2=0 8 | 9 | if [ "$mode" = "byte" ]; then 10 | t1=$(date +%s.%N) 11 | ocamlc _build/tmp.ml -o _build/tmp.out || exit 1 12 | t2=$(date +%s.%N) 13 | elif [ "$mode" = "native" ]; then 14 | t1=$(date +%s.%N) 15 | ocamlopt _build/tmp.ml -o _build/tmp.out || exit 1 16 | t2=$(date +%s.%N) 17 | elif [ "$mode" = "O2" ]; then 18 | t1=$(date +%s.%N) 19 | ocamlopt -O2 _build/tmp.ml -o _build/tmp.out || exit 1 20 | t2=$(date +%s.%N) 21 | fi 22 | if [ "$target" = "normalize" ]; then 23 | _build/tmp.out 24 | elif [ "$target" = "compile" ]; then 25 | echo "Printf.printf \"%.6f\" ($tgen +. $t2 -. $t1)" | ocaml -stdin 26 | else 27 | exit 1 28 | fi 29 | -------------------------------------------------------------------------------- /count_terms.ml: -------------------------------------------------------------------------------- 1 | 2 | 3 | open Common 4 | 5 | let _ = 6 | let max_size = int_of_string Sys.argv.(1) in 7 | let max_env_len = int_of_string Sys.argv.(2) in 8 | let table = Array.make_matrix max_size max_env_len (ApproxNum.of_int 0) in 9 | let count_terms size env_len = 10 | let open ApproxNum in 11 | match size with 12 | | 0 -> of_int 0 13 | | 1 -> of_int env_len 14 | | _ -> 15 | let n_lam = 16 | if env_len + 1 < max_env_len 17 | then table.(size - 1).(env_len + 1) 18 | else of_int 0 19 | in 20 | let n_app = ref (of_int 0) in 21 | for i = 1 to size - 2 do 22 | let n_fun = table.(i ).(env_len) in 23 | let n_arg = table.(size - 1 - i).(env_len) in 24 | n_app := !n_app <+> (n_fun <*> n_arg) 25 | done; 26 | n_lam <+> !n_app 27 | in 28 | 29 | let prog = ref 1 in 30 | let t0 = Sys.time () in 31 | for size = 0 to max_size - 1 do 32 | if size * size * 1000 > max_size * max_size * !prog then begin 33 | let t = Sys.time () in 34 | let eta = Float.to_int (t -. t0) * (1000 - !prog) / !prog in 35 | Printf.printf "counting terms: %03d%%%%, ETA %dh%dm%ds\n%!" 36 | !prog (eta / 3600) (eta mod 3600 / 60) (eta mod 60); 37 | incr prog 38 | end; 39 | for env_len = 0 to max_env_len - 1 do 40 | table.(size).(env_len) <- count_terms size env_len 41 | done 42 | done; 43 | 44 | let target_file = try Sys.argv.(3) with _ -> "data/term_counts" in 45 | let out = open_out_bin target_file in 46 | output_value out table; 47 | close_out out 48 | -------------------------------------------------------------------------------- /data/AM-variants/church_add.dat: -------------------------------------------------------------------------------- 1 | size AM.Crégut.list AM.Crégut.ADT AM.Crégut.arr 2 | 10000 0.002784 0.003090 0.007176 3 | 25000 0.010131 0.006574 0.009978 4 | 50000 0.024180 0.020857 0.024922 5 | 75000 0.050351 0.034290 0.054190 6 | 100000 0.083373 0.059035 0.069536 7 | 200000 0.186799 0.138934 0.152130 8 | 300000 0.250787 0.188326 0.231006 9 | 400000 0.299073 0.234324 0.264143 10 | -------------------------------------------------------------------------------- /data/AM-variants/church_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/AM-variants/church_add.png -------------------------------------------------------------------------------- /data/AM-variants/church_mul.dat: -------------------------------------------------------------------------------- 1 | size AM.Crégut.list AM.Crégut.ADT AM.Crégut.arr 2 | 120 0.001990 0.002296 0.001884 3 | 140 0.002829 0.002899 0.005175 4 | 160 0.003178 0.003902 0.007054 5 | 180 0.005709 0.004553 0.007120 6 | 200 0.006205 0.007122 0.009163 7 | 220 0.009329 0.007631 0.008838 8 | 240 0.012207 0.009563 0.015224 9 | 260 0.018084 0.012344 0.016360 10 | 280 0.015629 0.013410 0.018511 11 | 300 0.022056 0.013681 0.021203 12 | 320 0.024100 0.016430 0.023470 13 | -------------------------------------------------------------------------------- /data/AM-variants/church_mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/AM-variants/church_mul.png -------------------------------------------------------------------------------- /data/AM-variants/exponential.dat: -------------------------------------------------------------------------------- 1 | size AM.Crégut.list AM.Crégut.ADT AM.Crégut.arr 2 | 18 0.040290 0.040836 0.065050 3 | 19 0.080068 0.077637 0.093014 4 | 20 0.144957 0.142691 0.183079 5 | 21 0.293156 0.289319 0.387795 6 | 22 0.590228 0.558846 0.717397 7 | 23 1.171761 1.149250 1.424781 8 | 24 2.409292 2.226789 3.361750 9 | -------------------------------------------------------------------------------- /data/AM-variants/exponential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/AM-variants/exponential.png -------------------------------------------------------------------------------- /data/AM-variants/parigot_add.dat: -------------------------------------------------------------------------------- 1 | size AM.Crégut.list AM.Crégut.ADT AM.Crégut.arr 2 | 5 0.000465 0.000563 0.000705 3 | 6 0.001674 0.002062 0.005844 4 | 7 0.008647 0.006276 0.012560 5 | 8 0.030903 0.031084 0.037500 6 | 9 0.105233 0.108445 0.130686 7 | 10 0.423798 0.423256 0.520745 8 | 11 1.655919 1.684959 2.106128 9 | 12 6.697233 6.829554 9.074057 10 | -------------------------------------------------------------------------------- /data/AM-variants/parigot_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/AM-variants/parigot_add.png -------------------------------------------------------------------------------- /data/AM-variants/random.dat: -------------------------------------------------------------------------------- 1 | size AM.Crégut.list AM.Crégut.ADT AM.Crégut.arr 2 | 1000 0.001613 0.001671 0.000709 3 | 2000 0.000323 0.000320 0.000428 4 | 3000 0.003388 0.003328 0.001041 5 | 4000 0.008205 0.006258 0.002967 6 | 5000 0.003670 0.003777 0.002156 7 | 6000 0.003815 0.003644 0.009215 8 | 7000 0.002336 0.002716 0.001022 9 | 8000 0.006896 0.007118 0.009371 10 | -------------------------------------------------------------------------------- /data/AM-variants/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/AM-variants/random.png -------------------------------------------------------------------------------- /data/DBI-named/church_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.named.list NBE.named.tree NBE.named.ADT 2 | 10000 0.002700 0.002253 0.002482 0.001948 3 | 25000 0.005456 0.006782 0.006236 0.005892 4 | 50000 0.011749 0.015929 0.013004 0.013582 5 | 75000 0.022615 0.023168 0.022266 0.021206 6 | 100000 0.038493 0.044383 0.042470 0.041928 7 | 200000 0.120814 0.127345 0.146628 0.145293 8 | 300000 0.206324 0.201638 0.201806 0.188739 9 | 400000 0.368375 0.244573 0.241284 0.236701 10 | -------------------------------------------------------------------------------- /data/DBI-named/church_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/DBI-named/church_add.png -------------------------------------------------------------------------------- /data/DBI-named/church_mul.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.named.list NBE.named.tree NBE.named.ADT 2 | 120 0.000683 0.001368 0.000758 0.000868 3 | 140 0.001494 0.001559 0.001517 0.001409 4 | 160 0.003361 0.004580 0.004608 0.003323 5 | 180 0.003974 0.004170 0.005048 0.004153 6 | 200 0.003513 0.004777 0.003293 0.004159 7 | 220 0.005464 0.007962 0.006638 0.006543 8 | 240 0.008451 0.009221 0.008761 0.008733 9 | 260 0.007903 0.009187 0.009277 0.007260 10 | 280 0.009792 0.011870 0.011786 0.010520 11 | 300 0.009246 0.011110 0.010380 0.009140 12 | 320 0.010615 0.011820 0.011430 0.011732 13 | -------------------------------------------------------------------------------- /data/DBI-named/church_mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/DBI-named/church_mul.png -------------------------------------------------------------------------------- /data/DBI-named/exponential.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.named.list NBE.named.tree NBE.named.ADT 2 | 18 0.014463 0.016424 0.016568 0.014121 3 | 19 0.041199 0.040825 0.043268 0.059148 4 | 20 0.107139 0.103461 0.105269 0.103830 5 | 21 0.315576 0.311329 0.330499 0.315745 6 | 22 0.545475 0.539623 0.543811 0.557711 7 | 23 0.988068 1.028614 1.028895 1.001571 8 | 24 1.974372 1.902146 1.986669 1.984765 9 | -------------------------------------------------------------------------------- /data/DBI-named/exponential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/DBI-named/exponential.png -------------------------------------------------------------------------------- /data/DBI-named/parigot_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.named.list NBE.named.tree NBE.named.ADT 2 | 5 0.000154 0.000339 0.000569 0.000204 3 | 6 0.000600 0.001393 0.001443 0.000915 4 | 7 0.003675 0.006752 0.007053 0.004160 5 | 8 0.017518 0.028914 0.034036 0.024823 6 | 9 0.128780 0.138996 0.118537 0.119659 7 | 10 0.389060 0.475493 0.434924 0.400389 8 | 11 1.473829 1.871657 1.758474 1.555716 9 | 12 6.032519 8.204001 7.490595 6.175405 10 | -------------------------------------------------------------------------------- /data/DBI-named/parigot_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/DBI-named/parigot_add.png -------------------------------------------------------------------------------- /data/DBI-named/random.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.named.list NBE.named.tree NBE.named.ADT 2 | 1000 0.000319 0.000443 0.001199 0.000209 3 | 2000 0.000363 0.002418 0.004456 0.002103 4 | 3000 0.000877 0.002462 0.004773 0.001202 5 | 4000 0.003736 0.001569 0.010674 0.001544 6 | 5000 0.002476 0.006277 0.011300 0.005415 7 | 6000 0.001729 0.004047 0.010696 0.003228 8 | 7000 0.001428 0.003524 0.016192 0.003012 9 | 8000 0.001426 0.007133 0.018445 0.007191 10 | -------------------------------------------------------------------------------- /data/DBI-named/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/DBI-named/random.png -------------------------------------------------------------------------------- /data/DBI-named/self_interp_size.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.named.list NBE.named.tree NBE.named.ADT 2 | 1000 0.000485 0.002137 0.006361 0.001912 3 | 2000 0.002285 0.003711 0.010729 0.001361 4 | 3000 0.004707 0.007277 0.020500 0.004147 5 | 4000 0.003046 0.007952 0.028459 0.005416 6 | 5000 0.006814 0.012390 0.039515 0.005695 7 | 6000 0.009277 0.013100 0.054775 0.008908 8 | -------------------------------------------------------------------------------- /data/DBI-named/self_interp_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/DBI-named/self_interp_size.png -------------------------------------------------------------------------------- /data/NBE-hashcons/church_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.HC.idx NBE.HC.idx.o NBE.HC.lvlidx NBE.HC.lvlidx.o NBE.HC.hashcons 2 | 10000 0.002406 0.002557 0.000972 0.002509 0.001205 0.002508 3 | 25000 0.005117 0.004587 0.004093 0.005280 0.003597 0.014454 4 | 50000 0.011842 0.008259 0.012605 0.007911 0.011218 0.038358 5 | 75000 0.022166 0.018448 0.017982 0.019839 0.017864 0.067664 6 | 100000 0.036959 0.027199 0.025427 0.027288 0.026962 0.106366 7 | 200000 0.117307 0.080471 0.082332 0.080019 0.081396 0.352315 8 | 300000 0.210575 0.143927 0.175279 0.146255 0.175108 0.436190 9 | 400000 0.368625 0.255959 0.259369 0.253103 0.259753 0.560737 10 | -------------------------------------------------------------------------------- /data/NBE-hashcons/church_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-hashcons/church_add.png -------------------------------------------------------------------------------- /data/NBE-hashcons/church_mul.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.HC.idx NBE.HC.idx.o NBE.HC.lvlidx NBE.HC.lvlidx.o NBE.HC.hashcons 2 | 120 0.000684 0.000742 0.000590 0.000921 0.000878 0.004610 3 | 140 0.000924 0.001312 0.001499 0.000821 0.001326 0.004332 4 | 160 0.005037 0.001499 0.001681 0.001617 0.001397 0.005684 5 | 180 0.004389 0.004595 0.004919 0.004699 0.004198 0.010179 6 | 200 0.003628 0.003204 0.006060 0.004945 0.003965 0.013199 7 | 220 0.005691 0.004780 0.004310 0.003547 0.004392 0.014960 8 | 240 0.007835 0.005610 0.008812 0.006007 0.008433 0.016599 9 | 260 0.007389 0.007481 0.007614 0.007650 0.007321 0.029590 10 | 280 0.010418 0.009626 0.009713 0.008733 0.008569 0.029707 11 | 300 0.008679 0.008776 0.008289 0.008443 0.009066 0.031331 12 | 320 0.011873 0.010343 0.008976 0.010453 0.009973 0.050679 13 | -------------------------------------------------------------------------------- /data/NBE-hashcons/church_mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-hashcons/church_mul.png -------------------------------------------------------------------------------- /data/NBE-hashcons/exponential.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.HC.idx NBE.HC.idx.o NBE.HC.lvlidx NBE.HC.lvlidx.o NBE.HC.hashcons 2 | 18 0.014408 0.009801 0.013777 0.009568 0.012440 0.011482 3 | 19 0.041937 0.025309 0.029118 0.025155 0.029399 0.022611 4 | 20 0.105410 0.054116 0.065607 0.055411 0.055548 0.032999 5 | 21 0.312809 0.177567 0.170693 0.176724 0.177368 0.063662 6 | 22 0.573284 0.489096 0.484172 0.486863 0.482426 0.118316 7 | 23 0.997864 0.828913 0.836297 0.834724 0.820427 0.216205 8 | 24 1.979135 1.507064 1.508713 1.518698 1.577972 0.450705 9 | -------------------------------------------------------------------------------- /data/NBE-hashcons/exponential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-hashcons/exponential.png -------------------------------------------------------------------------------- /data/NBE-hashcons/parigot_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.HC.idx NBE.HC.idx.o NBE.HC.lvlidx NBE.HC.lvlidx.o NBE.HC.hashcons 2 | 5 0.000169 0.000194 0.000158 0.000178 0.000172 0.000368 3 | 6 0.000661 0.000800 0.000867 0.000567 0.000901 0.001908 4 | 7 0.004008 0.003548 0.003473 0.003040 0.003604 0.004282 5 | 8 0.024746 0.016364 0.018024 0.018444 0.015881 0.011424 6 | 9 0.117355 0.105116 0.101561 0.098224 0.095842 0.036186 7 | 10 0.391864 0.324522 0.317839 0.320599 0.326728 0.136783 8 | 11 1.471840 1.223138 1.209195 1.232366 1.243409 0.516051 9 | 12 5.922022 4.992789 4.896628 5.221508 5.184439 2.488705 10 | -------------------------------------------------------------------------------- /data/NBE-hashcons/parigot_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-hashcons/parigot_add.png -------------------------------------------------------------------------------- /data/NBE-hashcons/random.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.HC.idx NBE.HC.idx.o NBE.HC.lvlidx NBE.HC.lvlidx.o NBE.HC.hashcons 2 | 1000 0.000390 0.001202 0.000527 0.000998 0.000443 0.001239 3 | 2000 0.000440 0.001004 0.000429 0.000834 0.000439 0.004319 4 | 3000 0.000368 0.002084 0.000358 0.002062 0.000355 0.003326 5 | 4000 0.003953 0.001167 0.004009 0.001195 0.003664 0.006475 6 | 5000 0.002488 0.003167 0.002380 0.003590 0.002516 0.025550 7 | 6000 0.001935 0.000469 0.002038 0.000495 0.001944 0.010479 8 | 7000 0.001646 0.007068 0.001650 0.007741 0.001476 0.042185 9 | 8000 0.003502 0.004936 0.001722 0.004990 0.001974 0.053941 10 | -------------------------------------------------------------------------------- /data/NBE-hashcons/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-hashcons/random.png -------------------------------------------------------------------------------- /data/NBE-hashcons/self_interp_size.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.HC.idx NBE.HC.idx.o NBE.HC.lvlidx NBE.HC.lvlidx.o NBE.HC.hashcons 2 | 1000 0.000536 0.000609 0.000740 0.000722 0.000550 0.001404 3 | 2000 0.002355 0.001527 0.002754 0.002040 0.002471 0.002798 4 | 3000 0.003082 0.003253 0.003618 0.003110 0.003610 0.005285 5 | 4000 0.002917 0.005200 0.002850 0.005055 0.004082 0.007095 6 | 5000 0.005511 0.006968 0.006177 0.007541 0.006079 0.011183 7 | 6000 0.007481 0.007450 0.008485 0.007409 0.008025 0.012195 8 | -------------------------------------------------------------------------------- /data/NBE-hashcons/self_interp_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-hashcons/self_interp_size.png -------------------------------------------------------------------------------- /data/NBE-memo/church_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.memo.v1 NBE.memo.v2 NBE.memo.v3 NBE.memo.v4 NBE.memo.named 2 | 10000 0.002252 0.004995 0.004249 0.003500 0.003400 0.003033 3 | 25000 0.004765 0.010739 0.009350 0.007551 0.008029 0.007989 4 | 50000 0.012024 0.027117 0.023805 0.020086 0.020642 0.016379 5 | 75000 0.024442 0.051946 0.048537 0.025326 0.034687 0.026980 6 | 100000 0.037363 0.077059 0.074324 0.041858 0.054974 0.046048 7 | 200000 0.118908 0.254501 0.198281 0.135196 0.173445 0.158757 8 | 300000 0.210121 0.466246 0.391846 0.280783 0.328711 0.287106 9 | 400000 0.369242 0.555943 0.515730 0.389976 0.399047 0.283986 10 | -------------------------------------------------------------------------------- /data/NBE-memo/church_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-memo/church_add.png -------------------------------------------------------------------------------- /data/NBE-memo/church_mul.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.memo.v1 NBE.memo.v2 NBE.memo.v3 NBE.memo.v4 NBE.memo.named 2 | 120 0.000651 0.004151 0.001154 0.000960 0.000802 0.001234 3 | 140 0.001235 0.005381 0.003730 0.001489 0.003962 0.001084 4 | 160 0.003268 0.007583 0.003668 0.003771 0.004301 0.006013 5 | 180 0.003961 0.006303 0.005345 0.004883 0.005364 0.005798 6 | 200 0.003564 0.010441 0.007492 0.005143 0.006413 0.004431 7 | 220 0.005896 0.012077 0.012759 0.007269 0.006763 0.005157 8 | 240 0.007743 0.012847 0.011893 0.010526 0.010221 0.008120 9 | 260 0.007647 0.015185 0.015917 0.011423 0.011507 0.011833 10 | 280 0.009780 0.018668 0.015578 0.013316 0.010770 0.011752 11 | 300 0.009200 0.019316 0.016810 0.011820 0.012619 0.011460 12 | 320 0.011385 0.030577 0.028735 0.014641 0.014643 0.014931 13 | -------------------------------------------------------------------------------- /data/NBE-memo/church_mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-memo/church_mul.png -------------------------------------------------------------------------------- /data/NBE-memo/exponential.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.memo.v1 NBE.memo.v2 NBE.memo.v3 NBE.memo.v4 NBE.memo.named 2 | 18 0.014457 0.000005 0.000005 0.000003 0.000004 0.000002 3 | 19 0.044569 0.000004 0.000004 0.000003 0.000003 0.000002 4 | 20 0.109069 0.000005 0.000004 0.000003 0.000004 0.000003 5 | 21 0.313446 0.000005 0.000003 0.000004 0.000004 0.000004 6 | 22 0.554622 0.000003 0.000003 0.000003 0.000004 0.000004 7 | 23 1.003650 0.000005 0.000003 0.000004 0.000003 0.000004 8 | 24 1.934303 0.000005 0.000004 0.000004 0.000004 0.000005 9 | -------------------------------------------------------------------------------- /data/NBE-memo/exponential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-memo/exponential.png -------------------------------------------------------------------------------- /data/NBE-memo/parigot_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.memo.v1 NBE.memo.v2 NBE.memo.v3 NBE.memo.v4 NBE.memo.named 2 | 5 0.000163 0.000195 0.000159 0.000121 0.000121 0.000020 3 | 6 0.000611 0.000747 0.000590 0.000296 0.000467 0.000019 4 | 7 0.003604 0.002803 0.002005 0.001955 0.002074 0.000040 5 | 8 0.018536 0.006643 0.007706 0.004428 0.005044 0.000046 6 | 9 0.132653 0.024341 0.020547 0.020830 0.020981 0.000064 7 | 10 0.390545 0.077649 0.080576 0.083926 0.088308 0.000058 8 | 11 1.475017 0.252692 0.231760 0.222755 0.236811 0.000076 9 | 12 5.975749 0.773689 0.715169 0.695633 0.725081 0.000097 10 | -------------------------------------------------------------------------------- /data/NBE-memo/parigot_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-memo/parigot_add.png -------------------------------------------------------------------------------- /data/NBE-memo/random.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.memo.v1 NBE.memo.v2 NBE.memo.v3 NBE.memo.v4 NBE.memo.named 2 | 1000 0.000480 0.002304 0.002847 0.000718 0.000708 0.001035 3 | 2000 0.000282 0.000437 0.000491 0.000327 0.000350 0.002261 4 | 3000 0.000697 0.003625 0.001660 0.001818 0.001674 0.001905 5 | 4000 0.003779 0.005351 0.005161 0.004434 0.004473 0.001787 6 | 5000 0.002373 0.004214 0.004091 0.003204 0.003318 0.006178 7 | 6000 0.002126 0.004742 0.005120 0.003289 0.003043 0.004230 8 | 7000 0.001606 0.003188 0.003692 0.002601 0.002218 0.003440 9 | 8000 0.001564 0.008166 0.003467 0.002792 0.002799 0.007925 10 | -------------------------------------------------------------------------------- /data/NBE-memo/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-memo/random.png -------------------------------------------------------------------------------- /data/NBE-memo/self_interp_size.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list NBE.memo.v1 NBE.memo.v2 NBE.memo.v3 NBE.memo.v4 NBE.memo.named 2 | 1000 0.000491 0.002398 0.001761 0.000680 0.000842 0.002610 3 | 2000 0.002248 0.003876 0.003783 0.003128 0.003230 0.003342 4 | 3000 0.003188 0.008347 0.006671 0.004578 0.004702 0.008362 5 | 4000 0.002961 0.008103 0.007173 0.004703 0.005959 0.009296 6 | 5000 0.006840 0.010970 0.009174 0.006388 0.007650 0.012846 7 | 6000 0.008022 0.009945 0.015646 0.008761 0.009834 0.013727 8 | -------------------------------------------------------------------------------- /data/NBE-memo/self_interp_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-memo/self_interp_size.png -------------------------------------------------------------------------------- /data/NBE-variants/church_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.HOAS.list NBE.HOAS.tree NBE.HOAS.skew NBE.closure.list NBE.lazy AM.Crégut.CBV 2 | 10000 0.002402 0.003108 0.002718 0.002986 0.002889 0.003714 3 | 25000 0.005405 0.005603 0.006644 0.005518 0.007020 0.010768 4 | 50000 0.012135 0.014981 0.017207 0.013044 0.021225 0.027379 5 | 75000 0.023415 0.025825 0.023515 0.022515 0.035408 0.047347 6 | 100000 0.037053 0.039215 0.046237 0.038472 0.061906 0.085402 7 | 200000 0.116583 0.118402 0.141947 0.117314 0.207707 0.211708 8 | 300000 0.212236 0.215605 0.261841 0.212547 0.310576 0.287073 9 | 400000 0.381708 0.376165 0.376768 0.378278 0.415352 0.373925 10 | -------------------------------------------------------------------------------- /data/NBE-variants/church_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-variants/church_add.png -------------------------------------------------------------------------------- /data/NBE-variants/church_mul.dat: -------------------------------------------------------------------------------- 1 | size NBE.HOAS.list NBE.HOAS.tree NBE.HOAS.skew NBE.closure.list NBE.lazy AM.Crégut.CBV 2 | 120 0.000751 0.001022 0.001171 0.000899 0.002123 0.002055 3 | 140 0.000891 0.001264 0.003327 0.001230 0.002999 0.002267 4 | 160 0.003878 0.004288 0.003467 0.004175 0.003494 0.003690 5 | 180 0.003896 0.004938 0.005486 0.006231 0.005442 0.003984 6 | 200 0.003338 0.004771 0.006117 0.004571 0.006335 0.006086 7 | 220 0.005599 0.006787 0.006604 0.005576 0.009309 0.006084 8 | 240 0.007055 0.008679 0.008112 0.008774 0.009961 0.009601 9 | 260 0.008147 0.008898 0.012860 0.014128 0.020861 0.012142 10 | 280 0.010924 0.009732 0.012241 0.011790 0.021904 0.015355 11 | 300 0.009791 0.011040 0.012326 0.009510 0.016844 0.014379 12 | 320 0.011478 0.014578 0.016226 0.012302 0.018475 0.015826 13 | -------------------------------------------------------------------------------- /data/NBE-variants/church_mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-variants/church_mul.png -------------------------------------------------------------------------------- /data/NBE-variants/exponential.dat: -------------------------------------------------------------------------------- 1 | size NBE.HOAS.list NBE.HOAS.tree NBE.HOAS.skew NBE.closure.list NBE.lazy AM.Crégut.CBV 2 | 18 0.013535 0.015080 0.015017 0.015173 0.015637 0.000005 3 | 19 0.045701 0.043112 0.043536 0.041932 0.049030 0.000006 4 | 20 0.107435 0.106812 0.106272 0.108505 0.105536 0.000006 5 | 21 0.316001 0.321043 0.315697 0.314578 0.324830 0.000006 6 | 22 0.549375 0.542890 0.551305 0.546859 0.550077 0.000006 7 | 23 1.008206 0.994021 1.018227 1.000669 1.014634 0.000005 8 | 24 1.891166 1.939243 1.923854 1.920017 1.983803 0.000007 9 | -------------------------------------------------------------------------------- /data/NBE-variants/exponential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-variants/exponential.png -------------------------------------------------------------------------------- /data/NBE-variants/parigot_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.HOAS.list NBE.HOAS.tree NBE.HOAS.skew NBE.closure.list NBE.lazy AM.Crégut.CBV 2 | 5 0.000171 0.000482 0.000427 0.000203 0.000638 0.000474 3 | 6 0.000984 0.002241 0.002158 0.000782 0.002315 0.001668 4 | 7 0.005590 0.008327 0.006454 0.003915 0.007416 0.008617 5 | 8 0.021020 0.029915 0.032468 0.027842 0.054512 0.033348 6 | 9 0.117784 0.174413 0.116813 0.130590 0.117199 0.102956 7 | 10 0.386149 0.460658 0.401648 0.390370 0.452626 0.500482 8 | 11 1.810448 1.873244 1.709115 1.942720 2.200856 1.696710 9 | 12 6.412949 7.619887 6.619751 6.153519 7.645037 6.842263 10 | -------------------------------------------------------------------------------- /data/NBE-variants/parigot_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-variants/parigot_add.png -------------------------------------------------------------------------------- /data/NBE-variants/random.dat: -------------------------------------------------------------------------------- 1 | size NBE.HOAS.list NBE.HOAS.tree NBE.HOAS.skew NBE.closure.list NBE.lazy AM.Crégut.CBV 2 | 1000 0.000459 0.002344 0.001398 0.000475 0.001151 0.002248 3 | 2000 0.000444 0.003834 0.001169 0.000515 0.001088 0.002711 4 | 3000 0.000757 0.005528 0.002612 0.001072 0.002471 0.004450 5 | 4000 0.003474 0.008273 0.004617 0.003517 0.004860 0.005572 6 | 5000 0.003145 0.008848 0.004426 0.003219 0.003501 0.005108 7 | 6000 0.003077 0.011002 0.003770 0.002401 0.003668 0.005017 8 | 7000 0.002026 0.012480 0.002449 0.001619 0.002153 0.006904 9 | 8000 0.002119 0.011321 0.002606 0.001688 0.003154 0.005913 10 | -------------------------------------------------------------------------------- /data/NBE-variants/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-variants/random.png -------------------------------------------------------------------------------- /data/NBE-variants/self_interp_size.dat: -------------------------------------------------------------------------------- 1 | size NBE.HOAS.list NBE.HOAS.tree NBE.HOAS.skew NBE.closure.list NBE.lazy AM.Crégut.CBV 2 | 1000 0.001640 0.007240 0.002364 0.000609 0.002831 0.002133 3 | 2000 0.002915 0.015473 0.004426 0.002774 0.003916 0.004650 4 | 3000 0.005172 0.025867 0.009197 0.004286 0.007506 0.006622 5 | 4000 0.006331 0.040673 0.011418 0.003625 0.012077 0.010864 6 | 5000 0.008214 0.051519 0.016089 0.006665 0.015693 0.014362 7 | 6000 0.009225 0.070911 0.020224 0.008061 0.017903 0.018738 8 | -------------------------------------------------------------------------------- /data/NBE-variants/self_interp_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/NBE-variants/self_interp_size.png -------------------------------------------------------------------------------- /data/compiled-compile/church_mul.dat: -------------------------------------------------------------------------------- 1 | size compiled.HOAS.byte.C compiled.HOAS.native.C compiled.HOAS.O2.C compiled.evalapply.byte.C compiled.evalapply.native.C compiled.evalapply.O2.C 2 | 120 0.012557 0.136848 0.128393 0.015914 0.125131 0.148599 3 | 140 0.012978 0.148823 0.139513 0.021740 0.132596 0.125497 4 | 160 0.013214 0.143727 0.142116 0.016714 0.125024 0.126485 5 | 180 0.014582 0.154152 0.157771 0.019349 0.132767 0.125487 6 | 200 0.019088 0.165561 0.154267 0.022767 0.132166 0.134238 7 | 220 0.017501 0.172731 0.172788 0.019297 0.136353 0.131156 8 | 240 0.016399 0.170304 0.171973 0.021512 0.139744 0.145610 9 | 260 0.016767 0.185853 0.179365 0.023376 0.146503 0.145096 10 | 280 0.020467 0.193234 0.195605 0.023052 0.145018 0.146896 11 | 300 0.022681 0.195264 0.192440 0.021389 0.151110 0.147846 12 | 320 0.022537 0.202402 0.207821 0.021253 0.155917 0.147061 13 | -------------------------------------------------------------------------------- /data/compiled-compile/church_mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/compiled-compile/church_mul.png -------------------------------------------------------------------------------- /data/compiled-compile/exponential.dat: -------------------------------------------------------------------------------- 1 | size compiled.HOAS.byte.C compiled.HOAS.native.C compiled.HOAS.O2.C compiled.evalapply.byte.C compiled.evalapply.native.C compiled.evalapply.O2.C 2 | 18 0.010071 0.120076 0.107529 0.019736 0.111579 0.114294 3 | 19 0.011457 0.111411 0.100425 0.014643 0.108795 0.110224 4 | 20 0.010089 0.106713 0.103832 0.014604 0.109356 0.117152 5 | 21 0.011487 0.101422 0.111531 0.014624 0.109615 0.108029 6 | 22 0.017806 0.105756 0.101732 0.014883 0.131926 0.119841 7 | 23 0.011735 0.121901 0.110657 0.014292 0.109066 0.110593 8 | 24 0.012290 0.104334 0.105501 0.024879 0.138755 0.114170 9 | -------------------------------------------------------------------------------- /data/compiled-compile/exponential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/compiled-compile/exponential.png -------------------------------------------------------------------------------- /data/compiled-compile/parigot_add.dat: -------------------------------------------------------------------------------- 1 | size compiled.HOAS.byte.C compiled.HOAS.native.C compiled.HOAS.O2.C compiled.evalapply.byte.C compiled.evalapply.native.C compiled.evalapply.O2.C 2 | 5 0.010319 0.115651 0.127867 0.012959 0.146614 0.124830 3 | 6 0.011082 0.110078 0.140992 0.014171 0.120793 0.119245 4 | 7 0.012327 0.113076 0.112165 0.013319 0.116160 0.149900 5 | 8 0.013511 0.114655 0.119139 0.019538 0.109334 0.108009 6 | 9 0.012371 0.116256 0.133343 0.015579 0.124324 0.115123 7 | 10 0.014320 0.130449 0.118414 0.014534 0.134750 0.115438 8 | 11 0.012420 0.120287 0.116931 0.014158 0.115974 0.122079 9 | 12 0.012865 0.122826 0.120875 0.013379 0.113492 0.130429 10 | -------------------------------------------------------------------------------- /data/compiled-compile/parigot_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/compiled-compile/parigot_add.png -------------------------------------------------------------------------------- /data/compiled-compile/random.dat: -------------------------------------------------------------------------------- 1 | size compiled.HOAS.byte.C compiled.HOAS.native.C compiled.HOAS.O2.C compiled.evalapply.byte.C compiled.evalapply.native.C compiled.evalapply.O2.C 2 | 1000 1.132749 8.648800 8.458632 0.694569 3.017880 2.973691 3 | 2000 3.378489 MISSING MISSING 1.748238 6.676803 6.730791 4 | 3000 6.530143 MISSING MISSING 3.108472 11.011134 10.930142 5 | 4000 10.599528 MISSING MISSING 4.839757 15.865773 15.882538 6 | 5000 15.391434 MISSING MISSING 7.272681 MISSING MISSING 7 | 6000 MISSING MISSING MISSING 9.537875 MISSING MISSING 8 | 7000 MISSING MISSING MISSING 12.503241 MISSING MISSING 9 | 8000 MISSING MISSING MISSING 15.838875 MISSING MISSING 10 | -------------------------------------------------------------------------------- /data/compiled-compile/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/compiled-compile/random.png -------------------------------------------------------------------------------- /data/compiled-normalize/church_mul.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list compiled.HOAS.byte.N compiled.HOAS.native.N compiled.HOAS.O2.N compiled.evalapply.byte.N compiled.evalapply.native.N compiled.evalapply.O2.N 2 | 120 0.000702 0.001825 0.000503 0.000483 0.001485 0.00054 0.000585 3 | 140 0.001102 0.002537 0.000938 0.000733 0.00157 0.000805 0.000758 4 | 160 0.003385 0.002449 0.000984 0.000958 0.002534 0.000956 0.000941 5 | 180 0.004374 0.002761 0.001283 0.001167 0.002684 0.001185 0.001175 6 | 200 0.003369 0.005927 0.003478 0.003317 0.015136 0.003482 0.003461 7 | 220 0.005162 0.006028 0.003905 0.004049 0.006448 0.00399 0.004078 8 | 240 0.006887 0.011469 0.004381 0.005031 0.010322 0.00473 0.004724 9 | 260 0.010696 0.023276 0.006999 0.006689 0.010979 0.007163 0.006728 10 | 280 0.010776 0.012795 0.007524 0.007447 0.012641 0.007845 0.007935 11 | 300 0.008796 0.012716 0.007748 0.00772 0.011494 0.007666 0.007826 12 | 320 0.012007 0.020563 0.009277 0.009538 0.01398 0.009492 0.009428 13 | -------------------------------------------------------------------------------- /data/compiled-normalize/church_mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/compiled-normalize/church_mul.png -------------------------------------------------------------------------------- /data/compiled-normalize/exponential.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list compiled.HOAS.byte.N compiled.HOAS.native.N compiled.HOAS.O2.N compiled.evalapply.byte.N compiled.evalapply.native.N compiled.evalapply.O2.N 2 | 18 0.014135 0.026127 0.013365 0.012648 0.022445 0.012151 0.011961 3 | 19 0.042106 0.058092 0.039119 0.041575 0.058982 0.041593 0.038699 4 | 20 0.107448 0.143824 0.10249 0.100351 0.136355 0.101309 0.100603 5 | 21 0.308913 0.37966 0.303581 0.302667 0.379931 0.330431 0.309775 6 | 22 0.553154 0.679485 0.535605 0.537172 0.666335 0.529035 0.5372 7 | 23 0.973736 1.262329 1.009371 0.995171 1.259046 1.016292 1.000193 8 | 24 1.908078 2.499579 1.90625 1.908105 2.437501 2.200202 2.327991 9 | -------------------------------------------------------------------------------- /data/compiled-normalize/exponential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/compiled-normalize/exponential.png -------------------------------------------------------------------------------- /data/compiled-normalize/parigot_add.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list compiled.HOAS.byte.N compiled.HOAS.native.N compiled.HOAS.O2.N compiled.evalapply.byte.N compiled.evalapply.native.N compiled.evalapply.O2.N 2 | 5 0.000152 0.000308 8.5e-05 8.5e-05 0.000256 9.5e-05 6.5e-05 3 | 6 0.000622 0.001064 0.000382 0.000372 0.000783 0.000324 0.000239 4 | 7 0.003324 0.008151 0.002442 0.002432 0.004798 0.001909 0.001959 5 | 8 0.018994 0.034534 0.012749 0.01241 0.021684 0.011782 0.012257 6 | 9 0.123053 0.150973 0.105725 0.106269 0.119614 0.089111 0.085275 7 | 10 0.387160 0.524813 0.3666 0.360476 0.512595 0.39173 0.400015 8 | 11 1.452336 1.958803 1.361905 1.348362 1.868273 1.330758 1.334316 9 | 12 5.777015 7.780542 5.431224 5.468371 7.568972 5.870313 5.387863 10 | -------------------------------------------------------------------------------- /data/compiled-normalize/parigot_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/compiled-normalize/parigot_add.png -------------------------------------------------------------------------------- /data/compiled-normalize/random.dat: -------------------------------------------------------------------------------- 1 | size NBE.closure.list compiled.HOAS.byte.N compiled.HOAS.native.N compiled.HOAS.O2.N compiled.evalapply.byte.N compiled.evalapply.native.N compiled.evalapply.O2.N 2 | 1000 0.000442 0.000749 0.001484 0.001483 0.000646 0.000743 0.000738 3 | 2000 0.000355 0.001173 MISSING MISSING 0.001015 0.001167 0.001256 4 | 3000 0.000381 0.00097 MISSING MISSING 0.000806 0.001157 0.001371 5 | 4000 0.003695 0.001471 MISSING MISSING 0.001228 0.001627 0.001526 6 | 5000 0.002532 0.001457 MISSING MISSING 0.001186 MISSING MISSING 7 | 6000 0.003871 MISSING MISSING MISSING 0.004128 MISSING MISSING 8 | 7000 0.001735 MISSING MISSING MISSING 0.001868 MISSING MISSING 9 | 8000 0.010820 MISSING MISSING MISSING 0.011311 MISSING MISSING 10 | -------------------------------------------------------------------------------- /data/compiled-normalize/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/compiled-normalize/random.png -------------------------------------------------------------------------------- /data/subst-NBE/church_add.dat: -------------------------------------------------------------------------------- 1 | size subst.naive subst.whead NBE.closure.list 2 | 10000 0.009149 0.008108 0.002985 3 | 25000 0.033983 0.023256 0.005007 4 | 50000 0.084993 0.066493 0.013481 5 | 75000 0.149838 0.128837 0.023413 6 | 100000 0.222781 0.156517 0.037991 7 | 200000 0.501537 0.402958 0.120917 8 | 300000 0.879051 0.679198 0.207956 9 | 400000 1.329194 0.917536 0.370334 10 | -------------------------------------------------------------------------------- /data/subst-NBE/church_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/subst-NBE/church_add.png -------------------------------------------------------------------------------- /data/subst-NBE/church_mul.dat: -------------------------------------------------------------------------------- 1 | size subst.naive subst.whead NBE.closure.list 2 | 120 0.065104 0.073704 0.000824 3 | 140 0.129114 0.123060 0.000905 4 | 160 0.213230 0.221706 0.004561 5 | 180 0.350326 0.342283 0.004341 6 | 200 0.513330 0.523929 0.004347 7 | 220 0.714086 0.760239 0.005882 8 | 240 1.033424 1.064720 0.008587 9 | 260 1.398201 1.405511 0.010386 10 | 280 1.804557 1.848332 0.010238 11 | 300 2.311230 2.615978 0.008336 12 | 320 2.805038 2.857824 0.011969 13 | -------------------------------------------------------------------------------- /data/subst-NBE/church_mul.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/subst-NBE/church_mul.png -------------------------------------------------------------------------------- /data/subst-NBE/exponential.dat: -------------------------------------------------------------------------------- 1 | size subst.naive subst.whead NBE.closure.list 2 | 18 0.094668 0.100870 0.015595 3 | 19 0.185967 0.256368 0.042699 4 | 20 0.417966 0.509827 0.104362 5 | 21 0.849633 1.050080 0.323247 6 | 22 1.732821 2.024426 0.551748 7 | 23 3.536780 4.124995 0.998048 8 | 24 7.356972 8.195054 1.994862 9 | -------------------------------------------------------------------------------- /data/subst-NBE/exponential.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/subst-NBE/exponential.png -------------------------------------------------------------------------------- /data/subst-NBE/parigot_add.dat: -------------------------------------------------------------------------------- 1 | size subst.naive subst.whead NBE.closure.list 2 | 5 0.000352 0.002612 0.000177 3 | 6 0.001886 0.012433 0.000910 4 | 7 0.008727 0.072824 0.004288 5 | 8 0.049742 0.362150 0.017977 6 | 9 0.228233 1.626929 0.126902 7 | 10 0.800573 7.732210 0.397354 8 | 11 3.355417 MISSING 1.562973 9 | 12 15.597657 MISSING 6.085315 10 | -------------------------------------------------------------------------------- /data/subst-NBE/parigot_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/subst-NBE/parigot_add.png -------------------------------------------------------------------------------- /data/subst-NBE/random.dat: -------------------------------------------------------------------------------- 1 | size subst.naive subst.whead NBE.closure.list 2 | 1000 0.015381 0.012503 0.000495 3 | 2000 0.049316 0.030929 0.000464 4 | 3000 0.107069 0.059507 0.000737 5 | 4000 1.304346 0.106101 0.003743 6 | 5000 0.251915 0.136580 0.002295 7 | 6000 4.217177 0.248054 0.002415 8 | 7000 MISSING 0.350262 0.001683 9 | 8000 5.385890 0.375602 0.001149 10 | -------------------------------------------------------------------------------- /data/subst-NBE/random.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/subst-NBE/random.png -------------------------------------------------------------------------------- /data/subst-NBE/self_interp_size.dat: -------------------------------------------------------------------------------- 1 | size subst.naive subst.whead NBE.closure.list 2 | 1000 0.328757 2.626253 0.001011 3 | 2000 1.884528 16.464166 0.002162 4 | 3000 5.681343 MISSING 0.003914 5 | 4000 12.521041 MISSING 0.003084 6 | 5000 MISSING MISSING 0.007065 7 | 6000 MISSING MISSING 0.007237 8 | -------------------------------------------------------------------------------- /data/subst-NBE/self_interp_size.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Guest0x0/normalization-bench/ed3a61c65c5ac3238f8905a020df671caac991fa/data/subst-NBE/self_interp_size.png -------------------------------------------------------------------------------- /dune: -------------------------------------------------------------------------------- 1 | (env 2 | (_ 3 | (flags (:standard -warn-error -a+31)))) 4 | 5 | (data_only_dirs data) 6 | 7 | (executable 8 | (name bench) 9 | (modes native) 10 | (libraries common normalizers) 11 | (modules bench)) 12 | 13 | (executable 14 | (name count_terms) 15 | (libraries common) 16 | (modules count_terms)) 17 | 18 | (executable 19 | (name gen_random_terms) 20 | (libraries common) 21 | (modules gen_random_terms)) 22 | -------------------------------------------------------------------------------- /dune-project: -------------------------------------------------------------------------------- 1 | (lang dune 2.9) 2 | -------------------------------------------------------------------------------- /gen_random_terms.ml: -------------------------------------------------------------------------------- 1 | 2 | let _ = 3 | let _ = Random.self_init () in 4 | let size = int_of_string Sys.argv.(1) in 5 | let env_len = int_of_string Sys.argv.(2) in 6 | let num_terms = int_of_string Sys.argv.(3) in 7 | let target_file = Sys.argv.(4) in 8 | 9 | let term_count_file = 10 | open_in_bin (try Sys.argv.(5) with _ -> "data/term_counts") 11 | in 12 | let table : Common.ApproxNum.t array array = input_value term_count_file in 13 | close_in term_count_file; 14 | 15 | let max_size = Array.length table in 16 | let max_env_len = Array.length table.(0) in 17 | if size <= 0 then 18 | raise(Invalid_argument "gen_random_terms: non-positive size"); 19 | if env_len < 0 then 20 | raise(Invalid_argument "gen_random_terms: negative number of free variables"); 21 | if size >= max_size then 22 | raise(Invalid_argument "gen_random_terms: size too large"); 23 | if env_len >= max_env_len then 24 | raise(Invalid_argument "gen_random_terms: free variable number too large"); 25 | 26 | let exception Fail in 27 | let rec gen size env_len = 28 | let open Common.ApproxNum in 29 | let open Common.Syntax in 30 | match size with 31 | | 1 -> 32 | Idx(Random.int env_len) 33 | | _ -> 34 | let count = table.(size).(env_len) in 35 | let p = Random.float 1. in 36 | let nth = p *> count in 37 | let n_lam = 38 | if env_len + 1 < max_env_len 39 | then table.(size - 1).(env_len + 1) 40 | else of_int 0 41 | in 42 | if compare nth n_lam <= 0 43 | then 44 | Lam(gen (size - 1) (env_len + 1)) 45 | else if size > 2 then 46 | let rec loop acc i = 47 | let n_func = table.(i ).(env_len) in 48 | let n_arg = table.(size - 1 - i).(env_len) in 49 | let acc' = acc <+> (n_func <*> n_arg) in 50 | if compare nth acc' <= 0 51 | then App( gen i env_len 52 | , gen (size - 1 - i) env_len ) 53 | else loop acc' (i + 1) 54 | in 55 | loop n_lam 1 56 | else 57 | raise Fail 58 | in 59 | let out = open_out target_file in 60 | let rec loop i = 61 | if i >= num_terms 62 | then () 63 | else 64 | let i' = 65 | match gen size env_len with 66 | | tm -> 67 | let buf = Common.Syntax.serialize tm in 68 | Buffer.output_buffer out buf; 69 | i + 1 70 | | exception Fail -> 71 | i 72 | in 73 | loop i' 74 | in 75 | loop 1; 76 | close_out out 77 | -------------------------------------------------------------------------------- /plot.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cmd="dune exec ./bench.exe" 4 | 5 | ( 6 | echo "set terminal png;" 7 | echo "set datafile missing 'MISSING'" 8 | for combi in $($cmd list-combinations); do 9 | for bench in $($cmd list-benches $combi); do 10 | echo "set output 'data/$combi/$bench.png'" 11 | echo "set title '$combi@$bench' noenhanced" 12 | column=2 13 | echo -n "plot" 14 | for normalizer in $($cmd list-normalizers $combi); do 15 | echo -n " 'data/$combi/$bench.dat' using 1:$column title columnheader($column) with lines lw 3," 16 | column=$((column + 1)) 17 | done 18 | echo ";" 19 | done 20 | done 21 | ) | gnuplot 22 | --------------------------------------------------------------------------------