package herdtools7

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file StaticModel.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
open AST
open ASTUtils
open Error

exception NotSupported

(*---------- Symbolic representation ----------*)

module Monomial : sig
  type t

  val compare : t -> t -> int
  val one : t
  val single_term : identifier -> t
  val mult : t -> t -> t
  val divide : t -> t -> t

  val to_scaled_expr : t -> Q.t -> expr
  (** Constructs an expression for [factor] * [monos]. *)

  val pp_with_factor : Format.formatter -> t * Q.t -> unit
end = struct
  module AtomMap = Map.Make (String)
  (** A map from identifiers. *)

  type t = int AtomMap.t
  (** A unitary monomial.
      They are unitary in the sense that they do not have any factors:
      {m 3 \times X^2 } is not unitary, while {m x^2 } is.

      Maps each variable to its exponent.
      For example: {m X^2 + Y^4 } represented by {m X \to 2, Y \to 4 },
      and {m 1 } is represented by the empty map.

      Invariant: all integer exponents are strictly positive. *)

  let compare = AtomMap.compare Int.compare
  let one = AtomMap.empty
  let single_term atom = AtomMap.singleton atom 1

  let mult mono1 mono2 =
    AtomMap.union
      (fun _ p1 p2 ->
        assert (p1 > 0 && p2 > 0);
        Some (p1 + p2))
      mono1 mono2

  let divide mono1 mono2 =
    let divide_unitary a1 a2 =
      match (a1, a2) with
      | _, None -> a1
      | Some p1, Some p2 when p1 > p2 -> Some (p1 - p2) (* not currently used *)
      | Some p1, Some p2 when p1 = p2 -> None
      | _ -> raise NotSupported
    in
    AtomMap.merge (fun _ -> divide_unitary) mono1 mono2

  let to_scaled_expr monos factor =
    let start = expr_of_z (Q.num factor) in
    let numerator =
      AtomMap.fold
        (fun atom exponent acc -> mul_expr acc (pow_expr (var_ atom) exponent))
        monos start
    in
    div_expr numerator (Q.den factor)

  let pp_with_factor f (monos, factor) =
    let open Format in
    if AtomMap.is_empty monos then Q.pp_print f factor
    else (
      pp_open_hbox f ();
      let pp_sep f () = fprintf f "@ \u{d7} " in
      if Q.equal factor Q.one then ()
      else (
        Q.pp_print f factor;
        pp_sep f ());
      PP.pp_print_seq ~pp_sep
        (fun f (x, p) ->
          pp_print_string f x;
          match p with
          | 1 -> ()
          | 2 -> pp_print_string f "\u{b2}"
          | _ -> fprintf f "^%d" p)
        f (AtomMap.to_seq monos);
      pp_close_box f ())
end

module Polynomial : sig
  type t

  val compare : t -> t -> int
  val single_term : Monomial.t -> Q.t -> t
  val to_mono : t -> (Monomial.t * Q.t) option
  val scale : Q.t -> t -> t
  val neg : t -> t
  val add : t -> t -> t
  val mult : t -> t -> t
  val divide_by_term : t -> Q.t -> Monomial.t -> t
  val extract_constant_term : t -> Q.t * t
  val is_constant : t -> Q.t option
  val to_expr : t -> expr
  val pp : Format.formatter -> t -> unit
