• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

formalsec / smtml / 518

05 Apr 2026 10:17AM UTC coverage: 44.784% (+0.06%) from 44.728%
518

push

github

filipeom
fix: lazier evaluation of naryop and proper symbolic string concat

Previously, to evaluate a naryop we started by checking if the list of
expressions contained only values. If so, we would perform another
traversal over the list to extract the actual values.
In the happy path this would cause two traversals over the list of
expressions.
To fix this we can iterate through the list and extract the values until
either one of two things occurs:

1. We reach the end of the list -> dispatch concrete evaluation
2. We get something other than a value -> stop

Doing this allows us to preserve the short-circuit evaluation of
naryops with symbolic arguments and also reduce a traversal during the
evaluation of concrete naryops.

This commit also addresses the previous incorrect simplification of the
operation `str.++` (string concatenation). Previously, we only joined
daisy-chained concatenation operators if the lists contained only two
arguments. This is wrong because the operator can take a list.
This resulted in expressions not being simplified when the operator was
used with 3 or more arguments.

5 of 15 new or added lines in 1 file covered. (33.33%)

966 of 2157 relevant lines covered (44.78%)

9.39 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

35.9
/src/smtml/expr.ml
1
(* SPDX-License-Identifier: MIT *)
2
(* Copyright (C) 2023-2026 formalsec *)
3
(* Written by the Smtml programmers *)
4

5
type t = expr Hc.hash_consed
6

7
and expr =
8
  | Val of Value.t
9
  | Ptr of
10
      { base : Bitvector.t
11
      ; offset : t
12
      }
13
  | Symbol of Symbol.t
14
  | List of t list
15
  | App of Symbol.t * t list
16
  | Unop of Ty.t * Ty.Unop.t * t
17
  | Binop of Ty.t * Ty.Binop.t * t * t
18
  | Triop of Ty.t * Ty.Triop.t * t * t * t
19
  | Relop of Ty.t * Ty.Relop.t * t * t
20
  | Cvtop of Ty.t * Ty.Cvtop.t * t
21
  | Naryop of Ty.t * Ty.Naryop.t * t list
22
  | Extract of t * int * int
23
  | Concat of t * t
24
  | Binder of Binder.t * t list * t
25

26
module Expr = struct
27
  type t = expr
28

29
  let list_eq (l1 : 'a list) (l2 : 'a list) : bool =
30
    if List.compare_lengths l1 l2 = 0 then List.for_all2 phys_equal l1 l2
4✔
31
    else false
×
32

33
  let equal (e1 : expr) (e2 : expr) : bool =
34
    match (e1, e2) with
627✔
35
    | Val v1, Val v2 -> Value.equal v1 v2
550✔
36
    | Ptr { base = b1; offset = o1 }, Ptr { base = b2; offset = o2 } ->
5✔
37
      Bitvector.equal b1 b2 && phys_equal o1 o2
5✔
38
    | Symbol s1, Symbol s2 -> Symbol.equal s1 s2
26✔
39
    | List l1, List l2 -> list_eq l1 l2
4✔
40
    | App (s1, l1), App (s2, l2) -> Symbol.equal s1 s2 && list_eq l1 l2
×
41
    | Unop (t1, op1, e1), Unop (t2, op2, e2) ->
9✔
42
      Ty.equal t1 t2 && Ty.Unop.equal op1 op2 && phys_equal e1 e2
9✔
43
    | Binop (t1, op1, e1, e3), Binop (t2, op2, e2, e4) ->
20✔
44
      Ty.equal t1 t2 && Ty.Binop.equal op1 op2 && phys_equal e1 e2
19✔
45
      && phys_equal e3 e4
19✔
46
    | Relop (t1, op1, e1, e3), Relop (t2, op2, e2, e4) ->
1✔
47
      Ty.equal t1 t2 && Ty.Relop.equal op1 op2 && phys_equal e1 e2
1✔
48
      && phys_equal e3 e4
1✔
49
    | Triop (t1, op1, e1, e3, e5), Triop (t2, op2, e2, e4, e6) ->
×
50
      Ty.equal t1 t2 && Ty.Triop.equal op1 op2 && phys_equal e1 e2
×
51
      && phys_equal e3 e4 && phys_equal e5 e6
×
52
    | Cvtop (t1, op1, e1), Cvtop (t2, op2, e2) ->
×
53
      Ty.equal t1 t2 && Ty.Cvtop.equal op1 op2 && phys_equal e1 e2
×
54
    | Naryop (t1, op1, l1), Naryop (t2, op2, l2) ->
×
55
      Ty.equal t1 t2 && Ty.Naryop.equal op1 op2 && list_eq l1 l2
×
56
    | Extract (e1, h1, l1), Extract (e2, h2, l2) ->
×
57
      phys_equal e1 e2 && h1 = h2 && l1 = l2
×
58
    | Concat (e1, e3), Concat (e2, e4) -> phys_equal e1 e2 && phys_equal e3 e4
×
59
    | Binder (binder1, vars1, e1), Binder (binder2, vars2, e2) ->
×
60
      Binder.equal binder1 binder2 && list_eq vars1 vars2 && phys_equal e1 e2
×
61
    | ( ( Val _ | Ptr _ | Symbol _ | List _ | App _ | Unop _ | Binop _ | Triop _
×
62
        | Relop _ | Cvtop _ | Naryop _ | Extract _ | Concat _ | Binder _ )
×
63
      , _ ) ->
64
      false
65

66
  (* Optimized mixer (DJB2 variant). Inlines to simple arithmetic. *)
67
  let[@inline] combine h v = (h * 33) + v
354✔
68

69
  let hash (e : expr) : int =
70
    match e with
1,057✔
71
    | Val v -> Value.hash v
833✔
72
    | Ptr { base; offset } -> combine (Bitvector.hash base) offset.tag
21✔
73
    | Symbol s -> Symbol.hash s
68✔
74
    | List l -> List.fold_left (fun acc x -> combine acc x.Hc.tag) 0 l
16✔
75
    | App (s, es) ->
×
76
      let h_s = Symbol.hash s in
77
      List.fold_left (fun acc x -> combine acc x.Hc.tag) h_s es
