Skip to main content

isla_axiomatic/
axiomatic.rs

1// BSD 2-Clause License
2//
3// Copyright (c) 2020 Alasdair Armstrong
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are
9// met:
10//
11// 1. Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13//
14// 2. Redistributions in binary form must reproduce the above copyright
15// notice, this list of conditions and the following disclaimer in the
16// documentation and/or other materials provided with the distribution.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30//! This module implements utilities for working with axiomatic memory
31//! models.
32
33use std::collections::HashMap;
34use std::error::Error;
35use std::fmt;
36
37use isla_lib::bitvector::BV;
38use isla_lib::config::ISAConfig;
39use isla_lib::ir::{Name, SharedState, Val};
40use isla_lib::smt::{EvPath, Event};
41
42pub type ThreadId = usize;
43
44/// An iterator over candidate executions
45pub struct Candidates<'ev, B> {
46    index: Vec<usize>,
47    max_index: Vec<usize>,
48    threads: &'ev [Vec<EvPath<B>>],
49    out_of_bounds: bool,
50}
51
52impl<'ev, B: BV> Candidates<'ev, B> {
53    /// Create a candidate exeuction iterator from a slice containing
54    /// vectors for each path through a thread.
55    pub fn new(threads: &'ev [Vec<EvPath<B>>]) -> Self {
56        Candidates {
57            index: vec![0; threads.len()],
58            max_index: threads.iter().map(|t| t.len()).collect(),
59            threads,
60            out_of_bounds: !threads.iter().all(|t| !t.is_empty()),
61        }
62    }
63
64    pub fn total(&self) -> usize {
65        if self.threads.is_empty() {
66            0
67        } else {
68            self.max_index.iter().product()
69        }
70    }
71}
72
73fn increment_index(index: &mut [usize], max_index: &[usize], carry: usize) -> bool {
74    if carry == index.len() {
75        return true;
76    }
77
78    index[carry] += 1;
79    if index[carry] == max_index[carry] {
80        index[carry] = 0;
81        increment_index(index, max_index, carry + 1)
82    } else {
83        false
84    }
85}
86
87impl<'ev, B: BV> Iterator for Candidates<'ev, B> {
88    type Item = Vec<&'ev [Event<B>]>;
89
90    fn next(&mut self) -> Option<Self::Item> {
91        if self.out_of_bounds {
92            None
93        } else {
94            let mut result = Vec::with_capacity(self.threads.len());
95            self.threads.iter().zip(self.index.iter()).for_each(|(thread, i)| result.push(thread[*i].as_ref()));
96            self.out_of_bounds = increment_index(&mut self.index, &self.max_index, 0);
97            Some(result)
98        }
99    }
100}
101
102pub struct Pairs<'a, A> {
103    index: (usize, usize),
104    slice: &'a [A],
105}
106
107impl<'a, A> Pairs<'a, A> {
108    pub fn from_slice(slice: &'a [A]) -> Self {
109        Pairs { index: (0, 0), slice }
110    }
111}
112
113impl<'a, A> Iterator for Pairs<'a, A> {
114    type Item = (&'a A, &'a A);
115
116    fn next(&mut self) -> Option<Self::Item> {
117        self.index.1 += 1;
118        if self.index.1 > self.slice.len() {
119            self.index.1 = 1;
120            self.index.0 += 1;
121        }
122        if self.index.0 >= self.slice.len() {
123            return None;
124        }
125        Some((&self.slice[self.index.0], &self.slice[self.index.1 - 1]))
126    }
127}
128
129/// An AxEvent (axiomatic event) is an event combined with metadata
130/// about where and when it was executed in a candidate
131/// execution. This can be combined with the footprint analysis to
132/// determine various dependency orders on events.
133#[derive(Debug)]
134pub struct AxEvent<'a, B> {
135    /// The opcode for the instruction that contained the underlying event
136    pub opcode: B,
137    /// The place of the event in po-order for it's thread
138    pub po: usize,
139    /// If a single instruction contains multiple events, this will
140    /// order them
141    pub intra_instruction_order: usize,
142    /// The thread id for the event
143    pub thread_id: ThreadId,
144    /// A generated unique name for the event
145    pub name: String,
146    /// The underlying event in the SMT trace
147    pub base: &'a Event<B>,
148    /// Is the event an instruction fetch (i.e. base is ReadMem with an ifetch read_kind)
149    pub is_ifetch: bool,
150}
151
152impl<'a, B: BV> AxEvent<'a, B> {
153    pub fn address(&self) -> Option<&'a Val<B>> {
154        match self.base {
155            Event::ReadMem { address, .. } | Event::WriteMem { address, .. } | Event::CacheOp { address, .. } => {
156                Some(address)
157            }
158            _ => None,
159        }
160    }
161
162    pub fn read_value(&self) -> Option<(&'a Val<B>, u32)> {
163        match self.base {
164            Event::ReadMem { value, bytes, .. } => Some((value, *bytes)),
165            _ => None,
166        }
167    }
168
169    pub fn write_data(&self) -> Option<(&'a Val<B>, u32)> {
170        match self.base {
171            Event::WriteMem { data, bytes, .. } => Some((data, *bytes)),
172            _ => None,
173        }
174    }
175}
176
177pub mod relations {
178    use std::collections::HashMap;
179
180    use isla_lib::bitvector::BV;
181    use isla_lib::smt::Event;
182
183    use super::AxEvent;
184    use crate::footprint_analysis::{addr_dep, ctrl_dep, data_dep, rmw_dep, Footprint};
185
186    pub fn is_write<B: BV>(ev: &AxEvent<B>) -> bool {
187        ev.base.is_memory_write()
188    }
189
190    pub fn is_s1_translate<B: BV>(ev: &AxEvent<B>) -> bool {
191        if let Event::ReadMem { kind, .. } = ev.base {
192            kind == &"stage 1"
193        } else {
194            false
195        }
196    }
197
198    pub fn is_s2_translate<B: BV>(ev: &AxEvent<B>) -> bool {
199        if let Event::ReadMem { kind, .. } = ev.base {
200            kind == &"stage 2"
201        } else {
202            false
203        }
204    }
205
206    pub fn is_translate<B: BV>(ev: &AxEvent<B>) -> bool {
207        is_s1_translate(ev) || is_s2_translate(ev)
208    }
209
210    pub fn is_read<B: BV>(ev: &AxEvent<B>) -> bool {
211        !is_translate(ev) && !ev.is_ifetch && ev.base.is_memory_read()
212    }
213
214    pub fn is_barrier<B: BV>(ev: &AxEvent<B>) -> bool {
215        ev.base.is_barrier()
216    }
217
218    pub fn is_ifetch<B: BV>(ev: &AxEvent<B>) -> bool {
219        ev.is_ifetch
220    }
221
222    pub fn is_cache_op<B: BV>(ev: &AxEvent<B>) -> bool {
223        ev.base.is_cache_op()
224    }
225
226    // TODO:
227    pub fn amo<B: BV>(_ev1: &AxEvent<B>, _ev2: &AxEvent<B>) -> bool {
228        false
229    }
230
231    pub fn univ<B: BV>(_: &AxEvent<B>, _: &AxEvent<B>) -> bool {
232        true
233    }
234
235    pub fn disjoint<B: BV>(ev1: &AxEvent<B>, ev2: &AxEvent<B>) -> bool {
236        ev1.po != ev2.po || ev1.thread_id != ev2.thread_id
237    }
238
239    pub fn po<B: BV>(ev1: &AxEvent<B>, ev2: &AxEvent<B>) -> bool {
240        ev1.po < ev2.po && ev1.thread_id == ev2.thread_id
241    }
242
243    pub fn intra_instruction_ordered<B: BV>(ev1: &AxEvent<B>, ev2: &AxEvent<B>) -> bool {
244        ev1.po == ev2.po && ev1.thread_id == ev2.thread_id && ev1.intra_instruction_order < ev2.intra_instruction_order
245    }
246
247    pub fn internal<B: BV>(ev1: &AxEvent<B>, ev2: &AxEvent<B>) -> bool {
248        ev1.po != ev2.po && ev1.thread_id == ev2.thread_id
249    }
250
251    pub fn external<B: BV>(ev1: &AxEvent<B>, ev2: &AxEvent<B>) -> bool {
252        ev1.po != ev2.po && ev1.thread_id != ev2.thread_id
253    }
254
255    pub type DepRel<B> = fn(&AxEvent<B>, &AxEvent<B>, &[Vec<B>], &HashMap<B, Footprint>) -> bool;
256
257    pub fn addr<B: BV>(
258        ev1: &AxEvent<B>,
259        ev2: &AxEvent<B>,
260        thread_opcodes: &[Vec<B>],
261        footprints: &HashMap<B, Footprint>,
262    ) -> bool {
263        !ev1.is_ifetch && po(ev1, ev2) && addr_dep(ev1.po, ev2.po, &thread_opcodes[ev1.thread_id], footprints)
264    }
265
266    pub fn data<B: BV>(
267        ev1: &AxEvent<B>,
268        ev2: &AxEvent<B>,
269        thread_opcodes: &[Vec<B>],
270        footprints: &HashMap<B, Footprint>,
271    ) -> bool {
272        po(ev1, ev2) && data_dep(ev1.po, ev2.po, &thread_opcodes[ev1.thread_id], footprints)
273    }
274
275    pub fn ctrl<B: BV>(
276        ev1: &AxEvent<B>,
277        ev2: &AxEvent<B>,
278        thread_opcodes: &[Vec<B>],
279        footprints: &HashMap<B, Footprint>,
280    ) -> bool {
281        !ev1.is_ifetch && po(ev1, ev2) && ctrl_dep(ev1.po, ev2.po, &thread_opcodes[ev1.thread_id], footprints)
282    }
283
284    pub fn rmw<B: BV>(
285        ev1: &AxEvent<B>,
286        ev2: &AxEvent<B>,
287        thread_opcodes: &[Vec<B>],
288        footprints: &HashMap<B, Footprint>,
289    ) -> bool {
290        (po(ev1, ev2) || intra_instruction_ordered(ev1, ev2))
291            && rmw_dep(ev1.po, ev2.po, &thread_opcodes[ev1.thread_id], footprints)
292    }
293
294    pub fn translation_walk_order<B: BV>(
295        ev1: &AxEvent<B>,
296        ev2: &AxEvent<B>,
297    ) -> bool {
298        intra_instruction_ordered(ev1, ev2) && is_translate(ev1) && is_translate(ev2)
299    }
300}
301
302pub struct ExecutionInfo<'ev, B> {
303    /// A vector containing all the events in a candidate execution
304    pub events: Vec<AxEvent<'ev, B>>,
305    /// A vector of po-ordered instruction opcodes for each thread
306    pub thread_opcodes: Vec<Vec<B>>,
307    /// The final write for each register in each thread (if written at all)
308    pub final_writes: HashMap<(Name, ThreadId), &'ev Val<B>>,
309}
310
311#[derive(Debug)]
312pub enum CandidateError<B> {
313    MultipleInstructionsInCycle { opcode1: B, opcode2: B },
314    NoInstructionsInCycle,
315}
316
317impl<B: BV> fmt::Display for CandidateError<B> {
318    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
319        use CandidateError::*;
320        match self {
321            MultipleInstructionsInCycle { opcode1, opcode2 } => write!(
322                f,
323                "A single fetch-execute-decode cycle in this candidate execution was associated with multiple instructions: {} and {}",
324                opcode1,
325                opcode2
326            ),
327            NoInstructionsInCycle => write!(
328                f,
329                "A fetch-execute-decode cycle was encountered that had no associated instructions"
330            ),
331        }
332    }
333}
334
335impl<B: BV> Error for CandidateError<B> {
336    fn source(&self) -> Option<&(dyn Error + 'static)> {
337        None
338    }
339}
340
341impl<'ev, B: BV> ExecutionInfo<'ev, B> {
342    pub fn from(
343        candidate: &'ev [&[Event<B>]],
344        shared_state: &SharedState<B>,
345        isa_config: &ISAConfig<B>,
346    ) -> Result<Self, CandidateError<B>> {
347        use CandidateError::*;
348        let mut exec = ExecutionInfo {
349            events: Vec::new(),
350            thread_opcodes: vec![Vec::new(); candidate.len()],
351            final_writes: HashMap::new(),
352        };
353
354        let rk_ifetch = shared_state.enum_member(isa_config.ifetch_read_kind).expect("Invalid ifetch read kind");
355
356        for (tid, thread) in candidate.iter().enumerate() {
357            for (po, cycle) in thread.split(|ev| ev.is_cycle()).skip(1).enumerate() {
358                let mut cycle_events: Vec<(usize, usize, String, &Event<B>, bool)> = Vec::new();
359                let mut cycle_instr: Option<B> = None;
360
361                for (eid, event) in cycle.iter().enumerate() {
362                    match event {
363                        Event::Instr(Val::Bits(bv)) => {
364                            if let Some(opcode) = cycle_instr {
365                                return Err(MultipleInstructionsInCycle { opcode1: *bv, opcode2: opcode });
366                            } else {
367                                exec.thread_opcodes[tid].push(*bv);
368                                cycle_instr = Some(*bv)
369                            }
370                        }
371                        Event::ReadMem { read_kind: Val::Enum(e), .. } => {
372                            if e.member == rk_ifetch {
373                                cycle_events.push((tid, eid, format!("R{}_{}_{}", po, eid, tid), event, true))
374                            } else {
375                                cycle_events.push((tid, eid, format!("R{}_{}_{}", po, eid, tid), event, false))
376                            }
377                        }
378                        Event::ReadMem { .. } => panic!("ReadMem event with non-concrete enum read_kind"),
379                        Event::WriteMem { .. } => {
380                            cycle_events.push((tid, eid, format!("W{}_{}_{}", po, eid, tid), event, false))
381                        }
382                        Event::Barrier { .. } => {
383                            cycle_events.push((tid, eid, format!("F{}_{}_{}", po, eid, tid), event, false))
384                        }
385                        Event::CacheOp { .. } => {
386                            cycle_events.push((tid, eid, format!("C{}_{}_{}", po, eid, tid), event, false))
387                        }
388                        Event::WriteReg(reg, _, val) => {
389                            exec.final_writes.insert((*reg, tid), val);
390                        }
391                        _ => (),
392                    }
393                }
394
395                for (tid, eid, name, ev, is_ifetch) in cycle_events {
396                    // Events must be associated with an instruction
397                    if let Some(opcode) = cycle_instr {
398                        exec.events.push(AxEvent {
399                            opcode,
400                            po,
401                            intra_instruction_order: eid,
402                            thread_id: tid,
403                            name,
404                            base: ev,
405                            is_ifetch,
406                        })
407                    } else if !ev.has_read_kind(rk_ifetch) {
408                        // Unless we have a single failing ifetch
409                        return Err(NoInstructionsInCycle);
410                    }
411                }
412            }
413        }
414
415        Ok(exec)
416    }
417}
418
419/// This module defines utilites for parsing and interpreting the
420/// models returned by Z3 when invoked on the command line.
421pub mod model {
422    use std::collections::HashMap;
423
424    use isla_lib::bitvector::BV;
425    use isla_lib::ir::Val;
426
427    use super::Pairs;
428    use crate::sexp::{DefineFun, InterpretEnv, InterpretError, SexpFn, SexpVal};
429    use crate::sexp_lexer::SexpLexer;
430    use crate::sexp_parser::SexpParser;
431
432    /// A model, as parsed from the SMT solver output, contains a list
433    /// of function declarations (which can have arity 0 for
434    /// constants) for each declare-const and declare-fun in the
435    /// model. A model can also be parameterised by a set of
436    /// events. The two lifetime parameters correspond to the
437    /// underlying smtlib model `'s` and the events `'ev`.
438    pub struct Model<'s, 'ev, B> {
439        env: InterpretEnv<'s, 'ev, B>,
440        functions: HashMap<&'s str, SexpFn<'s>>,
441    }
442
443    impl<'s, 'ev, B: BV> Model<'s, 'ev, B> {
444        /// Parse a model from a string of the form (model (define-fun ...) (define-fun ...) ...)
445        pub fn parse(events: &[&'ev str], model: &'s str) -> Option<Self> {
446            let mut env = InterpretEnv::new();
447            for event in events {
448                env.add_event(event)
449            }
450
451            let lexer = SexpLexer::new(model);
452            match SexpParser::new().parse(lexer) {
453                Ok(sexp) => match sexp.dest_fn("model") {
454                    Some(function_sexps) => {
455                        let mut functions = HashMap::new();
456                        for f in function_sexps {
457                            if let Some(DefineFun { name, params, body, .. }) = f.dest_define_fun() {
458                                let params = params.iter().map(|(a, _)| *a).collect();
459                                functions.insert(name, SexpFn { params, body });
460                            } else {
461                                return None;
462                            }
463                        }
464                        Some(Model { env, functions })
465                    }
466                    None => None,
467                },
468                Err(_) => None,
469            }
470        }
471
472        /// Interprets a function in the model
473        pub fn interpret(&mut self, f: &str, args: &[SexpVal<'ev, B>]) -> Result<SexpVal<'ev, B>, InterpretError> {
474            let function = self.functions.get(f).ok_or_else(|| InterpretError::UnknownFunction(f.to_string()))?;
475
476            self.env.add_args(&function.params, args)?;
477            let result = function.body.interpret(&mut self.env, &self.functions)?;
478            self.env.clear_args(&function.params);
479
480            Ok(result)
481        }
482
483        /// Inteprets a relation (an Event * Event -> Bool function)
484        /// over a set of events. Note that all events in this set
485        /// must have been used to parameterise the model in
486        /// Model::parse.
487        pub fn interpret_rel(
488            &mut self,
489            rel: &str,
490            events: &[&'ev str],
491        ) -> Result<Vec<(&'ev str, &'ev str)>, InterpretError> {
492            let mut pairs = Vec::new();
493
494            for (ev1, ev2) in Pairs::from_slice(events) {
495                match self.interpret(rel, &[SexpVal::Event(*ev1), SexpVal::Event(ev2)])? {
496                    SexpVal::Bool(true) => pairs.push((*ev1, *ev2)),
497                    SexpVal::Bool(false) => (),
498                    _ => return Err(InterpretError::Type(rel.to_string())),
499                }
500            }
501
502            Ok(pairs)
503        }
504
505        /// Interpret either a bitvector symbol in the model, or just
506        /// return the bitvector directory if the val is concrete
507        pub fn interpret_bits(&mut self, val: &Val<B>) -> Result<B, InterpretError> {
508            match val {
509                Val::Symbolic(v) => {
510                    let smt_name = format!("v{}", v);
511                    let sexp = self.interpret(&smt_name, &[])?;
512                    sexp.into_bits().ok_or_else(|| InterpretError::NotFound(smt_name))
513                }
514                Val::Bits(bv) => Ok(*bv),
515                _ => Err(InterpretError::Type("interpret_bv".to_string())),
516            }
517        }
518    }
519
520    #[cfg(test)]
521    mod tests {
522        use super::*;
523
524        use isla_lib::bitvector::b64::B64;
525
526        #[test]
527        fn test_parse() {
528            let smtlib = "(model (define-fun v12331 () (_ BitVec 32) #x00000001))";
529            Model::<B64>::parse(&[], smtlib).unwrap();
530        }
531
532        #[test]
533        fn test_interpret_1() {
534            let smtlib = "(model (define-fun dmb ((x!0 Event)) Bool false))";
535            let ev = "R0";
536            let mut model = Model::<B64>::parse(&[ev], smtlib).unwrap();
537            let result = model.interpret("dmb", &[SexpVal::Event(ev)]).unwrap();
538            assert_eq!(result, SexpVal::Bool(false));
539        }
540
541        #[test]
542        fn test_interpret_2() {
543            let smtlib = "(model (define-fun |0xdmb%| ((x!0 Event)) Bool false))";
544            let ev = "R0";
545            let mut model = Model::<B64>::parse(&[ev], smtlib).unwrap();
546            let result = model.interpret("0xdmb%", &[SexpVal::Event(ev)]).unwrap();
547            assert_eq!(result, SexpVal::Bool(false));
548        }
549
550        #[test]
551        fn test_interpret_3() {
552            let smtlib =
553                "(model (define-fun |foo| ((x!0 Event)) Bool (let ((a!0 true)) (let ((a!0 false)) (and a!0)))))";
554            let ev = "R0";
555            let mut model = Model::<B64>::parse(&[ev], smtlib).unwrap();
556            let result = model.interpret("foo", &[SexpVal::Event(ev)]).unwrap();
557            assert_eq!(result, SexpVal::Bool(false));
558        }
559
560        #[test]
561        fn test_interpret_4() {
562            let smtlib = "(model (define-fun |foo| ((x!0 Event)) Bool (ite false true (not (= x!0 R0)))))";
563            let ev = "R0";
564            let mut model = Model::<B64>::parse(&[ev], smtlib).unwrap();
565            let result = model.interpret("foo", &[SexpVal::Event(ev)]).unwrap();
566            assert_eq!(result, SexpVal::Bool(false));
567        }
568
569        #[test]
570        fn test_interpret_rel() {
571            let smtlib = "(model (define-fun obs ((x!0 Event) (x!1 Event)) Bool
572                            (or (and (= x!0 W0) (= x!1 R1))
573                                (and (= x!0 IW) (= x!1 W0))
574                                (and (= x!0 W1) (= x!1 R0))
575                                (and (= x!0 IW) (= x!1 W1)))))";
576            let evs = ["IW", "W0", "W1", "R0", "R1"];
577            let mut model = Model::<B64>::parse(&evs, smtlib).unwrap();
578            let result = model.interpret_rel("obs", &evs).unwrap();
579            assert!(result.contains(&("W0", "R1")));
580            assert!(result.contains(&("IW", "W0")));
581            assert!(result.contains(&("W1", "R0")));
582            assert!(result.contains(&("IW", "W1")));
583            assert!(result.len() == 4);
584        }
585    }
586}