end = struct
  module MonomialMap = Map.Make (Monomial)
  (** A map from a monomial. *)

  type t = Q.t MonomialMap.t
  (** A polynomial.
      Maps each monomial to its factor.
      For example, {m X^2 - X + 4 } is represented by
      {m X^2 \to 1, X \to -1, 1 \to 4 } *)

  let compare = MonomialMap.compare Q.compare

  let single_term mono factor =
    if Q.equal factor Q.zero then MonomialMap.empty
    else MonomialMap.singleton mono factor

  let to_mono poly =
    if MonomialMap.cardinal poly = 1 then Some (MonomialMap.choose poly)
    else None

  let add poly1 poly2 =
    MonomialMap.union
      (fun _ c1 c2 ->
        let coeff = Q.add c1 c2 in
        if Q.equal coeff Q.zero then None else Some coeff)
      poly1 poly2

  let scale factor poly =
    assert (factor <> Q.zero);
    MonomialMap.map (Q.mul factor) poly

  let termwise f poly =
    MonomialMap.fold
      (fun mono factor -> add (f factor mono))
      poly MonomialMap.empty

  let mult_mono poly factor mono =
    termwise
      (fun f m -> single_term (Monomial.mult m mono) (Q.mul f factor))
      poly

  let neg poly = MonomialMap.map Q.neg poly
  let mult poly1 poly2 = termwise (mult_mono poly1) poly2

  let divide_by_term poly factor mono =
    termwise
      (fun f m -> single_term (Monomial.divide m mono) (Q.div f factor))
      poly

  let extract_constant_term poly =
    let c = try MonomialMap.find Monomial.one poly with Not_found -> Q.zero
    and p = MonomialMap.remove Monomial.one poly in
    (c, p)

  let is_constant poly =
    if MonomialMap.is_empty poly then Some Q.zero
    else if MonomialMap.cardinal poly = 1 then
      MonomialMap.find_opt Monomial.one poly
    else None

  let to_expr poly =
    List.fold_left
      (fun acc (m, c) ->
        if ASTUtils.expr_equal (fun _ _ -> false) acc zero_expr then
          Monomial.to_scaled_expr m c
        else
          let e_m = Monomial.to_scaled_expr m (Q.abs c) in
          add_expr acc (Q.sign c, e_m))
      zero_expr
      (MonomialMap.bindings poly |> List.rev)

  let pp f poly =
    let open Format in
    if MonomialMap.is_empty poly then pp_print_string f "0"
    else (
      pp_open_hvbox f 2;
      let pp_sep f () = fprintf f "@ + " in
      PP.pp_print_seq ~pp_sep Monomial.pp_with_factor f
        (MonomialMap.to_seq poly);
      pp_close_box f ())
end

module Conjunction : sig
  type eq = Zero | NonZero
  type t

  val empty : t
  val is_bottom : t -> bool
  val is_empty : t -> bool
  val of_bool : bool -> t
  val single_conjunct : Polynomial.t -> eq -> t
  val conj : t -> t -> t
  val to_expr : t -> expr option

  type triviality = TriviallyTrue | TriviallyFalse | NonTrivial

  val get_triviality : t -> triviality
  val reduce : t -> t
  val pp : Format.formatter -> t -> unit
end = struct
  type eq = Zero | NonZero  (** A (in)equation for a numerical value. *)

  let satisfies_eq q eq =
    let eq_zero = Q.equal q Q.zero in
    match eq with Zero -> eq_zero | NonZero -> not eq_zero

  let pp_eq f s =
    let s = match s with Zero -> "= 0" | NonZero -> "!= 0" in
    Format.pp_print_string f s

  let eq_to_op = function Zero -> EQ_OP | NonZero -> NEQ

  module PolynomialMap = Map.Make (Polynomial)
  (** Map from polynomials. *)

  type t = eq PolynomialMap.t option
  (** A conjunctive logical formula with polynomials.

      We use the [option] to represent falsity as [None].  [Some map] is then a
      conjunction of constraints on polynomials, as dictated by [map].  For
      example, {m X^2 = 0} is represented with {m Some (X^2 \to Zero)}.
  *)

  let is_bottom = function None -> true | Some _ -> false

  let is_empty = function
    | None -> false
    | Some map -> PolynomialMap.is_empty map

  let empty = Some PolynomialMap.empty
  let of_bool b = if b then empty else None
  let single_conjunct p eq = Some (PolynomialMap.singleton p eq)

  let conj c1 c2 =
    let exception BottomInterrupt in
    let eq_and eq1 eq2 =
      if eq1 = eq2 then eq1 else raise_notrace BottomInterrupt
    in
    match (c1, c2) with
    | None, _ | _, None -> None
    | Some cjs1, Some cjs2 -> (
        try
          Some
            (PolynomialMap.union
               (fun _ eq1 eq2 -> Some (eq_and eq1 eq2))
               cjs1 cjs2)
        with BottomInterrupt -> None)

  let to_expr =
    let one_to_expr poly eq =
      let c, p = Polynomial.extract_constant_term poly in
      binop (eq_to_op eq) (expr_of_rational (Q.neg c)) (Polynomial.to_expr p)
    in
    Option.map (fun map ->
        PolynomialMap.fold
          (fun p eq e -> conj_expr (one_to_expr p eq) e)
          map (literal (L_Bool true)))

  let is_true p eq =
    match Polynomial.is_constant p with
    | Some q -> satisfies_eq q eq
    | None -> false

  let is_false p eq =
    match Polynomial.is_constant p with
    | Some q -> not (satisfies_eq q eq)
    | None -> false

  let reduce = function
    | None -> None
    | Some cjs ->
        let non_trivial =
          PolynomialMap.filter (fun p s -> not (is_true p s)) cjs
        in
        if PolynomialMap.exists is_false non_trivial then None
        else Some non_trivial

  type triviality = TriviallyTrue | TriviallyFalse | NonTrivial

  let get_triviality = function
    | None -> TriviallyFalse
    | Some cjs ->
        if PolynomialMap.for_all is_true cjs then TriviallyTrue
        else if PolynomialMap.exists is_false cjs then TriviallyFalse
        else NonTrivial

  let pp f =
    let open Format in
    let pp_one f (p, s) = fprintf f "@[<h>%a@ %a@]" Polynomial.pp p pp_eq s in
    function
    | None -> pp_print_string f "\u{22a5}"
    | Some m ->
        if PolynomialMap.is_empty m then pp_print_string f "\u{22a4}"
        else
          let pp_sep f () = fprintf f "@ \u{2227} " in
          fprintf f "@[<hov 2>%a@]"
            (PP.pp_print_seq ~pp_sep pp_one)
            (PolynomialMap.to_seq m)