×
78
    | Unop (ty, op, e) ->
19✔
79
      let h1 = Ty.hash ty in
80
      let h2 = combine h1 (Ty.Unop.hash op) in
19✔
81
      combine h2 e.tag
19✔
82
    | Binop (ty, op, e1, e2) ->
57✔
83
      let h = Ty.hash ty in
84
      let h = combine h (Ty.Binop.hash op) in
57✔
85
      let h = combine h e1.tag in
57✔
86
      combine h e2.tag
57✔
87
    | Triop (ty, op, e1, e2, e3) ->
×
88
      let h = Ty.hash ty in
89
      let h = combine h (Ty.Triop.hash op) in
×
90
      let h = combine h e1.tag in
×
91
      let h = combine h e2.tag in
×
92
      combine h e3.tag
×
93
    | Relop (ty, op, e1, e2) ->
9✔
94
      let h = Ty.hash ty in
95
      let h = combine h (Ty.Relop.hash op) in
9✔
96
      let h = combine h e1.tag in
9✔
97
      combine h e2.tag
9✔
98
    | Cvtop (ty, op, e) ->
16✔
99
      let h = Ty.hash ty in
100
      let h = combine h (Ty.Cvtop.hash op) in
16✔
101
      combine h e.tag
16✔
102
    | Naryop (ty, op, es) ->
×
103
      let h = Ty.hash ty in
104
      let h = combine h (Ty.Naryop.hash op) in
×
105
      List.fold_left (fun acc x -> combine acc x.Hc.tag) h es
×
106
    | Extract (e, hi, lo) ->
14✔
107
      let h = e.tag in
108
      let h = combine h hi in
109
      combine h lo
14✔
110
    | Concat (e1, e2) -> combine e1.tag e2.tag
4✔
111
    | Binder (b, vars, e) ->
×
112
      let h = Binder.hash b in
113
      let h_vars = List.fold_left (fun acc x -> combine acc x.Hc.tag) h vars in
×
114
      combine h_vars e.tag
×
115
end
116

117
module Hc = Hc.Make_strong [@inlined hint] (Expr)
118

119
let equal (hte1 : t) (hte2 : t) = phys_equal hte1 hte2 [@@inline]
238✔
120

121
let hash (hte : t) = hte.tag [@@inline]
4✔
122

123
module Key = struct
124
  type nonrec t = t
125

126
  let to_int hte = hash hte
×
127

128
  let compare x y = compare (to_int x) (to_int y)
×
129
end
130

131
let[@inline] make e = Hc.hashcons e
833✔
132

133
let[@inline] view (hte : t) = hte.node
599✔
134

135
let[@inline] compare (hte1 : t) (hte2 : t) = compare hte1.tag hte2.tag
×
136

137
let symbol s = make (Symbol s)
47✔
138

139
(** The return type of an expression *)
140
let rec ty (hte : t) : Ty.t =
141
  match view hte with
13✔
142
  | Val x -> Value.type_of x
×
143
  | Ptr _ -> Ty_bitv 32
×
144
  | Symbol x -> Symbol.type_of x
10✔
145
  | List _ -> Ty_list
×
146
  | App (sym, _) ->
×
147
    begin match sym.ty with Ty_none -> Ty_app | ty -> ty
×
148
    end
149
  | Triop (_, Ite, _, hte1, hte2) ->
×
150
    let ty1 = ty hte1 in
151
    assert (
×
152
      let ty2 = ty hte2 in
153
      Ty.equal ty1 ty2 );
×
154
    ty1
155
  | Cvtop (_, (Zero_extend m | Sign_extend m), hte) -> (
×
156
    match ty hte with Ty_bitv n -> Ty_bitv (n + m) | _ -> assert false )
1✔
157
  | Unop (ty, _, _)
×
158
  | Binop (ty, _, _, _)
×
159
  | Triop (ty, _, _, _, _)
×
160
  | Relop (ty, _, _, _)
×
161
  | Cvtop (ty, _, _)
×
162
  | Naryop (ty, _, _) ->
×
163
    ty
164
  | Extract (_, h, l) -> Ty_bitv ((h - l) * 8)
2✔
165
  | Concat (e1, e2) -> (
×
166
    match (ty e1, ty e2) with
×
167
    | Ty_bitv n1, Ty_bitv n2 -> Ty_bitv (n1 + n2)
×
168
    | t1, t2 ->
×
169
      Fmt.failwith "Invalid concat of (%a) with (%a)" Ty.pp t1 Ty.pp t2 )
170
  | Binder (_, _, e) -> ty e
×
171

172
let rec is_symbolic (v : t) : bool =
173
  match view v with
×
174
  | Val _ -> false
×
175
  | Symbol _ -> true
×
176
  | Ptr { offset; _ } -> is_symbolic offset
×
177
  | Unop (_, _, v) | Cvtop (_, _, v) | Extract (v, _, _) | Binder (_, _, v) ->
×
178
    is_symbolic v
179
  | Binop (_, _, v1, v2) | Relop (_, _, v1, v2) | Concat (v1, v2) ->
×
180
    is_symbolic v1 || is_symbolic v2
×
181
  | Triop (_, _, v1, v2, v3) ->
×
182
    is_symbolic v1 || is_symbolic v2 || is_symbolic v3
×
183
  | List vs | App (_, vs) | Naryop (_, _, vs) -> List.exists is_symbolic vs
×
184

185
let get_symbols (hte : t list) =
186
  let tbl = Hashtbl.create 64 in
1✔
187
  let rec symbols (hte : t) =
1✔
188
    match view hte with
13✔
189
    | Val _ -> ()
3✔
190
    | Ptr { offset; _ } -> symbols offset
×
191
    | Symbol s -> Hashtbl.replace tbl s ()
5✔
192
    | List es -> List.iter symbols es
×
193
    | App (_, es) -> List.iter symbols es
×
194
    | Unop (_, _, e1) -> symbols e1
×
195
    | Binop (_, _, e1, e2) ->
2✔
196
      symbols e1;
197
      symbols e2
2✔
198
    | Triop (_, _, e1, e2, e3) ->
×
199
      symbols e1;
200
      symbols e2;
×
201
      symbols e3
