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
// we have some methods on `VarMap` that may not currently be used by callers,
// but they still make sense to be part of `VarMap`
#![allow(dead_code)]
use crate::backend::{SolverRef, BV};
use crate::double_keyed_map::DoubleKeyedMap;
use crate::error::*;
use itertools::Itertools;
use log::debug;
use llvm_ir::Name;
#[derive(Clone)]
pub struct VarMap<V: BV> {
solver: V::SolverRef,
/// Maps a `Name` to the `BV` corresponding to the active version of that `Name`.
/// Different variables in different functions can have the same `Name` but
/// different values, so we actually have `(String, Name)` as the key type
/// where the `String` is the function name. We assume no two functions have
/// the same name.
active_version: DoubleKeyedMap<String, Name, V>,
/// Maps a `Name` to the version number of the latest version of that `Name`.
/// E.g., for `Name`s that have been created once, we have 0 here.
/// Like with the `active_version` map, the key type here includes the function name.
///
/// The version number here may not correspond to the active version in the
/// presence of recursion: when we return from a recursive call, the caller's
/// versions of the variables are active, even though the callee's versions
/// are the most recently created.
version_num: DoubleKeyedMap<String, Name, usize>,
/// Maximum version number of any given `Name`.
/// This bounds the maximum number of distinct versions of any given `Name`,
/// and thus can be used to bound both loop iterations and recursion depth.
///
/// Variables with the same `Name` in different functions do not share
/// counters for this purpose - they can each have versions up to the
/// `max_version_num`.
max_version_num: usize,
}
impl<V: BV> VarMap<V> {
/// `max_versions_of_name`: the maximum number of distinct versions allowed
/// of any given `Name`, that is, the maximum number of `BV`s created for a
/// given LLVM SSA value. Used to bound both loop iterations and recursion
/// depth.
///
/// Variables with the same `Name` in different functions do not share
/// counters for this purpose - they can each have up to
/// `max_versions_of_name` distinct versions.
pub fn new(solver: V::SolverRef, max_versions_of_name: usize) -> Self {
Self {
solver,
active_version: DoubleKeyedMap::new(),
version_num: DoubleKeyedMap::new(),
max_version_num: max_versions_of_name - 1, // because 0 is a version
}
}
/// Create a new (unconstrained) `BV` for the given `(String, Name)` pair.
///
/// This function performs uniquing, so if you call it twice
/// with the same `(String, Name)` pair, you will get two different `BV`s.
///
/// Returns the new `BV`, or `Err` if it can't be created.
///
/// (As of this writing, the only `Err` that might be returned is
/// `Error::LoopBoundExceeded`, which is returned if creating the new `BV`
/// would exceed `max_versions_of_name` -- see
/// [`VarMap::new()`](struct.VarMap.html#method.new).)
pub fn new_bv_with_name(&mut self, funcname: String, name: Name, bits: u32) -> Result<V> {
let new_version = self.new_version_of_name(&funcname, &name)?;
let bv = V::new(self.solver.clone(), bits, Some(&new_version));
debug!("Adding var {:?} = {:?}", name, bv);
self.active_version.insert(funcname, name, bv.clone());
Ok(bv)
}
/// Assign the given `(String, Name)` pair to map to the given `BV`.
///
/// This function performs uniquing, so it creates a new version of the
/// variable represented by the `(String, Name)` pair rather than overwriting
/// the current version.
///
/// Returns `Err` if the assignment can't be performed.
///
/// (As of this writing, the only `Err` that might be returned is
/// `Error::LoopBoundExceeded`, which is returned if creating the new version
/// of the `BV` would exceed `max_versions_of_name` -- see
/// [`VarMap::new()`](struct.VarMap.html#method.new).)
pub fn assign_bv_to_name(&mut self, funcname: String, name: Name, bv: V) -> Result<()> {
let new_version_num = self
.version_num
.entry(funcname.clone(), name.clone())
.and_modify(|v| *v += 1) // increment if it already exists in map
.or_insert(0); // insert a 0 if it didn't exist in map
if *new_version_num > self.max_version_num {
Err(Error::LoopBoundExceeded(self.max_version_num))
} else {
// We don't actually use the new_version_num except for the above check,
// since we aren't creating a new BV that needs a versioned name
debug!("Assigning var {:?} = {:?}", name, bv);
self.active_version.insert(funcname, name, bv);
Ok(())
}
}
/// Look up the most recent `BV` created for the given `(String, Name)` pair.
#[allow(clippy::ptr_arg)] // as of this writing, clippy warns that the &String argument should be &str; but it actually needs to be &String here
pub fn lookup_var(&self, funcname: &String, name: &Name) -> &V {
self.active_version.get(funcname, name).unwrap_or_else(|| {
let keys: Vec<(&String, &Name)> = self.active_version.keys().collect();
panic!(
"Failed to find var {:?} from function {:?} in map with keys {:?}",
name, funcname, keys
);
})
}
/// Overwrite the latest version of the given `(String, Name)` pair to instead be `bv`.
/// The `(String, Name)` pair must have already been previously assigned a value.
#[allow(clippy::ptr_arg)] // as of this writing, clippy warns that the &String argument should be &str; but it actually needs to be &String here
pub fn overwrite_latest_version_of_bv(&mut self, funcname: &String, name: &Name, bv: V) {
let mapvalue: &mut V = self
.active_version
.get_mut(funcname, name)
.unwrap_or_else(|| {
panic!(
"failed to find current active version of {:?} (function {:?}) in map",
name, funcname
)
});
*mapvalue = bv;
}
/// Get the most recent `BV` created for each `Name` in the given function.
/// Returns pairs of the `Name` and the `BV` assigned to that `Name`.
///
/// Returned pairs will be sorted by `Name`.
pub fn get_all_vars_in_fn(&self, funcname: &str) -> impl Iterator<Item = (&Name, &V)> {
self.active_version
.iter()
.filter(|&(fname, _, _)| funcname == fname)
.map(|(_, name, bv)| (name, bv))
.sorted_by_key(|&(name, _)| name)
}
/// Given a `Name` (from a particular function), creates a new version of it
/// and returns the corresponding versioned name
/// (or `Error::LoopBoundExceeded` if it would exceed the `max_version_num`)
fn new_version_of_name(&mut self, funcname: &str, name: &Name) -> Result<String> {
let new_version_num = self
.version_num
.entry(funcname.to_owned(), name.clone())
.and_modify(|v| *v += 1) // increment if it already exists in map
.or_insert(0); // insert a 0 if it didn't exist in map
if *new_version_num > self.max_version_num {
Err(Error::LoopBoundExceeded(self.max_version_num))
} else {
Ok(Self::build_versioned_name(funcname, name, *new_version_num))
}
}
/// Given a `Name` (from a particular function) and a version number, build
/// the corresponding versioned name.
///
/// This function does not modify (or even use) the current state of the
/// `VarMap`.
fn build_versioned_name(funcname: &str, name: &Name, version_num: usize) -> String {
let stem: String = match name {
Name::Name(s) => format!("name_{}", &s),
Name::Number(n) => format!("%{}", n),
};
format!("@{}_{}_{}", funcname, &stem, version_num)
}
/// Get a `RestoreInfo` which can later be used with `restore_fn_vars()` to
/// restore all of the given function's variables (in their current active
/// versions) back to active.
///
/// This is intended to support recursion. A `RestoreInfo` can be generated
/// before a recursive call, and then once the call returns, the restore
/// operation ensures the caller still has access to the correct versions of
/// its local variables (not the callee's versions, which are technically
/// more recent).
pub fn get_restore_info_for_fn(&self, funcname: String) -> RestoreInfo<V> {
let pairs_to_restore: Vec<_> = self
.active_version
.iter()
.filter(|(f, _, _)| f == &&funcname)
.map(|(_, n, v)| (n.clone(), v.clone()))
.collect();
RestoreInfo {
funcname,
pairs_to_restore,
}
}
/// Restore all of the variables in a `RestoreInfo` to their versions which
/// were active at the time the `RestoreInfo` was generated
pub fn restore_fn_vars(&mut self, rinfo: RestoreInfo<V>) {
let funcname = rinfo.funcname.clone();
for pair in rinfo.pairs_to_restore {
let val = self
.active_version
.get_mut(&funcname, &pair.0)
.unwrap_or_else(|| panic!("Malformed RestoreInfo: key {:?}", (&funcname, &pair.0)));
*val = pair.1;
}
}
/// Adapt the `VarMap` to a new solver instance.
///
/// The new solver instance should have been created (possibly transitively)
/// via `SolverRef::duplicate()` from the `SolverRef` this `VarMap` was
/// originally created with (or most recently changed to). Further, no new
/// variables should have been added since the call to
/// `SolverRef::duplicate()`.
pub fn change_solver(&mut self, new_solver: V::SolverRef) {
for v in self.active_version.values_mut() {
*v = new_solver.match_bv(v).unwrap();
}
self.solver = new_solver;
}
}
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct RestoreInfo<V: BV> {
funcname: String,
pairs_to_restore: Vec<(Name, V)>,
}
#[cfg(test)]
mod tests {
use super::*;
use crate::solver_utils;
use boolector::Btor;
use std::rc::Rc;
type BV = boolector::BV<Rc<Btor>>;
#[test]
fn lookup_vars() {
let btor = <Rc<Btor> as SolverRef>::new();
let mut varmap: VarMap<BV> = VarMap::new(btor, 20);
let funcname = "foo".to_owned();
// create llvm-ir names
let name1 = Name::from("val");
let name2 = Name::from(2);
// create corresponding BV values
let var1 = varmap
.new_bv_with_name(funcname.clone(), name1.clone(), 64)
.unwrap();
let var2 = varmap
.new_bv_with_name(funcname.clone(), name2.clone(), 1)
.unwrap(); // these clone()s wouldn't normally be necessary but we want to compare against the original values later
// check that looking up the llvm-ir values gives the correct BV ones
assert_eq!(varmap.lookup_var(&funcname, &name1), &var1);
assert_eq!(varmap.lookup_var(&funcname, &name2), &var2);
}
#[test]
fn vars_are_uniqued() {
let btor = <Rc<Btor> as SolverRef>::new();
let mut varmap: VarMap<BV> = VarMap::new(btor.clone(), 20);
let funcname = "foo".to_owned();
// create two vars with the same name
let name = Name::from("x");
let x1 = varmap
.new_bv_with_name(funcname.clone(), name.clone(), 64)
.unwrap();
let x2 = varmap.new_bv_with_name(funcname.clone(), name, 64).unwrap();
// constrain with incompatible constraints
x1.ugt(&BV::from_u64(btor.clone().into(), 2, 64)).assert();
x2.ult(&BV::from_u64(btor.clone().into(), 1, 64)).assert();
// check that we're still sat
assert_eq!(solver_utils::sat(&btor), Ok(true));
// now repeat with integer names
let name = Name::from(3);
let x1 = varmap
.new_bv_with_name(funcname.clone(), name.clone(), 64)
.unwrap();
let x2 = varmap.new_bv_with_name(funcname.clone(), name, 64).unwrap();
x1.ugt(&BV::from_u64(btor.clone().into(), 2, 64)).assert();
x2.ult(&BV::from_u64(btor.clone().into(), 1, 64)).assert();
assert_eq!(solver_utils::sat(&btor), Ok(true));
// now repeat with the same name but different functions
let name = Name::from(10);
let otherfuncname = "bar".to_owned();
let x1 = varmap
.new_bv_with_name(funcname.clone(), name.clone(), 64)
.unwrap();
let x2 = varmap
.new_bv_with_name(otherfuncname.clone(), name.clone(), 64)
.unwrap();
x1.ugt(&BV::from_u64(btor.clone().into(), 2, 64)).assert();
x2.ult(&BV::from_u64(btor.clone().into(), 1, 64)).assert();
assert_eq!(solver_utils::sat(&btor), Ok(true));
}
#[test]
fn enforces_max_version() {
let btor = <Rc<Btor> as SolverRef>::new();
// Create a `VarMap` with `max_version_num = 10`
let mut varmap: VarMap<BV> = VarMap::new(btor, 10);
// Check that we can create 10 versions of the same `Name`
let funcname = "foo".to_owned();
let name = Name::from(7);
for _ in 0 .. 10 {
let bv = varmap.new_bv_with_name(funcname.clone(), name.clone(), 64);
assert!(bv.is_ok());
}
// Check that we can create another 10 versions of that `Name` in a different function
let funcname2 = "bar".to_owned();
for _ in 0 .. 10 {
let bv = varmap.new_bv_with_name(funcname2.clone(), name.clone(), 64);
assert!(bv.is_ok());
}
// Check that we can't create an 11th version of that `Name`
let bv = varmap.new_bv_with_name(funcname, name, 64);
assert!(bv.is_err());
}
#[test]
fn restore_info() {
let btor = <Rc<Btor> as SolverRef>::new();
let mut varmap: VarMap<BV> = VarMap::new(btor, 10);
// create a var named "foo" in function "func"
let fooname = Name::from("foo");
let foo1 = varmap
.new_bv_with_name("func".to_owned(), fooname.clone(), 64)
.unwrap();
// save restore info for "func"
let rinfo = varmap.get_restore_info_for_fn("func".to_owned());
// create another var named "foo" in function "func"
let foo2 = varmap
.new_bv_with_name("func".to_owned(), fooname.clone(), 64)
.unwrap();
// check that a lookup gives the most recent var
assert_eq!(varmap.lookup_var(&"func".to_owned(), &fooname), &foo2);
// restore, and check that a lookup now gives the first var
varmap.restore_fn_vars(rinfo);
assert_eq!(varmap.lookup_var(&"func".to_owned(), &fooname), &foo1);
}
#[test]
fn restore_different_function() {
let btor = <Rc<Btor> as SolverRef>::new();
let mut varmap: VarMap<BV> = VarMap::new(btor, 10);
// create a var named "foo" in function "func"
let fooname = Name::from("foo");
let _foo1 = varmap
.new_bv_with_name("func".to_owned(), fooname.clone(), 64)
.unwrap();
// save restore info for function "blah"
let rinfo_blah = varmap.get_restore_info_for_fn("blah".to_owned());
// create another var named "foo" in function "func"
let foo2 = varmap
.new_bv_with_name("func".to_owned(), fooname.clone(), 64)
.unwrap();
// restore function "blah", and check that lookups in function "func" are unaffected
assert_eq!(varmap.lookup_var(&"func".to_owned(), &fooname), &foo2);
varmap.restore_fn_vars(rinfo_blah);
assert_eq!(varmap.lookup_var(&"func".to_owned(), &fooname), &foo2);
}
}