end

module IR : sig
  type t

  val of_var : identifier -> t
  val of_int : Z.t -> t
  val combine : t -> t -> t

  val cross_combine :
    (Polynomial.t -> Polynomial.t -> Polynomial.t) -> t -> t -> t

  val map : (Polynomial.t -> Polynomial.t) -> t -> t
  val restrict : Conjunction.t list -> t -> t
  val to_conjuncts : Conjunction.eq -> t -> Conjunction.t list
  val to_expr : t -> expr
  val reduce : t -> t
  val equal_mod_branches : t -> t -> bool
  val pp : Format.formatter -> t -> unit
end = struct
  type t = (Conjunction.t * Polynomial.t) list
  (** Case disjunctions: constrained polynomials.
      This is a branched tree of polynomials.
  *)
  (* Wanted invariants for (e : IR.t) :
     ∀ {c | (c, d) ∈ e } <=> true                           (I₂)
     ∀ (cᵢ, eᵢ), (cⱼ, eⱼ) ∈ e, i != j => cⱼ ∩ cⱼ = ∅        (I₃)
  *)

  let always e = [ (Conjunction.of_bool true, e) ]
  let of_var s = Polynomial.single_term (Monomial.single_term s) Q.one |> always
  let of_int i = Polynomial.single_term Monomial.one (Q.of_bigint i) |> always
  let combine = ( @ )

  let cross_combine f =
    let on_pair (cjs1, e1) (cjs2, e2) = (Conjunction.conj cjs1 cjs2, f e1 e2) in
    ASTUtils.list_cross on_pair

  let map f = List.map (fun (cj, e) -> (cj, f e))

  let restrict cjs ir =
    let restrict_one cjs (cjs', p) = (Conjunction.conj cjs cjs', p) in
    ASTUtils.list_cross restrict_one cjs ir

  let to_conjuncts eq ir =
    List.map
      (fun (cjs, p) -> Conjunction.conj (Conjunction.single_conjunct p eq) cjs)
      ir

  let to_expr = function
    | [] -> zero_expr
    | [ (cjs, p) ] ->
        assert (Conjunction.is_empty cjs);
        Polynomial.to_expr p
    | map ->
        let cannot_happen_expr = zero_expr in
        List.fold_left
          (fun e (cjs, p) ->
            match Conjunction.to_expr cjs with
            | None -> e
            | Some condition -> cond_expr condition (Polynomial.to_expr p) e)
          cannot_happen_expr (List.rev map)

  let reduce ir =
    ir
    |> List.filter_map (fun (cjs, poly) ->
           let cjs = Conjunction.reduce cjs in
           if Conjunction.is_bottom cjs then None else Some (cjs, poly))
    |> fun ir ->
    List.fold_right
      (fun (cjs, poly) acc ->
        match Conjunction.get_triviality cjs with
        | TriviallyTrue -> [ (Conjunction.empty, poly) ]
        | TriviallyFalse -> acc
        | NonTrivial -> (cjs, poly) :: acc)
      ir []

  let equal_mod_branches ir1 ir2 =
    let to_cond (cjs1, poly1) (cjs2, poly2) =
      let equality =
        let poly = Polynomial.add poly1 (Polynomial.neg poly2) in
        Conjunction.single_conjunct poly Zero
      in
      let cjs = Conjunction.conj cjs1 cjs2 in
      if Conjunction.is_bottom cjs then Conjunction.empty
      else
        let equality = Conjunction.reduce equality in
        let () =
          if false then Format.eprintf "@[Gave %a@.@]" Conjunction.pp equality
        in
        equality
    in
    ASTUtils.list_cross to_cond ir1 ir2
    |> List.for_all (fun cjs ->
           if Conjunction.is_bottom cjs then false else Conjunction.is_empty cjs)

  let pp f li =
    let open Format in
    let pp_one f (cjs, poly) =
      Format.fprintf f "@[<2>%a@ -> %a@]" Conjunction.pp cjs Polynomial.pp poly
    in
    fprintf f "@[<v 2>%a@]" (pp_print_list ~pp_sep:pp_print_space pp_one) li
end

(*---------- Converting expressions to symbolic representation ----------*)

let rec make_anonymous (env : StaticEnv.env) (ty : ty) : ty =
  match ty.desc with
  | T_Named x -> (
      match IMap.find_opt x env.global.declared_types with
      | Some (ty', _) -> make_anonymous env ty'
      | None -> fatal_from ty (Error.UndefinedIdentifier x))
  | _ -> ty

(* Begin ToIR *)
let rec to_ir env (e : expr) =
  let of_lit = function L_Int i -> IR.of_int i | _ -> raise NotSupported in
  match e.desc with
  | E_Literal (L_Int i) -> IR.of_int i
  | E_Var s -> (
      try StaticEnv.lookup_constants env s |> of_lit
      with Not_found -> (
        try StaticEnv.lookup_immutable_expr env s |> to_ir env
        with Not_found | NotSupported -> (
          let t =
            try StaticEnv.type_of env s
            with Not_found -> Error.fatal_from e (UndefinedIdentifier s)
          in
          let ty1 = make_anonymous env t in
          match ty1.desc with
          | T_Int (WellConstrained [ Constraint_Exact e ]) -> to_ir env e
          | T_Int _ -> IR.of_var s
          | _ -> raise NotSupported)))
  | E_Binop (PLUS, e1, e2) ->
      let ir1 = to_ir env e1 and ir2 = to_ir env e2 in
      IR.cross_combine Polynomial.add ir1 ir2
  | E_Binop (MINUS, e1, e2) ->
      let e2 = E_Unop (NEG, e2) |> ASTUtils.add_pos_from_st e2 in
      E_Binop (PLUS, e1, e2) |> ASTUtils.add_pos_from_st e |> to_ir env
  | E_Binop (MUL, { desc = E_Binop (DIV, e1, e2); _ }, e3) ->
      to_ir env (binop DIV (binop MUL e1 e3) e2)
  | E_Binop (MUL, e1, { desc = E_Binop (DIV, e2, e3); _ }) ->
      to_ir env (binop DIV (binop MUL e1 e2) e3)
  | E_Binop (MUL, e1, e2) ->
      let ir1 = to_ir env e1 and ir2 = to_ir env e2 in
      IR.cross_combine Polynomial.mult ir1 ir2
  | E_Binop (DIV, e1, { desc = E_Literal (L_Int i2); _ }) ->
      let ir1 = to_ir env e1 and f2 = Q.(Z.one /// i2) in
      IR.map (Polynomial.scale f2) ir1
  | E_Binop (DIV, e1, e2) ->
      let ir1 = to_ir env e1 and ir2 = to_ir env e2 in
      IR.cross_combine
        (fun poly1 poly2 ->
          match Polynomial.to_mono poly2 with
          | Some (mono, factor) -> Polynomial.divide_by_term poly1 factor mono
          | None -> raise NotSupported)
        ir1 ir2
  | E_Binop (SHL, e1, { desc = E_Literal (L_Int i2); _ }) when Z.leq Z.zero i2
    ->
      let ir1 = to_ir env e1
      and f2 = Z.to_int i2 |> Z.shift_left Z.one |> Q.of_bigint in
      IR.map (Polynomial.scale f2) ir1
  | E_Binop (op, { desc = E_Literal l1; _ }, { desc = E_Literal l2; _ }) ->
      Operations.binop_values e Error.Static op l1 l2 |> of_lit
  | E_Unop (NEG, e0) -> IR.map Polynomial.neg (to_ir env e0)
  | E_Cond (cond, e1, e2) ->
      let cjs, neg_cjs = to_cond env cond
      and ir1 = to_ir env e1
      and ir2 = to_ir env e2 in
      let ir1' = IR.restrict cjs ir1 and ir2' = IR.restrict neg_cjs ir2 in
      IR.combine ir1' ir2'
  | E_ATC (e', _) -> to_ir env e'
  | _ -> raise NotSupported
(* End *)

and to_cond env (e : expr) : Conjunction.t list * Conjunction.t list =
  let ( ||| ) = ( @ ) and ( &&& ) = ASTUtils.list_cross Conjunction.conj in
  match e.desc with
  | E_Literal (L_Bool b) ->
      ([ Conjunction.of_bool b ], [ Conjunction.of_bool (not b) ])
  | E_Binop (BAND, e1, e2) ->
      let cjs1, neg_cjs1 = to_cond env e1 and cjs2, neg_cjs2 = to_cond env e2 in
      (cjs1 &&& cjs2, neg_cjs1 ||| neg_cjs2)
  | E_Binop (BOR, e1, e2) ->
      let cjs1, neg_cjs1 = to_cond env e1 and cjs2, neg_cjs2 = to_cond env e2 in
      (cjs1 ||| cjs2, neg_cjs1 &&& neg_cjs2)
  | E_Binop (EQ_OP, e1, e2) ->
      let e' = E_Binop (MINUS, e1, e2) |> ASTUtils.add_pos_from_st e in
      let ir = to_ir env e' in
      (IR.to_conjuncts Zero ir, IR.to_conjuncts NonZero ir)
  | E_Cond (cond, e1, e2) ->
      let cjs_cond, neg_cjs_cond = to_cond env cond
      and cjs1, neg_cjs1 = to_cond env e1
      and cjs2, neg_cjs2 = to_cond env e2 in
      ( cjs_cond &&& cjs1 ||| (neg_cjs_cond &&& cjs2),
        neg_cjs_cond ||| neg_cjs1 &&& (cjs_cond ||| neg_cjs2) )
  | _ -> raise NotSupported

(*---------- Solving in the symbolic representation  ----------*)

(* Begin Normalize *)
let normalize env e =
  let { desc } = e |> to_ir env |> IR.reduce |> IR.to_expr in
  add_pos_from e desc
(* End *)

let try_normalize env e =
  try normalize env e with Error.ASLException _ | NotSupported -> e

let normalize_opt env e =
  try Some (normalize env e) with Error.ASLException _ | NotSupported -> None

let equal_in_env env e1 e2 =
  let dbg = false in
  let () =
    if dbg then
      Format.eprintf "@[<hv 2>Are %a@ and %a@ equal?@]@ " PP.pp_expr e1
        PP.pp_expr e2
  in
  try
    let ir1 = to_ir env e1 |> IR.reduce and ir2 = to_ir env e2 |> IR.reduce in
    let () =
      if dbg then
        Format.eprintf "@[Reducing them to@ %a@ and %a.@]@ " IR.pp ir1 IR.pp ir2
    in
    let res = IR.equal_mod_branches ir1 ir2 in
    let () =
      if dbg then if res then Format.eprintf "YES@." else Format.eprintf "NO@."
    in
    res
  with NotSupported ->
    let () = if dbg then Format.eprintf "Cannot answer this question yet." in
    false

let normalize_to_bool_opt env e =
  try
    let cond, _ncond = to_cond env e in
    if List.exists (fun c -> Conjunction.get_triviality c = TriviallyTrue) cond
    then Some true
    else if
      List.for_all (fun c -> Conjunction.get_triviality c = TriviallyFalse) cond
    then Some false
    else None
  with NotSupported -> None

(* Begin ReduceToZOpt *)
let reduce_to_z_opt env e =
  match (try_normalize env e).desc with
  | E_Literal (L_Int z) -> Some z
  | _ -> None
(* End *)
OCaml

Innovation. Community. Security.