×
202
    | Relop (_, _, e1, e2) ->
3✔
203
      symbols e1;
204
      symbols e2
3✔
205
    | Cvtop (_, _, e) -> symbols e
×
206
    | Naryop (_, _, es) -> List.iter symbols es
×
207
    | Extract (e, _, _) -> symbols e
×
208
    | Concat (e1, e2) ->
×
209
      symbols e1;
210
      symbols e2
×
211
    | Binder (_, vars, e) ->
×
212
      List.iter symbols vars;
213
      symbols e
×
214
  in
215
  List.iter symbols hte;
216
  Hashtbl.fold (fun k () acc -> k :: acc) tbl []
1✔
217

218
let rec pp_with ~printer fmt (hte : t) =
219
  match view hte with
×
220
  | Val v -> Value.pp_with ~printer fmt v
×
221
  | Ptr { base; offset } ->
×
222
    Fmt.pf fmt "(Ptr %a %a)"
223
      (Bitvector.pp_with ~printer)
224
      base (pp_with ~printer) offset
225
  | Symbol s -> Fmt.pf fmt "@[<hov 1>%a@]" Symbol.pp s
×
226
  | List v ->
×
227
    Fmt.pf fmt "@[<hov 1>[%a]@]" (Fmt.list ~sep:Fmt.comma (pp_with ~printer)) v
×
228
  | App (s, v) ->
×
229
    Fmt.pf fmt "@[<hov 1>(%a@ %a)@]" Symbol.pp s
230
      (Fmt.list ~sep:Fmt.comma (pp_with ~printer))
×
231
      v
232
  | Unop (ty, op, e) ->
×
233
    Fmt.pf fmt "@[<hov 1>(%a.%a@ %a)@]" Ty.pp ty Ty.Unop.pp op
234
      (pp_with ~printer) e
235
  | Binop (ty, op, e1, e2) ->
×
236
    Fmt.pf fmt "@[<hov 1>(%a.%a@ %a@ %a)@]" Ty.pp ty Ty.Binop.pp op
237
      (pp_with ~printer) e1 (pp_with ~printer) e2
238
  | Triop (ty, op, e1, e2, e3) ->
×
239
    Fmt.pf fmt "@[<hov 1>(%a.%a@ %a@ %a@ %a)@]" Ty.pp ty Ty.Triop.pp op
240
      (pp_with ~printer) e1 (pp_with ~printer) e2 (pp_with ~printer) e3
241
  | Relop (ty, op, e1, e2) ->
×
242
    Fmt.pf fmt "@[<hov 1>(%a.%a@ %a@ %a)@]" Ty.pp ty Ty.Relop.pp op
243
      (pp_with ~printer) e1 (pp_with ~printer) e2
244
  | Cvtop (ty, op, e) ->
×
245
    Fmt.pf fmt "@[<hov 1>(%a.%a@ %a)@]" Ty.pp ty Ty.Cvtop.pp op
246
      (pp_with ~printer) e
247
  | Naryop (ty, op, es) ->
×
248
    Fmt.pf fmt "@[<hov 1>(%a.%a@ %a)@]" Ty.pp ty Ty.Naryop.pp op
249
      (Fmt.list ~sep:Fmt.sp (pp_with ~printer))
×
250
      es
251
  | Extract (e, h, l) ->
×
252
    Fmt.pf fmt "@[<hov 1>(extract@ %a@ %d@ %d)@]" (pp_with ~printer) e l h
253
  | Concat (e1, e2) ->
×
254
    Fmt.pf fmt "@[<hov 1>(++@ %a@ %a)@]" (pp_with ~printer) e1
255
      (pp_with ~printer) e2
256
  | Binder (b, vars, e) ->
×
257
    Fmt.pf fmt "@[<hov 1>(%a@ (%a)@ %a)@]" Binder.pp b
258
      (Fmt.list ~sep:Fmt.sp (pp_with ~printer))
×
259
      vars (pp_with ~printer) e
260

261
let pp fmt e = pp_with ~printer:Without_type fmt e
×
262

263
let pp_safe fmt e = pp_with ~printer:With_type_and_hexa_float fmt e
×
264

265
let pp_list fmt (es : t list) = Fmt.hovbox (Fmt.list ~sep:Fmt.comma pp) fmt es
×
266

267
let pp_smtml fmt (es : t list) : unit =
268
  let pp_symbols fmt syms =
×
269
    Fmt.list ~sep:Fmt.cut
×
270
      (fun fmt sym ->
271
        let t = Symbol.type_of sym in
×
272
        Fmt.pf fmt "(let-const %a %a)" Symbol.pp sym Ty.pp t )
×
273
      fmt syms
274
  in
275
  let pp_asserts fmt es =
276
    Fmt.list ~sep:Fmt.cut
×
277
      (fun fmt e -> Fmt.pf fmt "(assert @[<h 2>%a@])" pp_safe e)
×
278
      fmt es
279
  in
280
  let syms = get_symbols es in
281
  if List.length syms > 0 then Fmt.pf fmt "@[<v>%a@]@\n" pp_symbols syms;
×
282
  if List.length es > 0 then Fmt.pf fmt "@[<v>%a@]@\n" pp_asserts es;
×
283
  Fmt.string fmt "(check-sat)"
×
284

285
let to_string e = Fmt.str "%a" pp e
×
286

287
let value (v : Value.t) : t = make (Val v) [@@inline]
689✔
288

289
let ptr base offset = make (Ptr { base = Bitvector.of_int32 base; offset })
7✔
290

291
let list l = make (List l)
5✔
292

293
let app symbol args = make (App (symbol, args))
×
294

295
let[@inline] binder bt vars expr = make (Binder (bt, vars, expr))
×
296

297
let let_in vars body = binder Let_in vars body
×
298

299
let forall vars body = binder Forall vars body
×
300

301
let exists vars body = binder Exists vars body
×
302

303
let raw_unop ty op hte = make (Unop (ty, op, hte)) [@@inline]
14✔
304

305
let negate_relop (hte : t) : t =
306
  let e =
×
307
    match view hte with
308
    | Relop (ty, Eq, e1, e2) -> Relop (ty, Ne, e1, e2)
