-
Notifications
You must be signed in to change notification settings - Fork 32
Expand file tree
/
Copy pathKnownBitsDomain.lean
More file actions
165 lines (138 loc) · 5.03 KB
/
Copy pathKnownBitsDomain.lean
File metadata and controls
165 lines (138 loc) · 5.03 KB
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
module
public import Veir.Analysis.DataFlow.Domains.AbstractDomain
public import Veir.RuntimeValue
import Veir.Meta.BVDecide
public section
namespace Veir
/-!
# Known-bits domain
This file defines the abstract value used by known-bits analysis. As in LLVM, a
known-bits value stores two masks: `zero` marks bits known to be zero and `one`
marks bits known to be one. Bits absent from both masks are unknown.
-/
/-- Two masks describing the known zero and known one bits of a fixed-width integer. -/
structure KnownBits where
bitwidth : Nat
zero : BitVec bitwidth
one : BitVec bitwidth
deriving DecidableEq, Repr
namespace KnownBits
/-- No bits are known for an integer of the given width. -/
def unknown (bitwidth : Nat) : KnownBits :=
{ bitwidth, zero := 0, one := 0 }
/-- Every bit of a concrete integer is known. -/
def constant (bitwidth : Nat) (value : Int) : KnownBits :=
let bits := BitVec.ofInt bitwidth value
{ bitwidth, zero := ~~~bits, one := bits }
/-- The zero and one masks do not make contradictory claims. -/
def isValid (bits : KnownBits) : Bool :=
bits.zero &&& bits.one == 0
/-- Keep only facts known on both incoming control-flow paths. -/
def join? (lhs rhs : KnownBits) : Option KnownBits :=
if h : lhs.bitwidth = rhs.bitwidth then
let rhsZero := h ▸ rhs.zero
let rhsOne := h ▸ rhs.one
some
{ bitwidth := lhs.bitwidth
zero := lhs.zero &&& rhsZero
one := lhs.one &&& rhsOne }
else
none
end KnownBits
/--
Sparse lattice for known bits. `bottom` is an uninitialized sparse value, `known`
contains width-aware masks, and `top` is used when even the integer width is unavailable.
-/
inductive KnownBitsLattice where
| bottom
| known (bits : KnownBits)
| top
deriving DecidableEq, Repr
namespace KnownBitsLattice
instance : Bot KnownBitsLattice where
bot := .bottom
instance : Top KnownBitsLattice where
top := .top
/-- No bit facts are known, but the integer width is known. -/
def unknown (bitwidth : Nat) : KnownBitsLattice :=
.known (KnownBits.unknown bitwidth)
/-- An exact fixed-width integer value. -/
def constant (bitwidth : Nat) (value : Int) : KnownBitsLattice :=
.known (KnownBits.constant bitwidth value)
/-- The concrete runtime values represented by a known-bits lattice element. -/
@[expose] def γ : KnownBitsLattice → Set RuntimeValue
| .bottom => ⊥
| .top => ⊤
| .known bits => fun concrete =>
match concrete with
| .int bitwidth (.val value) =>
∃ h : bitwidth = bits.bitwidth,
let value := value.cast h
value &&& bits.zero = 0 ∧ value &&& bits.one = bits.one
| _ => False
@[simp] theorem not_mem_γ_bottom (value : RuntimeValue) : value ∉ γ .bottom := fun h => h.elim
@[simp] theorem mem_γ_top (value : RuntimeValue) : value ∈ γ .top := trivial
/-- Normalize membership in a known-bits value to masks at the concrete value's width. -/
theorem mem_γ_known_masks_iff
{bits : KnownBits}
{bitwidth : Nat}
{value : BitVec bitwidth} :
RuntimeValue.int bitwidth (.val value) ∈ γ (.known bits) ↔
∃ zero one : BitVec bitwidth,
bits = ⟨bitwidth, zero, one⟩ ∧
value &&& zero = 0 ∧ value &&& one = one := by
constructor
· rcases bits with ⟨bitsWidth, zero, one⟩
rintro ⟨hwidth, hzero, hone⟩
change bitwidth = bitsWidth at hwidth
subst bitsWidth
simp at hzero hone
exact ⟨zero, one, rfl, hzero, hone⟩
· rintro ⟨zero, one, rfl, hzero, hone⟩
exact ⟨rfl, hzero, hone⟩
/-- Characterize known-bits membership as facts about each concrete bit. -/
theorem mem_γ_known_iff
{bits : KnownBits}
{bitwidth : Nat}
{value : BitVec bitwidth} :
RuntimeValue.int bitwidth (.val value) ∈ γ (.known bits) ↔
∃ zero one : BitVec bitwidth,
bits = ⟨bitwidth, zero, one⟩ ∧
(∀ i (hi : i < bitwidth), zero[i] = true → value[i] = false) ∧
(∀ i (hi : i < bitwidth), one[i] = true → value[i] = true) := by
rw [mem_γ_known_masks_iff]
constructor
· rintro ⟨zero, one, hbits, hzero, hone⟩
refine ⟨zero, one, hbits, ?_, ?_⟩
· intro i hi hzeroTrue
have hzeroBit := congrArg (fun value => value[i]) hzero
simp at hzeroBit
veir_bv_decide
· intro i hi honeTrue
have honeBit := congrArg (fun value => value[i]) hone
simp at honeBit
veir_bv_decide
· rintro ⟨zero, one, hbits, hzero, hone⟩
refine ⟨zero, one, hbits, ?_, ?_⟩
· ext i hi
have hzeroBit := hzero i hi
simp at hzeroBit ⊢
veir_bv_decide
· ext i hi
have honeBit := hone i hi
simp at honeBit ⊢
veir_bv_decide
/-- Join facts arriving along different control-flow paths. -/
def join : KnownBitsLattice → KnownBitsLattice → KnownBitsLattice
| .bottom, rhs => rhs
| lhs, .bottom => lhs
| .top, _ => .top
| _, .top => .top
| .known lhs, .known rhs =>
match lhs.join? rhs with
| some bits => .known bits
| none => .top
instance : Join KnownBitsLattice where
join := join
end KnownBitsLattice
end Veir