From 0ccefe73261103f6dc4bd3b4612a2e90613e2a9e Mon Sep 17 00:00:00 2001 From: Michael Sammler <noreply@sammler.me> Date: Thu, 15 Apr 2021 15:58:49 +0200 Subject: [PATCH] WIP bitvector library --- _CoqProject | 1 + theories/bitvector.v | 429 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 430 insertions(+) create mode 100644 theories/bitvector.v diff --git a/_CoqProject b/_CoqProject index 77e45b94..faa3feb3 100644 --- a/_CoqProject +++ b/_CoqProject @@ -47,3 +47,4 @@ theories/nat_cancel.v theories/namespaces.v theories/telescopes.v theories/binders.v +theories/bitvector.v diff --git a/theories/bitvector.v b/theories/bitvector.v new file mode 100644 index 00000000..1684ed61 --- /dev/null +++ b/theories/bitvector.v @@ -0,0 +1,429 @@ +From stdpp Require Export numbers. +From stdpp Require Import countable finite. +From stdpp Require Import options. + +Local Open Scope Z_scope. + +Lemma Z_bounded_iff_bits_nonneg k n : + 0 ≤ k → 0 ≤ n → + n < 2^k ↔ ∀ l, k ≤ l → Z.testbit n l = false. +Proof. + intros. + destruct (decide (n = 0)) as [->|]. + - setoid_rewrite Z.bits_0. split; [done|]. intros. by apply Z.pow_pos_nonneg. + - split. + + intros Hb%Z.log2_lt_pow2 l Hl; [| lia]. apply Z.bits_above_log2; lia. + + intros Hl. + destruct (decide (n < 2^k)); [done|]. + assert (Z.testbit n (Z.log2 n) = false) as Hbit. + { apply Hl, Z.log2_le_pow2; lia. } + rewrite Z.bit_log2 in Hbit; [done| lia]. +Qed. + +Tactic Notation "learn_hyp" constr(p) "as" ident(H') := + let P := type of p in + match goal with + | H : P |- _ => fail 1 + | _ => pose proof p as H' + end. +Tactic Notation "learn_hyp" constr(p) := + let H := fresh in learn_hyp p as H. + + +(*** Preliminary definitions *) +Definition bv_modulus (n : N) : Z := 2 ^ (Z.of_N n). +Definition bv_half_modulus (n : N) : Z := 2 ^ (Z.of_N (n - 1)). +Definition bv_wrap (n : N) (z : Z) : Z := z `mod` bv_modulus n. +Definition bv_swrap (n : N) (z : Z) : Z := bv_wrap n (z + bv_half_modulus n) - bv_half_modulus n. +Definition bv_ok (n : N) (z : Z) : Prop := -1 < z < bv_modulus n. + +Lemma bv_modulus_pos n : + 0 < bv_modulus n. +Proof. apply Z.pow_pos_nonneg; lia. Qed. + +Lemma bv_modulus_add n1 n2 : + bv_modulus (n1 + n2) = bv_modulus n1 * bv_modulus n2. +Proof. unfold bv_modulus. rewrite N2Z.inj_add. eapply Z.pow_add_r; lia. Qed. + +Lemma bv_ok_in_range n z: + bv_ok n z ↔ 0 ≤ z < bv_modulus n. +Proof. unfold bv_ok. lia. Qed. + +Lemma bv_wrap_ok n z : + bv_ok n (bv_wrap n z). +Proof. + unfold bv_wrap. + pose proof (Z.mod_pos_bound z (bv_modulus n)) as [??]. { apply bv_modulus_pos. } + split; lia. +Qed. + +Lemma bv_wrap_elim n z : + 0 ≤ z < bv_modulus n → bv_wrap n z = z. +Proof. intros. by apply Z.mod_small. Qed. + +Lemma bv_ok_bitwise_op {n} op bop n1 n2 : + (∀ k, Z.testbit (op n1 n2) k = bop (Z.testbit n1 k) (Z.testbit n2 k)) → + (0 ≤ n1 → 0 ≤ n2 → 0 ≤ op n1 n2) → + bop false false = false → + bv_ok n n1 → bv_ok n n2 → bv_ok n (op n1 n2). +Proof. + intros Hbits Hnonneg Hop [? Hok1]%bv_ok_in_range [? Hok2]%bv_ok_in_range. apply bv_ok_in_range. + split; [lia|]. + apply Z_bounded_iff_bits_nonneg; [lia..|]. intros l ?. + eapply Z_bounded_iff_bits_nonneg in Hok1;[|try done; lia..]. + eapply Z_bounded_iff_bits_nonneg in Hok2;[|try done; lia..]. + by rewrite Hbits, Hok1, Hok2. +Qed. + +(*** Definition of [bv n] *) +Record bv (n : N) := BV { + bv_unsigned : Z; + bv_is_ok : bv_ok n bv_unsigned; +}. +Global Arguments bv_unsigned {_}. +Global Arguments bv_is_ok {_}. + +Definition bv_signed {n} (b : bv n) := bv_swrap n (bv_unsigned b). + +Lemma bv_eq n (b1 b2 : bv n) : + b1 = b2 ↔ b1.(bv_unsigned) = b2.(bv_unsigned). +Proof. + destruct b1, b2. split; simpl; [ naive_solver|]. + intros. subst. f_equal. apply proof_irrel. +Qed. + +Global Program Instance bv_eq_dec n : EqDecision (bv n) := λ '(BV _ v1 p1) '(BV _ v2 p2), + match decide (v1 = v2) with + | left eqv => left _ + | right eqv => right _ + end. +Next Obligation. intros. subst. rewrite (proof_irrel p1 p2). exact eq_refl. Defined. +Next Obligation. intros. by injection. Qed. + +Global Instance bv_countable n : Countable (bv n). +Proof. + refine (inj_countable bv_unsigned + (λ x, match decide (bv_ok n x) with | left b => Some (BV n x b) | _ => None end) _). + intros x. case_decide; [ f_equal; by apply bv_eq|]. + by pose proof bv_is_ok x. +Qed. + +Definition bv_of_Z_checked (n : N) (z : Z) : option (bv n) := + match decide (bv_ok n z) with + | left Heq => Some (BV n z Heq) + | right _ => None + end. + +Program Definition bv_of_Z (n : N) (z : Z) : bv n := + BV n (bv_wrap n z) _. +Next Obligation. apply bv_wrap_ok. Qed. + +Lemma bv_of_Z_unsigned n z: + bv_unsigned (bv_of_Z n z) = bv_wrap n z. +Proof. done. Qed. + +Global Arguments bv_of_Z : simpl never. +Global Opaque bv_of_Z. + +Lemma bv_of_Z_elim n z: + 0 ≤ z < bv_modulus n → + bv_unsigned (bv_of_Z n z) = z. +Proof. rewrite bv_of_Z_unsigned. apply bv_wrap_elim. Qed. + +Lemma bv_in_range n (b : bv n): + 0 ≤ bv_unsigned b < bv_modulus n. +Proof. apply bv_ok_in_range. apply bv_is_ok. Qed. + +Global Program Instance bv_finite n : Finite (bv n) := + {| enum := bv_of_Z n <$> (seqZ 0 (bv_modulus n)) |}. +Next Obligation. + intros n. apply NoDup_alt. intros i j x. + rewrite !list_lookup_fmap. + intros [? [[??]%lookup_seqZ ?]]%fmap_Some. + intros [? [[??]%lookup_seqZ Hz]]%fmap_Some. subst. + apply bv_eq in Hz. rewrite !bv_of_Z_elim in Hz; lia. +Qed. +Next Obligation. + intros. apply elem_of_list_lookup. eexists (Z.to_nat (bv_unsigned x)). + rewrite list_lookup_fmap. apply fmap_Some. eexists _. + pose proof (bv_in_range _ x). split. + - apply lookup_seqZ. split; [done|]. rewrite Z2Nat.id; lia. + - apply bv_eq. rewrite bv_of_Z_elim; rewrite Z2Nat.id; lia. +Qed. + + +(*** Notation for [bv n] *) +Ltac solve_bitvector_eq := + try (vm_compute; done); + lazymatch goal with + | |- -1 < ?v < 2 ^ ?l => + fail "Bitvector constant" v "does not fit into" l "bits" + end. + +Notation "'[BV{' l } v ]" := (BV l v _) (at level 9, only printing) : stdpp_scope. +(* TODO: Somehow the following version creates a warning. *) +(* Notation "'[BV{' l } v ]" := (BV l v _) (at level 9, format "[BV{ l } v ]", only printing) : stdpp_scope. *) +(* TODO: This notation breaks when used in gmap notations. Why? *) +Notation "'[BV{' l } v ]" := (BV l v ltac:(solve_bitvector_eq)) (at level 9, only parsing) : stdpp_scope. + +(*** Automation *) +Ltac bv_saturate := + repeat match goal with | b : bv _ |- _ => learn_hyp (bv_in_range _ b) end. + + +(*** Operations on [bv n] *) +Program Definition bv_0 (n : N) := + BV n 0 _. +Next Obligation. + intros n. apply bv_ok_in_range. split; [done| apply bv_modulus_pos]. +Qed. + +Definition bv_succ {n} (x : bv n) : bv n := + bv_of_Z n (Z.succ (bv_unsigned x)). +Definition bv_pred {n} (x : bv n) : bv n := + bv_of_Z n (Z.pred (bv_unsigned x)). + +Definition bv_add {n} (x y : bv n) : bv n := (* SMT: bvadd *) + bv_of_Z n (Z.add (bv_unsigned x) (bv_unsigned y)). +Definition bv_sub {n} (x y : bv n) : bv n := (* SMT: bvsub *) + bv_of_Z n (Z.sub (bv_unsigned x) (bv_unsigned y)). +Definition bv_opp {n} (x y : bv n) : bv n := (* SMT: bvneg *) + bv_of_Z n (Z.opp (bv_unsigned x)). + +Definition bv_mul {n} (x y : bv n) : bv n := (* SMT: bvmul *) + bv_of_Z n (Z.mul (bv_unsigned x) (bv_unsigned y)). +Program Definition bv_divu {n} (x y : bv n) : bv n := (* SMT: bvudiv *) + BV n (Z.div (bv_unsigned x) (bv_unsigned y)) _. +Next Obligation. + intros n x y. apply bv_ok_in_range. bv_saturate. + destruct (decide (bv_unsigned y = 0)) as [->|?]. + { rewrite Zdiv_0_r. lia. } + split; [ apply Z.div_pos; lia |]. + apply (Z.le_lt_trans _ (bv_unsigned x)); [|lia]. + apply Z.div_le_upper_bound; [ lia|]. nia. +Qed. +Program Definition bv_modu {n} (x y : bv n) : bv n := (* SMT: bvurem *) + BV n (Z.modulo (bv_unsigned x) (bv_unsigned y)) _. +Next Obligation. + intros n x y. apply bv_ok_in_range. bv_saturate. + destruct (decide (bv_unsigned y = 0)) as [->|?]. + { rewrite Zmod_0_r. lia. } + split; [ apply Z_mod_pos; lia |]. + apply (Z.le_lt_trans _ (bv_unsigned x)); [|lia]. + apply Z.mod_le; lia. +Qed. +Definition bv_divs {n} (x y : bv n) : bv n := + bv_of_Z n (Z.div (bv_signed x) (bv_signed y)). +Definition bv_quots {n} (x y : bv n) : bv n := (* SMT: bvsdiv *) + bv_of_Z n (Z.quot (bv_signed x) (bv_signed y)). +Definition bv_mods {n} (x y : bv n) : bv n := (* SMT: bvsmod *) + bv_of_Z n (Z.modulo (bv_signed x) (bv_signed y)). +Definition bv_rems {n} (x y : bv n) : bv n := (* SMT: bvsrem *) + bv_of_Z n (Z.rem (bv_signed x) (bv_signed y)). + +Definition bv_shiftl {n} (x y : bv n) : bv n := (* SMT: bvshl *) + bv_of_Z n (Z.shiftl (bv_unsigned x) (bv_unsigned y)). +Program Definition bv_shiftr {n} (x y : bv n) : bv n := (* SMT: bvlshr *) + BV n (Z.shiftr (bv_unsigned x) (bv_unsigned y)) _. +Next Obligation. + intros n x y. apply bv_ok_in_range. bv_saturate. + split; [ apply Z.shiftr_nonneg; lia|]. + rewrite Z.shiftr_div_pow2; [|lia]. + apply (Z.le_lt_trans _ (bv_unsigned x)); [|lia]. + pose proof (Z.pow_pos_nonneg 2 (bv_unsigned y)). + apply Z.div_le_upper_bound; [ lia|]. nia. +Qed. +Definition bv_ashiftr {n} (x y : bv n) : bv n := (* SMT: bvashr *) + bv_of_Z n (Z.shiftr (bv_signed x) (bv_unsigned y)). + +Program Definition bv_or {n} (x y : bv n) : bv n := (* SMT: bvor *) + BV n (Z.lor (bv_unsigned x) (bv_unsigned y)) _. +Next Obligation. + intros. eapply bv_ok_bitwise_op; [ apply Z.lor_spec | by intros; eapply Z.lor_nonneg | done | apply bv_is_ok..]. +Qed. +Program Definition bv_and {n} (x y : bv n) : bv n := (* SMT: bvand *) + BV n (Z.land (bv_unsigned x) (bv_unsigned y)) _. +Next Obligation. + intros. eapply bv_ok_bitwise_op; [ apply Z.land_spec | intros; eapply Z.land_nonneg; by left | done | apply bv_is_ok..]. +Qed. +Program Definition bv_xor {n} (x y : bv n) : bv n := (* SMT: bvxor *) + BV n (Z.lxor (bv_unsigned x) (bv_unsigned y)) _. +Next Obligation. + intros. eapply bv_ok_bitwise_op; [ apply Z.lxor_spec | intros; eapply Z.lxor_nonneg; naive_solver | done | apply bv_is_ok..]. +Qed. +Program Definition bv_not {n} (x : bv n) : bv n := (* SMT: bvnot *) + bv_of_Z n (Z.lnot (bv_unsigned x)). + +Program Definition bv_zero_extend {n} (z : N) (b : bv n) : bv (n + z) := (* SMT: zero_extend *) + BV _ (bv_unsigned b) _. +Next Obligation. + intros. apply bv_ok_in_range. rewrite bv_modulus_add. bv_saturate. + pose proof (bv_modulus_pos z). nia. +Qed. + +Program Definition bv_sign_extend {n} (z : N) (b : bv n) : bv (n + z) := (* SMT: sign_extend *) + bv_of_Z _ (bv_signed b). + +(* s is start index and l is length. Note that this is different from +extract in SMTLIB which uses [extract (inclusive upper bound) +(inclusive lower bound)] *) +Program Definition bv_extract {n} (s l : N) (b : bv n) : bv l := + BV l (Z.land (bv_unsigned b ≫ Z.of_N s) (Z.ones (Z.of_N l))) _. +Next Obligation. + intros. + apply bv_ok_in_range. + assert (0 ≤ Z.land (bv_unsigned b ≫ Z.of_N s) (Z.ones (Z.of_N l))). + { apply Z.land_nonneg. left. apply Z.shiftr_nonneg. bv_saturate. lia. } + split; [lia|]. apply Z_bounded_iff_bits_nonneg; [lia.. |]. + intros k ?. rewrite Z.land_spec. apply andb_false_intro2. + apply Z.ones_spec_high. lia. +Qed. + +Program Definition bv_concat {n1 n2} (b1 : bv n1) (b2 : bv n2) : bv (n1 + n2) := (* SMT: concat *) + BV _ (Z.lor (bv_unsigned b1 ≪ Z.of_N n2) (bv_unsigned b2)) _. +Next Obligation. + intros. + apply bv_ok_in_range. + assert (0 ≤ Z.lor (bv_unsigned b1 ≪ Z.of_N n2) (bv_unsigned b2)). + { apply Z.lor_nonneg. bv_saturate. split; [|lia]. apply Z.shiftl_nonneg. lia. } + split; [lia|]. apply Z_bounded_iff_bits_nonneg; [lia.. |]. + intros k ?. rewrite Z.lor_spec, Z.shiftl_spec; [|lia]. + apply orb_false_intro; (eapply Z_bounded_iff_bits_nonneg; [..|done]); bv_saturate; try lia. + - apply (Z.lt_le_trans _ (bv_modulus n1)); [lia|]. apply Z.pow_le_mono_r; lia. + - apply (Z.lt_le_trans _ (bv_modulus n2)); [lia|]. apply Z.pow_le_mono_r; lia. +Qed. + +(*** [bvn] *) +Record bvn := BVN { + bvn_n : N; + bvn_val : bv bvn_n; +}. + +Definition bvn_unsigned (b : bvn) := bv_unsigned (b.(bvn_val)). + +Lemma bvn_eq (b1 b2 : bvn) : + b1 = b2 ↔ b1.(bvn_n) = b2.(bvn_n) ∧ bvn_unsigned b1 = bvn_unsigned b2. +Proof. split; [ naive_solver|]. destruct b1, b2; simpl; intros [??]. subst. f_equal. by apply bv_eq. Qed. + +Global Program Instance bvn_eq_dec : EqDecision bvn := λ '(BVN n1 b1) '(BVN n2 b2), + match decide (n1 = n2) with + | left eqv => match decide (bv_unsigned b1 = bv_unsigned b2) with + | left eqb => left _ + | right eqb => right _ + end + | right eqv => right _ + end. +(* TODO: The following does not compute to eq_refl*) +Next Obligation. intros. apply bvn_eq. naive_solver. Qed. +Next Obligation. intros. intros ?%bvn_eq. naive_solver. Qed. +Next Obligation. intros. intros ?%bvn_eq. naive_solver. Qed. + +Definition bvn_to_bv (n : N) (b : bvn) : option (bv n) := + match decide (b.(bvn_n) = n) with + | left eq => Some (eq_rect (bvn_n b) (λ n0 : N, bv n0) (bvn_val b) n eq) + | right _ => None + end. +Global Arguments bvn_to_bv !_ !_ /. + +Definition bv_to_bvn {n} (b : bv n) : bvn := BVN _ b. +Coercion bv_to_bvn : bv >-> bvn. + +(*** tests *) +Section test. +Goal ([BV{10} 3 ] = [BV{10} 5]). Abort. +Fail Goal ([BV{2} 3 ] = [BV{10} 5]). +Goal ([BV{2} 3 ] =@{bvn} [BV{10} 5]). Abort. +Fail Goal ([BV{2} 4 ] = [BV{2} 5]). +Goal bvn_to_bv 2 [BV{2} 3] = Some [BV{2} 3]. done. Abort. +End test. + +(* +(*** Work in progress benchmarks for the automation *) +(* TODO: Coq.micromega.ZifyClasses does not yet exist in Coq 8.10.2 *) +Require Import Coq.micromega.ZifyClasses. + +Global Instance Inj_bv_Z n : InjTyp (bv n) Z := + mkinj _ _ bv_unsigned (fun x => 0 ≤ x < bv_modulus n ) (bv_in_range _). + +Global Instance Op_bv_unsigned n : UnOp bv_unsigned := + { TUOp := fun x => x ; TUOpInj := (fun x : bv n => @eq_refl Z (bv_unsigned x)) }. + +Global Instance Op_bv_eq n : BinRel (@eq (bv n)) := + {| TR := @eq Z ; TRInj := bv_eq n |}. + +Section test_automation. + Context (n : N). + Implicit Type a : bv n. + Implicit Type l : list nat. + + Add InjTyp (Inj_bv_Z n). + Add UnOp (Op_bv_unsigned n). + Add BinRel (Op_bv_eq n). + + Goal ∀ a n1 n2, + bv_of_Z n (bv_unsigned (bv_of_Z n (bv_unsigned a + n1)) + n2) = + bv_of_Z n (bv_unsigned a + (n1 + n2)). + Abort. + + + Goal ∀ a z, 0 ≤ bv_unsigned a < bv_modulus n * bv_modulus z. + intros. pose proof (bv_modulus_pos z). nia. + Abort. + + (* Goal ∀ a, a = a. *) + (* zify. *) + (* Goal ∀ a, bv_of_Z n (bv_unsigned a + 0) = a. *) + (* zify. *) + (* bv_of_Z n (bv_unsigned a) = a *) + (* Abort. *) + + Goal ∀ a1 a2, + bv_unsigned a2 ≤ bv_unsigned a1 → + a1 ≠a2 → Z.to_nat (bv_unsigned a1 - bv_unsigned a2) ≠0%nat. + intros ???. rewrite bv_eq. lia. + Abort. + + Goal ∀ a a0 m, bv_of_Z n (bv_unsigned a + m) = bv_of_Z n (bv_unsigned a0 + m) → a = a0. + Abort. + + Goal ∀ a m n', bv_of_Z n (bv_unsigned a + m) = bv_of_Z n (bv_unsigned a + n') → m = n'. + Abort. + + Goal ∀ a1 a2 l, + bv_unsigned a1 ≤ bv_unsigned a2 ∧ bv_unsigned a2 < bv_unsigned a1 + S (length l) → + a2 ≠a1 → + bv_unsigned (bv_succ a1) ≤ bv_unsigned a2 ∧ bv_unsigned a2 < bv_unsigned (bv_succ a1) + length l. + Abort. + + Goal ∀ a2 l, + bv_unsigned a2 + S (length l) < bv_modulus n → + bv_unsigned (bv_succ a2) + length l < bv_modulus n. + Abort. + + Goal ∀ a1 a2 l, + bv_unsigned a2 + S (length l) < bv_modulus n → + bv_unsigned a1 < bv_unsigned a2 ∨ bv_unsigned a2 + S (length l) ≤ bv_unsigned a1→ + bv_unsigned a1 < bv_unsigned (bv_succ a2) ∨ bv_unsigned (bv_succ a2) + length l ≤ bv_unsigned a1. + Abort. + + Goal ∀ a1 a2 l, + bv_unsigned a1 < bv_unsigned a2 ∨ bv_unsigned a2 + S (length l) ≤ bv_unsigned a1 → + a2 ≠a1. + Abort. + + Goal ∀ a1 a2 l, + bv_unsigned a2 + S (length l) < bv_modulus n → + a1 ≠a2 → + bv_unsigned a2 ≤ bv_unsigned a1 ∧ bv_unsigned a1 < bv_unsigned a2 + S (length l) → + bv_unsigned (bv_succ a2) ≤ bv_unsigned a1 ∧ bv_unsigned a1 < bv_unsigned (bv_succ a2) + length l. + Abort. + + Goal ∀ a1 a2 l, + bv_unsigned a2 + S (length l) < bv_modulus n → + bv_unsigned a2 ≤ bv_unsigned a1 ∧ bv_unsigned a1 < bv_unsigned a2 + S (length l) → + a1 ≠a2 → + Z.to_nat (bv_unsigned a1 - bv_unsigned (bv_succ a2)) = + Init.Nat.pred (Z.to_nat (bv_unsigned a1 - bv_unsigned a2)). + Abort. +End test_automation. +*) -- GitLab