×
309
    | Relop (ty, Ne, e1, e2) -> Relop (ty, Eq, e1, e2)
×
310
    | Relop (ty, Lt, e1, e2) -> Relop (ty, Le, e2, e1)
×
311
    | Relop (ty, LtU, e1, e2) -> Relop (ty, LeU, e2, e1)
×
312
    | Relop (ty, Le, e1, e2) -> Relop (ty, Lt, e2, e1)
×
313
    | Relop (ty, LeU, e1, e2) -> Relop (ty, LtU, e2, e1)
×
314
    | _ -> Fmt.failwith "negate_relop: not a relop."
×
315
  in
316
  make e
317

318
let rec unop ty op hte =
319
  match (op, view hte) with
48✔
320
  | Ty.Unop.(Regexp_loop _ | Regexp_star), _ -> raw_unop ty op hte
×
321
  | _, Val v -> value (Eval.unop ty op v)
24✔
322
  | Not, Unop (_, Not, hte') -> hte'
1✔
323
  | Neg, Unop (_, Neg, hte') -> hte'
1✔
324
  | Not, Binop (inner_ty, Or, a, b) ->
3✔
325
    make (Binop (inner_ty, And, unop ty Not a, unop ty Not b))
3✔
326
  | Not, Relop (Ty_fp _, _, _, _) -> raw_unop ty op hte
2✔
327
  | Not, Relop (_, _, _, _) -> negate_relop hte
×
328
  | Trim, Cvtop (Ty_real, ToString, _) -> hte
×
329
  | Head, List (hd :: _) -> hd
1✔
330
  | Tail, List (_ :: tl) -> make (List tl)
1✔
331
  | Reverse, List es -> make (List (List.rev es))
2✔
332
  | Length, List es -> value (Int (List.length es))
1✔
333
  | _ -> raw_unop ty op hte
12✔
334

335
let raw_binop ty op hte1 hte2 = make (Binop (ty, op, hte1, hte2)) [@@inline]
35✔
336

337
let rec binop ty op hte1 hte2 =
338
  match (op, view hte1, view hte2) with
119✔
339
  | Ty.Binop.(String_in_re | Regexp_range), _, _ -> raw_binop ty op hte1 hte2
×
340
  | op, Val v1, Val v2 -> value (Eval.binop ty op v1 v2)
69✔
341
  | Sub, Ptr { base = b1; offset = os1 }, Ptr { base = b2; offset = os2 } ->
1✔
342
    if Bitvector.equal b1 b2 then binop ty Sub os1 os2
1✔
343
    else raw_binop ty op hte1 hte2
×
344
  | Add, Ptr { base; offset }, _ ->
2✔
345
    let m = Bitvector.numbits base in
346
    make (Ptr { base; offset = binop (Ty_bitv m) Add offset hte2 })
2✔
347
  | Sub, Ptr { base; offset }, _ ->
1✔
348
    let m = Bitvector.numbits base in
349
    make (Ptr { base; offset = binop (Ty_bitv m) Sub offset hte2 })
1✔
350
  | Rem, Ptr { base; offset }, _ ->
1✔
351
    let m = Bitvector.numbits base in
352
    let rhs = value (Bitv base) in
1✔
353
    let addr = binop (Ty_bitv m) Add rhs offset in
1✔
354
    binop ty Rem addr hte2
1✔
355
  | Add, _, Ptr { base; offset } ->
1✔
356
    let m = Bitvector.numbits base in
357
    make (Ptr { base; offset = binop (Ty_bitv m) Add offset hte1 })
1✔
358
  | Sub, _, Ptr { base; offset } ->
×
359
    let m = Bitvector.numbits base in
360
    let base = value (Bitv base) in
×
361
    binop ty Sub hte1 (binop (Ty_bitv m) Add base offset)
×
362
  | (Add | Or), Val (Bitv bv), _ when Bitvector.eqz bv -> hte2
×
363
  | (And | Div | DivU | Mul | Rem | RemU), Val (Bitv bv), _
×
364
    when Bitvector.eqz bv ->
4✔
365
    hte1
1✔
366
  | (Add | Or), _, Val (Bitv bv) when Bitvector.eqz bv -> hte1
×
367
  | Add, _, Val (Real -0.) -> hte1
×
368
  | Add, Val (Real -0.), _ -> hte2
×
369
  | Add, _, Val (Num (F32 -0l)) -> hte1
1✔
370
  | Add, Val (Num (F32 -0l)), _ -> hte2
×
371
  | Add, _, Val (Num (F64 -0L)) -> hte1
×
372
  | Add, Val (Num (F64 -0L)), _ -> hte2
×
373
  | (And | Mul), _, Val (Bitv bv) when Bitvector.eqz bv -> hte2
1✔
374
  | And, Val True, _ -> hte2
×
375
  | And, _, Val True -> hte1
1✔
376
  | And, Val False, _ -> hte1
1✔
377
  | And, _, Val False -> hte2
×
378
  | Or, Val True, _ -> hte1
×
379
  | Or, _, Val True -> hte2
1✔
380
  | Or, Val False, _ -> hte2
×
381
  | Or, _, Val False -> hte1
1✔
382
  | Add, Binop (ty, Add, x, { node = Val v1; _ }), Val v2 ->
1✔
383
    let v = value (Eval.binop ty Add v1 v2) in
1✔
384
    raw_binop ty Add x v
1✔
385
  | Sub, Binop (ty, Sub, x, { node = Val v1; _ }), Val v2 ->
1✔
386
    let v = value (Eval.binop ty Add v1 v2) in
1✔
387
    raw_binop ty Sub x v
1✔
388
  | Mul, Val (Bitv bv), _ when Bitvector.eq_one bv -> hte2
×
389
  | Mul, _, Val (Bitv bv) when Bitvector.eq_one bv -> hte1
×
390
  | Mul, Binop (ty, Mul, x, { node = Val v1; _ }), Val v2 ->
1✔
391
    let v = value (Eval.binop ty Mul v1 v2) in
1✔
392
    raw_binop ty Mul x v
1✔
393
  | Add, Val v1, Binop (ty, Add, x, { node = Val v2; _ }) ->
1✔
394
    let v = value (Eval.binop ty Add v1 v2) in
1✔
395
    raw_binop ty Add v x
1✔
396
  | Mul, Val v1, Binop (ty, Mul, x, { node = Val v2; _ }) ->
1✔
397
    let v = value (Eval.binop ty Mul v1 v2) in
1✔
398
    raw_binop ty Mul v x
1✔
399
  | At, List es, Val (Int n) ->
1✔
400
    (* TODO: use another datastructure? *)
401
    begin match List.nth_opt es n with None -> assert false | Some v -> v
1✔
402
    end
403
  | (String_contains | String_prefix | String_suffix), _, Val (Str "") ->
1✔
404
    value True
405
  | List_cons, _, List es -> make (List (hte1 :: es))
1✔
406
  | List_append, List _, (List [] | Val (List [])) -> hte1
×
407
  | List_append, (List [] | Val (List [])), List _ -> hte2
×
408
  | List_append, List l0, Val (List l1) -> make (List (l0 @ List.map value l1))
1✔
409
  | List_append, Val (List l0), List l1 -> make (List (List.map value l0 @ l1))
×
410
  | List_append, List l0, List l1 -> make (List (l0 @ l1))
×
411
  | _ -> raw_binop ty op hte1 hte2
24✔
412

413
let raw_triop ty op e1 e2 e3 = make (Triop (ty, op, e1, e2, e3)) [@@inline]
×
414

415
let triop ty op e1 e2 e3 =
416
  match (op, view e1, view e2, view e3) with
7✔
417
  | Ty.Triop.Ite, Val True, _, _ -> e2
1✔
418
  | Ite, Val False, _, _ -> e3
1✔
419
  | Ite, _, _, _ when equal e2 e3 -> e2
1✔
420
  | op, Val v1, Val v2, Val v3 -> value (Eval.triop ty op v1 v2 v3)
4✔
421
  | Ite, _, Triop (_, Ite, c2, r1, r2), Triop (_, Ite, _, _, _) ->
×
422
    let else_ = raw_triop ty Ite e1 r2 e3 in
423
    let cond = binop Ty_bool And e1 c2 in
×
424
    raw_triop ty Ite cond r1 else_
×
425
  | _ -> raw_triop ty op e1 e2 e3
×
426

427
let raw_relop ty op hte1 hte2 = make (Relop (ty, op, hte1, hte2)) [@@inline]
5✔
428

429
let rec relop ty (op : Ty.Relop.t) hte1 hte2 =
430
  let both_phys_eq = phys_equal hte1 hte2 in
77✔
431
  let can_be_shortcuted =
77✔
432
    match ty with
433
    | Ty.Ty_bool | Ty_bitv _ | Ty_int | Ty_unit -> both_phys_eq
×
434
    | Ty_fp _ | Ty_app | Ty_list | Ty_real | Ty_regexp | Ty_roundingMode
×
435
    | Ty_none | Ty_str ->
×
436
      false
437
  in
438
  match (op, view hte1, view hte2) with
77✔
439
  | (Eq | Le | LeU), _, _ when can_be_shortcuted -> value True
6✔
440
  | (Ne | Lt | LtU), _, _ when can_be_shortcuted -> value False
6✔
441
  | op, Val v1, Val v2 -> value (if Eval.relop ty op v1 v2 then True else False)
21✔
442
  | Ne, Val (Real v), _ | Ne, _, Val (Real v) ->
×
443
    if Float.is_nan v || Float.is_infinite v then value True
×
444
    else if both_phys_eq then value False
×
445
    else raw_relop ty op hte1 hte2
×
446
  | _, Val (Real v), _ | _, _, Val (Real v) ->
×
447
    if Float.is_nan v || Float.is_infinite v then value False
×
448
    else
449
      (* TODO: it is possible to add a shortcut when `both_phys_eq` *)
450
      raw_relop ty op hte1 hte2
×
451
  | Eq, _, Val Nothing | Eq, Val Nothing, _ -> value False
×
452
  | Ne, _, Val Nothing | Ne, Val Nothing, _ -> value True
×
453
  | Eq, _, Val (App (`Op "symbol", [ Str _ ]))
×
454
  | Eq, Val (App (`Op "symbol", [ Str _ ])), _ ->
×
455
    value False
456
  | Ne, _, Val (App (`Op "symbol", [ Str _ ]))
×
457
  | Ne, Val (App (`Op "symbol", [ Str _ ])), _ ->
×
458
    value True
459
  | ( Eq
×
460
    , Symbol ({ ty = Ty_fp prec1; _ } as s1)
461
    , Symbol ({ ty = Ty_fp prec2; _ } as s2) )
462
    when both_phys_eq || (prec1 = prec2 && Symbol.equal s1 s2) ->
×
463
    raw_unop Ty_bool Not (raw_unop (Ty_fp prec1) Is_nan hte1)
×
464
  | Eq, Ptr { base = b1; offset = os1 }, Ptr { base = b2; offset = os2 } ->
1✔
465
    if both_phys_eq then value True
×
466
    else if Bitvector.equal b1 b2 then relop Ty_bool Eq os1 os2
×
467
    else value False
1✔
468
  | Ne, Ptr { base = b1; offset = os1 }, Ptr { base = b2; offset = os2 } ->
1✔
469
    if both_phys_eq then value False
×
470
    else if Bitvector.equal b1 b2 then relop Ty_bool Ne os1 os2
×
471
    else value True
1✔
472
  | ( (LtU | LeU)
1✔
473
    , Ptr { base = b1; offset = os1 }
474
    , Ptr { base = b2; offset = os2 } ) ->
475
    if both_phys_eq then value True
×
476
    else if Bitvector.equal b1 b2 then relop ty op os1 os2
×
477
    else
478
      let b1 = Value.Bitv b1 in
2✔
479
      let b2 = Value.Bitv b2 in
480
      value (if Eval.relop ty op b1 b2 then True else False)
1✔
481
  | ( op
2✔
482
    , Val (Bitv _ as n)
483
    , Ptr { base; offset = { node = Val (Bitv _ as o); _ } } ) ->
484
    let base = Eval.binop (Ty_bitv 32) Add (Bitv base) o in
485
    value (if Eval.relop ty op n base then True else False)
×
486
  | op, Ptr { base; offset = { node = Val (Bitv _ as o); _ } }, Val (Bitv _ as n)
2✔
487
    ->
488
    let base = Eval.binop (Ty_bitv 32) Add (Bitv base) o in
489
    value (if Eval.relop ty op base n then True else False)
×
490
  | op, List l1, List l2 -> relop_list op l1 l2
×
491
  | _, _, _ -> raw_relop ty op hte1 hte2
5✔
492

493
and relop_list op l1 l2 =
494
  match (op, l1, l2) with
×
495
  | Eq, [], [] -> value True
×
496
  | Eq, _, [] | Eq, [], _ -> value False
×
497
  | Eq, l1, l2 ->
×
498
    if not (List.compare_lengths l1 l2 = 0) then value False
×
499
    else
500
      List.fold_left2
×
501
        (fun acc a b ->
502
          binop Ty_bool And acc
×
503
          @@
504
          match (ty a, ty b) with
×
505
          | Ty_real, Ty_real -> relop Ty_real Eq a b
×
506
          | _ -> relop Ty_bool Eq a b )
×
507
        (value True) l1 l2
×
508
  | Ne, _, _ -> unop Ty_bool Not @@ relop_list Eq l1 l2
×
509
  | (Lt | LtU | Le | LeU), _, _ -> assert false
510

511
let raw_cvtop ty op hte = make (Cvtop (ty, op, hte)) [@@inline]
8✔
512

513
let rec cvtop theory op hte =
514
  match (op, view hte) with
39✔
515
  | Ty.Cvtop.String_to_re, _ -> raw_cvtop theory op hte
×
516
  | _, Val v -> value (Eval.cvtop theory op v)
23✔
517
  | ToBool, Cvtop (_, OfBool, hte) -> hte
1✔
518
  | OfBool, Cvtop (_, ToBool, hte) -> hte
×
519
  | String_to_float, Cvtop (Ty_real, ToString, hte) -> hte
×
520
  | Reinterpret_float, Cvtop (Ty_real, Reinterpret_int, e1) -> e1
×
521
  | Reinterpret_float, Cvtop (Ty_fp n, Reinterpret_int, e) ->
×
522
    assert (match theory with Ty_bitv m -> n = m | _ -> false);
×
523
    e
524
  | Reinterpret_int, Cvtop (Ty_int, Reinterpret_float, e1) -> e1
×
525
  | Reinterpret_int, Cvtop (Ty_bitv m, Reinterpret_float, e) ->
2✔
526
    assert (match theory with Ty_fp n -> n = m | _ -> false);
×
527
    e
528
  | Zero_extend n, Ptr { base; offset } ->
1✔
529
    let offset = cvtop theory op offset in
530
    make (Ptr { base = Bitvector.zero_extend n base; offset })
1✔
531
  | WrapI64, Ptr { base; offset } ->
1✔
532
    let offset = cvtop theory op offset in
533
    make (Ptr { base = Bitvector.extract base ~high:31 ~low:0; offset })
1✔
534
  | WrapI64, Cvtop (Ty_bitv 64, Zero_extend 32, hte) ->
×
535
    assert (Ty.equal theory (ty hte) && Ty.equal theory (Ty_bitv 32));
×
536
    hte
537
  | ToString, Cvtop (_, OfString, e1) -> e1
1✔
538
  | OfString, Cvtop (_, ToString, e1) -> e1
×
539
  | PromoteF32, Cvtop (_, DemoteF64, e1) -> e1
×
540
  | DemoteF64, Cvtop (_, PromoteF32, e1) -> e1
×
541
  | Zero_extend 0, _ -> hte
1✔
542
  | Sign_extend 0, _ -> hte
×
543
  | String_from_code, Cvtop (_, String_to_code, e1) -> e1
×
544
  | String_to_code, Cvtop (_, String_from_code, e1) -> e1
1✔
545
  | _ -> raw_cvtop theory op hte
8✔
546

547
let raw_naryop ty op es = make (Naryop (ty, op, es)) [@@inline]
×
548

549
let naryop ty op hexps =
550
  (* Get list of value or exit early *)
551
  let rec extract_values acc = function
7✔
552
    | [] -> Some (List.rev acc)
7✔
553
    | hexp :: remaining ->
18✔
554
      begin match view hexp with
555
      | Val v -> extract_values (v :: acc) remaining
18✔
NEW
556
      | _ -> None
×
557
      end
558
  in
559

560
  match extract_values [] hexps with
561
  | Some vlist -> value (Eval.naryop ty op vlist)
7✔
NEW
562
  | None ->
×
563
    begin match (ty, op) with
NEW
564
    | Ty_str, Concat ->
×
565
      let rec concat_exprs acc = function
NEW
566
        | [] -> List.rev acc
×
NEW
567
        | hexp :: remainig ->
×
568
          let acc =
569
            match view hexp with
NEW
570
            | Naryop (Ty_str, Concat, inner_hexps) ->
×
NEW
571
              List.rev_append inner_hexps acc
×
NEW
572
            | _ -> hexp :: acc
×
573
          in
574
          concat_exprs acc remainig
575
      in
NEW
576
      raw_naryop ty op (concat_exprs [] hexps)
×
NEW
577
    | _ -> raw_naryop ty op hexps
×
578
    end
579

580
let[@inline] raw_extract (hte : t) ~(high : int) ~(low : int) : t =
581
  make (Extract (hte, high, low))
7✔
582

583
let extract (hte : t) ~(high : int) ~(low : int) : t =
584
  match (view hte, high, low) with
12✔
585
  | Val (Bitv bv), high, low ->
3✔
586
    let high = (high * 8) - 1 in
587
    let low = low * 8 in
588
    value (Bitv (Bitvector.extract bv ~high ~low))
3✔
589
  | ( Cvtop
2✔
590
        ( _
591
        , (Zero_extend 24 | Sign_extend 24)
1✔
592
        , ({ node = Symbol { ty = Ty_bitv 8; _ }; _ } as sym) )
593
    , 1
594
    , 0 ) ->
595
    sym
596
  | Concat (_, e), h, l when Ty.size (ty e) = h - l -> e
2✔
597
  | Concat (e, _), 8, 4 when Ty.size (ty e) = 4 -> e
×
598
  | _ ->
5✔
599
    if high - low = Ty.size (ty hte) then hte else raw_extract hte ~high ~low
×
600

601
let raw_concat (msb : t) (lsb : t) : t = make (Concat (msb, lsb)) [@@inline]
2✔
602

603
(* TODO: don't rebuild so many values it generates unecessary hc lookups *)
604
let rec concat (msb : t) (lsb : t) : t =
605
  match (view msb, view lsb) with
6✔
606
  | Val (Bitv a), Val (Bitv b) -> value (Bitv (Bitvector.concat a b))
1✔
607
  | Val (Bitv _), Concat (({ node = Val (Bitv _); _ } as b), se) ->
×
608
    raw_concat (concat msb b) se
×
609
  | Extract (s1, h, m1), Extract (s2, m2, l) when equal s1 s2 && m1 = m2 ->
3✔
610
    if h - l = Ty.size (ty s1) then s1 else raw_extract s1 ~high:h ~low:l
1✔
611
  | Extract (_, _, _), Concat (({ node = Extract (_, _, _); _ } as e2), e3) ->
×
612
    raw_concat (concat msb e2) e3
×
613
  | _ -> raw_concat msb lsb
2✔
614

615
let rec simplify_expr ?(in_relop = false) (hte : t) : t =
4✔
616
  match view hte with
16✔
617
  | Val _ | Symbol _ -> hte
4✔
618
  | Ptr { base; offset } ->
×
619
    let offset = simplify_expr ~in_relop offset in
620
    if not in_relop then make (Ptr { base; offset })
×
621
    else binop (Ty_bitv 32) Add (value (Bitv base)) offset
×
622
  | List es -> make @@ List (List.map (simplify_expr ~in_relop) es)
×
623
  | App (x, es) -> make @@ App (x, List.map (simplify_expr ~in_relop) es)
×
624
  | Unop (ty, op, e) ->
×
625
    let e = simplify_expr ~in_relop e in
626
    unop ty op e
×
627
  | Binop (ty, op, e1, e2) ->
6✔
628
    let e1 = simplify_expr ~in_relop e1 in
629
    let e2 = simplify_expr ~in_relop e2 in
6✔
630
    binop ty op e1 e2
6✔
631
  | Relop (ty, op, e1, e2) ->
×
632
    let e1 = simplify_expr ~in_relop:true e1 in
633
    let e2 = simplify_expr ~in_relop:true e2 in
×
634
    relop ty op e1 e2
×
635
  | Triop (ty, op, c, e1, e2) ->
×
636
    let c = simplify_expr ~in_relop c in
637
    let e1 = simplify_expr ~in_relop e1 in
×
638
    let e2 = simplify_expr ~in_relop e2 in
×
639
    triop ty op c e1 e2
×
640
  | Cvtop (ty, op, e) ->
×
641
    let e = simplify_expr ~in_relop e in
642
    cvtop ty op e
×
643
  | Naryop (ty, op, es) ->
×
644
    let es = List.map (simplify_expr ~in_relop) es in
645
    naryop ty op es
×
646
  | Extract (s, high, low) ->
×
647
    let s = simplify_expr ~in_relop s in
648
    extract s ~high ~low
×
649
  | Concat (e1, e2) ->
×
650
    let msb = simplify_expr ~in_relop e1 in
651
    let lsb = simplify_expr ~in_relop e2 in
×
652
    concat msb lsb
×
653
  | Binder _ ->
×
654
    (* Not simplifying anything atm *)
655
    hte
656

657
module Cache = Hashtbl.Make (struct
658
  type nonrec t = t
659

660
  let hash = hash
661

662
  let equal = equal
663
end)
664

665
let simplify =
666
  (* TODO: it may make sense to share the cache with simplify_expr ? *)
667
  let cache = Cache.create 512 in
668
  fun e ->
4✔
669
    match Cache.find_opt cache e with
2✔
670
    | Some simplified -> simplified
×
671
    | None ->
2✔
672
      let rec loop x =
673
        let x' = simplify_expr x in
4✔
674
        if equal x x' then begin
2✔
675
          Cache.add cache e x';
676
          x'
2✔
677
        end
678
        else loop x'
2✔
679
      in
680
      loop e
681

682
module Bool = struct
683
  open Ty
684

685
  let of_val = function
686
    | Val True -> Some true
×
687
    | Val False -> Some false
×
688
    | _ -> None
×
689

690
  let true_ = value True
4✔
691

692
  let false_ = value False
4✔
693

694
  let to_val b = if b then true_ else false_
×
695

696
  let v b = to_val b [@@inline]
×
697

698
  let not b =
699
    let bexpr = view b in
×
700
    match of_val bexpr with
×
701
    | Some b -> to_val (not b)
×
702
    | None -> (
×
703
      match bexpr with
704
      | Unop (Ty_bool, Not, cond) -> cond
×
705
      | _ -> unop Ty_bool Not b )
×
706

707
  let equal b1 b2 =
708
    match (view b1, view b2) with
×
709
    | Val True, Val True | Val False, Val False -> true_
×
710
    | _ -> relop Ty_bool Eq b1 b2
×
711

712
  let distinct b1 b2 =
713
    match (view b1, view b2) with
×
714
    | Val True, Val False | Val False, Val True -> true_
×
715
    | _ -> relop Ty_bool Ne b1 b2
×
716

717
  let and_ b1 b2 =
718
    match (of_val (view b1), of_val (view b2)) with
×
719
    | Some true, _ -> b2
×
720
    | _, Some true -> b1
×
721
    | Some false, _ | _, Some false -> false_
×
722
    | _ -> binop Ty_bool And b1 b2
×
723

724
  let or_ b1 b2 =
725
    match (of_val (view b1), of_val (view b2)) with
×
726
    | Some false, _ -> b2
×
727
    | _, Some false -> b1
×
728
    | Some true, _ | _, Some true -> true_
×
729
    | _ -> binop Ty_bool Or b1 b2
×
730

731
  let implies a b = binop Ty_bool Implies a b
×
732

733
  let ite c r1 r2 = triop Ty_bool Ite c r1 r2
×
734
end
735

736
module Smtlib = struct
737
  let rec pp fmt (hte : t) =
738
    match view hte with
13✔
739
    | Val v -> Value.Smtlib.pp fmt v
3✔
740
    | Ptr _ -> assert false
741
    | Symbol s -> Fmt.pf fmt "@[<hov 1>%a@]" Symbol.pp s
5✔
742
    | List _ -> assert false
743
    | App _ -> assert false
744
    | Unop (ty, op, e) ->
×
745
      Fmt.pf fmt "@[<hov 1>(%a@ %a)@]" Ty.Smtlib.pp_unop (ty, op) pp e
746
    | Binop (ty, op, e1, e2) ->
2✔
747
      Fmt.pf fmt "@[<hov 1>(%a@ %a@ %a)@]" Ty.Smtlib.pp_binop (ty, op) pp e1 pp
748
        e2
749
    | Triop _ -> assert false
750
    | Relop (ty, op, e1, e2) ->
3✔
751
      Fmt.pf fmt "@[<hov 1>(%a@ %a@ %a)@]" Ty.Smtlib.pp_relop (ty, op) pp e1 pp
752
        e2
753
    | Cvtop _ -> assert false
754
    | Naryop _ -> assert false
755
    | Extract _ -> assert false
756
    | Concat _ -> assert false
757
    | Binder _ -> assert false
758
end
759

760
let inline_symbol_values map e =
761
  let rec aux e =
2✔
762
    match view e with
2✔
763
    | Val _ -> e
×
764
    | Symbol symbol ->
2✔
765
      begin match Symbol.Map.find_opt symbol map with
766
      | None -> e
1✔
767
      | Some v -> value v
1✔
768
      end
769
    | Ptr e ->
×
770
      let offset = aux e.offset in
771
      make @@ Ptr { e with offset }
×
772
    | List vs ->
×
773
      let vs = List.map aux vs in
774
      list vs
×
775
    | App (x, vs) ->
×
776
      let vs = List.map aux vs in
777
      app x vs
×
778
    | Unop (ty, op, v) ->
×
779
      let v = aux v in
780
      unop ty op v
×
781
    | Binop (ty, op, v1, v2) ->
×
782
      let v1 = aux v1 in
783
      let v2 = aux v2 in
×
784
      binop ty op v1 v2
×
785
    | Triop (ty, op, v1, v2, v3) ->
×
786
      let v1 = aux v1 in
787
      let v2 = aux v2 in
×
788
      let v3 = aux v3 in
×
789
      triop ty op v1 v2 v3
×
790
    | Cvtop (ty, op, v) ->
×
791
      let v = aux v in
792
      cvtop ty op v
×
793
    | Relop (ty, op, v1, v2) ->
×
794
      let v1 = aux v1 in
795
      let v2 = aux v2 in
×
796
      relop ty op v1 v2
×
797
    | Naryop (ty, op, vs) ->
×
798
      let vs = List.map aux vs in
799
      naryop ty op vs
×
800
    | Extract (e, high, low) ->
×
801
      let e = aux e in
802
      extract e ~high ~low
×
803
    | Concat (e1, e2) ->
×
804
      let e1 = aux e1 in
805
      let e2 = aux e2 in
×
806
      concat e1 e2
×
807
    | Binder (b, vars, e) ->
×
808
      let e = aux e in
809
      binder b vars e
×
810
  in
811
  aux e
812

813
module Set = struct
814
  include Set.Make (Key)
815

816
  type key = Key.t
817

818
  let hash = Hashtbl.hash
819

820
  let to_list s = elements s
×
821

822
  let pp fmt v =
823
    Fmt.pf fmt "@[<hov 1>%a@]"
×
824
      (Fmt.iter iter ~sep:(fun fmt () -> Fmt.pf fmt "@;") pp)
×
825
      v
826

827
  let get_symbols (set : t) =
828
    let tbl = Hashtbl.create 64 in
×
829
    let rec symbols hte =
×
830
      match view hte with
×
831
      | Val _ -> ()
×
832
      | Ptr { offset; _ } -> symbols offset
×
833
      | Symbol s -> Hashtbl.replace tbl s ()
×
834
      | List es -> List.iter symbols es
×
835
      | App (_, es) -> List.iter symbols es
×
836
      | Unop (_, _, e1) -> symbols e1
×
837
      | Binop (_, _, e1, e2) ->
×
838
        symbols e1;
839
        symbols e2
×
840
      | Triop (_, _, e1, e2, e3) ->
×
841
        symbols e1;
842
        symbols e2;
×
843
        symbols e3
×
844
      | Relop (_, _, e1, e2) ->
×
845
        symbols e1;
846
        symbols e2
×
847
      | Cvtop (_, _, e) -> symbols e
×
848
      | Naryop (_, _, es) -> List.iter symbols es
×
849
      | Extract (e, _, _) -> symbols e
×
850
      | Concat (e1, e2) ->
×
851
        symbols e1;
852
        symbols e2
×
853
      | Binder (_, vars, e) ->
×
854
        List.iter symbols vars;
855
        symbols e
×
856
    in
857
    iter symbols set;
858
    Hashtbl.fold (fun k () acc -> k :: acc) tbl []
×
859

860
  let map f set =
861
    fold
×
862
      (fun elt set ->
863
        let elt = f elt in
×
864
        add elt set )
×
865
      set empty
866

867
  let inline_symbol_values symbol_map set =
868
    map (inline_symbol_values symbol_map) set
×
869
end
870

871
let rec split_conjunctions (e : t) : Set.t =
872
  match view e with
×
873
  | Binop (Ty_bool, And, e1, e2) ->
×
874
    let s1 = split_conjunctions e1 in
875
    let s2 = split_conjunctions e2 in
×
876
    Set.union s1 s2
×
877
  | _ -> Set.singleton e
×
878

879
let rec split_disjunctions (e : t) : Set.t =
880
  match view e with
×
881
  | Binop (Ty_bool, Or, e1, e2) ->
×
882
    let s1 = split_disjunctions e1 in
883
    let s2 = split_disjunctions e2 in
×
884
    Set.union s1 s2
×
885
  | _ -> Set.singleton e
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc