Skip to main content

isla_axiomatic/
litmus.rs

1// BSD 2-Clause License
2//
3// Copyright (c) 2019, 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
30use std::collections::HashMap;
31use std::fmt;
32use std::fs::File;
33use std::io::prelude::*;
34use std::path::Path;
35use std::process::Stdio;
36use std::sync::Arc;
37use toml::{value::Table, Value};
38
39use isla_lib::bitvector::BV;
40use isla_lib::config::ISAConfig;
41use isla_lib::ir::{Loc, Name, Reset, Symtab};
42use isla_lib::lexer::Lexer;
43use isla_lib::log;
44use isla_lib::memory::Region;
45use isla_lib::smt::Solver;
46use isla_lib::value_parser::LocParser;
47use isla_lib::zencode;
48
49use crate::sandbox::SandboxedCommand;
50
51pub mod exp;
52mod exp_lexer;
53lalrpop_mod!(
54    #[allow(clippy::all)]
55    exp_parser,
56    "/litmus/exp_parser.rs"
57);
58
59/// We have a special purpose temporary file module which is used to
60/// create the output file for each assembler/linker invocation. Each
61/// call to new just creates a new file name using our PID and a
62/// unique counter. This file isn't opened until we read it, after the
63/// assembler has created the object file. Dropping the `TmpFile`
64/// removes the file if it exists.
65mod tmpfile {
66    use std::env;
67    use std::fs::{create_dir, remove_file, OpenOptions};
68    use std::io::prelude::*;
69    use std::path::{Path, PathBuf};
70    use std::process;
71    use std::sync::atomic::{AtomicUsize, Ordering};
72
73    #[derive(Debug)]
74    pub struct TmpFile {
75        path: PathBuf,
76    }
77
78    static TMP_COUNTER: AtomicUsize = AtomicUsize::new(0);
79
80    impl TmpFile {
81        pub fn new() -> TmpFile {
82            let mut path = env::temp_dir();
83            path.push("isla");
84            if !path.is_dir() {
85                create_dir(&path).expect("Could not create temporary directory")
86            }
87            path.push(format!("isla_{}_{}", process::id(), TMP_COUNTER.fetch_add(1, Ordering::SeqCst)));
88            TmpFile { path }
89        }
90
91        pub fn path(&self) -> &Path {
92            self.path.as_ref()
93        }
94
95        pub fn read_to_end(&mut self) -> std::io::Result<Vec<u8>> {
96            let mut fd = OpenOptions::new().read(true).open(&self.path)?;
97            let mut buffer = Vec::new();
98            fd.read_to_end(&mut buffer)?;
99            Ok(buffer)
100        }
101    }
102
103    impl Drop for TmpFile {
104        fn drop(&mut self) {
105            if remove_file(&self.path).is_err() {}
106        }
107    }
108}
109
110type ThreadName = String;
111
112/// In addition to the threads, system litmus tests can contain extra
113/// sections containing additional code. These are linked at specific
114/// addresess. For example we might place a section at VBAR_EL1 for a
115/// thread to serve as an exception handler in ARMv8.
116struct UnassembledSection<'a> {
117    name: &'a str,
118    address: u64,
119    code: &'a str,
120}
121
122static THREAD_PREFIX: &str = "litmus_";
123
124fn validate_section_name(name: &str) -> bool {
125    for (i, c) in name.chars().enumerate() {
126        if i == 0 && !c.is_ascii_alphabetic() {
127            return false;
128        }
129
130        if !(c.is_ascii_alphanumeric() || c == '_') {
131            return false;
132        }
133    }
134
135    // Would conflict with the name we use for threads by default
136    if name.len() >= THREAD_PREFIX.len() && &name[0..THREAD_PREFIX.len()] == THREAD_PREFIX {
137        return false;
138    }
139
140    true
141}
142
143fn parse_address(addr: &str) -> Result<u64, String> {
144    if addr.len() < 2 {
145        return Err(format!("Address {} is too short, it must have the form 0xHEX or #xHEX", addr));
146    }
147    if &addr[0..2] != "0x" && &addr[0..2] != "#x" {
148        return Err(format!("Address {} must start with either `0x' or `#x'", addr));
149    }
150    u64::from_str_radix(&addr[2..], 16).map_err(|_| format!("Cannot parse {} as hexadecimal", addr))
151}
152
153enum LinkerLine<'a, 'b> {
154    Thread(&'a str),
155    Section(&'a UnassembledSection<'b>),
156}
157
158/// When we assemble a litmus test, we need to make sure any branch
159/// instructions have addresses that will match the location at which
160/// we load each thread in memory. To do this we invoke the linker and
161/// give it a linker script with the address for each thread in the
162/// litmus thread.
163fn generate_linker_script<B>(
164    threads: &[(ThreadName, &str)],
165    sections: &[UnassembledSection<'_>],
166    isa: &ISAConfig<B>,
167) -> String {
168    use std::fmt::Write;
169    use LinkerLine::*;
170
171    let mut thread_address = isa.thread_base;
172
173    let mut script = String::new();
174    writeln!(&mut script, "start = 0;\nSECTIONS\n{{").unwrap();
175
176    let mut t = 0;
177    let mut s = 0;
178
179    loop {
180        let line = match (threads.get(t), sections.get(s)) {
181            (Some((tid, _)), Some(section)) if thread_address < section.address => Thread(&*tid),
182            (Some(_), Some(section)) => Section(section),
183            (Some((tid, _)), None) => Thread(&*tid),
184            (None, Some(section)) => Section(section),
185            (None, None) => break,
186        };
187
188        match line {
189            Thread(tid) => {
190                writeln!(
191                    &mut script,
192                    "  . = 0x{:x};\n  {}{} : {{ *({}{}) }}",
193                    thread_address, THREAD_PREFIX, tid, THREAD_PREFIX, tid
194                )
195                .unwrap();
196                thread_address += isa.thread_stride;
197                t += 1
198            }
199            Section(section) => {
200                writeln!(&mut script, "  . = 0x{:x};\n  {} : {{ *({}) }}", section.address, section.name, section.name)
201                    .unwrap();
202                s += 1
203            }
204        }
205    }
206
207    writeln!(&mut script, "}}").unwrap();
208
209    log!(log::LITMUS, script);
210
211    script
212}
213
214type AssembledThreads = (Vec<(ThreadName, Vec<u8>)>, Vec<(u64, Vec<u8>)>, String);
215
216#[cfg(feature = "sandbox")]
217fn validate_code(code: &str) -> Result<(), String> {
218    // We already run in sandbox, but we can additionally rule out any
219    // directives
220    if code.contains('.') {
221        return Err("Invalid assembly in litmus".to_string());
222    }
223
224    if code.len() > 1000 {
225        return Err("Assembly in litmus thread too long".to_string());
226    }
227
228    for c in code.chars() {
229        if !c.is_ascii() || (c.is_control() && !c.is_ascii_whitespace()) {
230            return Err("Assembly block can contain only ascii text".to_string());
231        }
232    }
233
234    Ok(())
235}
236
237#[cfg(not(feature = "sandbox"))]
238fn validate_code(_: &str) -> Result<(), String> {
239    Ok(())
240}
241
242/// This function takes some assembly code for each thread, which
243/// should ideally be formatted as instructions separated by a newline
244/// and a tab (`\n\t`), and invokes the assembler provided in the
245/// `ISAConfig<B>` on this code. The generated ELF is then read in and
246/// the assembled code is returned as a vector of bytes corresponding
247/// to it's section in the ELF file as given by the thread name. If
248/// `reloc` is true, then we will also invoke the linker to place each
249/// thread's section at the correct address.
250fn assemble<B>(
251    threads: &[(ThreadName, &str)],
252    sections: &[UnassembledSection<'_>],
253    reloc: bool,
254    isa: &ISAConfig<B>,
255) -> Result<AssembledThreads, String> {
256    use goblin::Object;
257
258    let objfile = tmpfile::TmpFile::new();
259
260    let mut assembler = SandboxedCommand::from_tool(&isa.assembler)
261        .arg("-o")
262        .arg(objfile.path())
263        .stdin(Stdio::piped())
264        .stdout(Stdio::piped())
265        .stderr(Stdio::piped())
266        .spawn()
267        .map_err(|err| {
268            format!("Failed to spawn assembler {}. Got error: {}", &isa.assembler.executable.display(), err)
269        })?;
270
271    // Write each thread to the assembler's standard input, in a section called `THREAD_PREFIXN` for each thread `N`
272    {
273        let stdin = assembler.stdin.as_mut().ok_or_else(|| "Failed to open stdin for assembler".to_string())?;
274        for (thread_name, code) in threads.iter() {
275            validate_code(code)?;
276            stdin
277                .write_all(format!("\t.section {}{}\n", THREAD_PREFIX, thread_name).as_bytes())
278                .and_then(|_| stdin.write_all(code.as_bytes()))
279                .map_err(|_| format!("Failed to write to assembler input file {}", objfile.path().display()))?
280        }
281        for section in sections {
282            validate_code(section.code)?;
283            if !validate_section_name(section.name) {
284                return Err(format!("Section name {} is invalid", section.name));
285            };
286            stdin
287                .write_all(format!("\t.section {}\n", section.name).as_bytes())
288                .and_then(|_| stdin.write_all(section.code.as_bytes()))
289                .map_err(|_| format!("Failed to write to assembler input file {}", objfile.path().display()))?
290        }
291    }
292
293    let output = assembler.wait_with_output().map_err(|_| "Failed to read stdout from assembler".to_string())?;
294
295    if !output.status.success() {
296        return Err(String::from_utf8_lossy(&output.stderr).to_string());
297    }
298
299    let (mut objfile, objdump) = if reloc {
300        let objfile_reloc = tmpfile::TmpFile::new();
301        let linker_script = tmpfile::TmpFile::new();
302        {
303            let mut fd = File::create(linker_script.path())
304                .map_err(|_| "Failed to create temp file for linker script".to_string())?;
305            fd.write_all(generate_linker_script(threads, sections, isa).as_bytes())
306                .map_err(|_| "Failed to write linker script".to_string())?;
307        }
308
309        let linker_status = SandboxedCommand::from_tool(&isa.linker)
310            .arg("-T")
311            .arg(linker_script.path())
312            .arg("-o")
313            .arg(objfile_reloc.path())
314            .arg(objfile.path())
315            .status()
316            .map_err(|err| {
317                format!("Failed to invoke linker {}. Got error: {}", &isa.linker.executable.display(), err)
318            })?;
319
320        // Invoke objdump to get the assembled output in human readable
321        // form. If objdump fails for whatever reason, we don't want to
322        // consider it a hard error however.
323        let objdump = {
324            let output = SandboxedCommand::from_tool(&isa.objdump).arg("-D").arg(objfile_reloc.path()).output();
325
326            if let Ok(output) = output {
327                String::from_utf8_lossy(if output.status.success() { &output.stdout } else { &output.stderr })
328                    .to_string()
329            } else {
330                format!("Failed to invoke {}", &isa.objdump.executable.display())
331            }
332        };
333
334        if linker_status.success() {
335            (objfile_reloc, objdump)
336        } else {
337            return Err(format!("Linker failed with exit code {}", linker_status));
338        }
339    } else {
340        (objfile, "Objdump not available unless linker was used".to_string())
341    };
342
343    let buffer = objfile.read_to_end().map_err(|_| "Failed to read generated ELF file".to_string())?;
344
345    // Get the code from the generated ELF's `THREAD_PREFIXN` section for each thread
346    let mut assembled_threads: Vec<(ThreadName, Vec<u8>)> = Vec::new();
347    let mut assembled_sections: Vec<(u64, Vec<u8>)> = Vec::new();
348    match Object::parse(&buffer) {
349        Ok(Object::Elf(elf)) => {
350            let shdr_strtab = elf.shdr_strtab;
351            for section in elf.section_headers {
352                if let Some(Ok(section_name)) = shdr_strtab.get(section.sh_name) {
353                    for (thread_name, _) in threads.iter() {
354                        if section_name == format!("{}{}", THREAD_PREFIX, thread_name) {
355                            let offset = section.sh_offset as usize;
356                            let size = section.sh_size as usize;
357                            assembled_threads.push((thread_name.to_string(), buffer[offset..(offset + size)].to_vec()))
358                        }
359                    }
360                    for litmus_section in sections {
361                        if section_name == litmus_section.name {
362                            let offset = section.sh_offset as usize;
363                            let size = section.sh_size as usize;
364                            assembled_sections.push((litmus_section.address, buffer[offset..(offset + size)].to_vec()))
365                        }
366                    }
367                }
368            }
369        }
370        Ok(_) => return Err("Generated object was not an ELF file".to_string()),
371        Err(err) => return Err(format!("Failed to parse ELF file: {}", err)),
372    };
373
374    if assembled_threads.len() != threads.len() {
375        return Err("Could not find all threads in generated ELF file".to_string());
376    };
377
378    log!(log::LITMUS, objdump);
379
380    Ok((assembled_threads, assembled_sections, objdump))
381}
382
383/// For error reporting it's very helpful to be able to turn the raw
384/// opcodes we work with into actual human-readable assembly. To do
385/// this we use a regex to pair up the opcode with it's disassembly in
386/// objdump output for the litmus test.
387pub fn instruction_from_objdump<'obj>(opcode: &str, objdump: &'obj str) -> Option<String> {
388    use regex::Regex;
389    let instr_re = Regex::new(&format!(r"[0-9a-zA-Z]+:\s0*{}\s+(.*)", opcode)).unwrap();
390
391    // Find all instructions for an opcode in the objdump output. Return None if
392    // for some reason they are non-unique
393    // (this could happen if e.g. relocations have not been applied tojumps).
394    let mut instr: Option<&'obj str> = None;
395    for caps in instr_re.captures_iter(objdump) {
396        if let Some(prev) = instr {
397            if prev == caps.get(1)?.as_str().trim() {
398                continue;
399            } else {
400                return None;
401            }
402        } else {
403            instr = Some(caps.get(1)?.as_str().trim())
404        }
405    }
406
407    let whitespace_re = Regex::new(r"\s+").unwrap();
408    Some(whitespace_re.replace_all(instr?, " ").to_string())
409}
410
411pub fn opcode_from_objdump<B: BV>(addr: B, objdump: &str) -> Option<B> {
412    use regex::Regex;
413    let opcode_re = Regex::new(&format!(r"{:x}:\t([0-9a-fA-F]+) \t", addr)).unwrap();
414
415    if let Some(caps) = opcode_re.captures(objdump) {
416        B::from_str(&format!("0x{}", caps.get(1)?.as_str()))
417    } else {
418        None
419    }
420}
421
422fn label_from_objdump(label: &str, objdump: &str) -> Option<u64> {
423    use regex::Regex;
424    let label_re = Regex::new(&format!(r"([0-9a-fA-F]+) <{}>:", label)).unwrap();
425
426    if let Some(caps) = label_re.captures(objdump) {
427        u64::from_str_radix(caps.get(1)?.as_str(), 16).ok()
428    } else {
429        None
430    }
431}
432
433pub fn assemble_instruction<B>(instr: &str, isa: &ISAConfig<B>) -> Result<Vec<u8>, String> {
434    let instr = instr.to_owned() + "\n";
435    if let [(_, bytes)] = assemble(&[("single".to_string(), &instr)], &[], false, isa)?.0.as_slice() {
436        Ok(bytes.to_vec())
437    } else {
438        Err(format!("Failed to assemble instruction {}", instr))
439    }
440}
441
442fn parse_symbolic_locations(
443    litmus_toml: &Value,
444    symbolic_addrs: &HashMap<String, u64>,
445) -> Result<HashMap<String, u64>, String> {
446    let sym_locs_table = match litmus_toml.get("locations") {
447        Some(value) => value
448            .as_table()
449            .ok_or_else(|| "[locations] must be a table of <symbolic address> = <value> pairs".to_string())?,
450        // Most litmus tests won't define any symbolic locations.
451        None => return Ok(HashMap::new()),
452    };
453
454    let mut sym_locs = HashMap::new();
455    for (sym_loc, value) in sym_locs_table {
456        let value = value.as_str().ok_or_else(|| "Invalid symbolic address value")?;
457        let value = match i64::from_str_radix(value, 10) {
458            Ok(n) => n as u64,
459            Err(_) => *symbolic_addrs.get(value).ok_or_else(|| {
460                format!("Could not parse symbolic location value {} as an integer or address value", value)
461            })?,
462        };
463        sym_locs.insert(sym_loc.clone(), value);
464    }
465
466    Ok(sym_locs)
467}
468
469fn parse_symbolic_types(litmus_toml: &Value) -> Result<HashMap<String, u32>, String> {
470    let sym_types_table = match litmus_toml.get("types") {
471        Some(value) => value
472            .as_table()
473            .ok_or_else(|| "[types] must be a table of <symbolic address> = <type> pairs".to_string())?,
474        // Most litmus tests won't define any symbolic types.
475        None => return Ok(HashMap::new()),
476    };
477
478    let mut sym_sizeof = HashMap::new();
479    for (sym_type, ty) in sym_types_table {
480        let sizeof = match ty.as_str() {
481            Some("uint64_t") => 8,
482            Some("uint32_t") => 4,
483            Some("uint16_t") => 2,
484            Some("uint8_t") => 1,
485            _ => return Err("Invalid type in litmus [types] table".to_string()),
486        };
487        sym_sizeof.insert(sym_type.clone(), sizeof);
488    }
489
490    Ok(sym_sizeof)
491}
492
493fn parse_init<B>(
494    reg: &str,
495    value: &Value,
496    symbolic_addrs: &HashMap<String, u64>,
497    objdump: &str,
498    symtab: &Symtab,
499    isa: &ISAConfig<B>,
500) -> Result<(Name, u64), String> {
501    let reg = match isa.register_renames.get(reg) {
502        Some(reg) => *reg,
503        None => symtab.get(&zencode::encode(reg)).ok_or_else(|| format!("No register {} in thread init", reg))?,
504    };
505
506    let value = value.as_str().ok_or_else(|| "Init value must be a string".to_string())?;
507
508    match symbolic_addrs.get(value) {
509        Some(addr) => Ok((reg, *addr)),
510        None => {
511            if value.starts_with("0x") {
512                match u64::from_str_radix(&value[2..], 16) {
513                    Ok(n) => Ok((reg, n)),
514                    Err(_) => Err(format!("Cannot parse hexadecimal initial value in litmus: {}", value)),
515                }
516            } else if value.ends_with(':') {
517                match label_from_objdump(&value[0..value.len() - 1], objdump) {
518                    Some(addr) => Ok((reg, addr)),
519                    None => Err(format!("Could not find label {}", value)),
520                }
521            } else {
522                match i64::from_str_radix(value, 10) {
523                    Ok(n) => Ok((reg, n as u64)),
524                    Err(_) => Err(format!("Cannot handle initial value in litmus: {}", value)),
525                }
526            }
527        }
528    }
529}
530
531pub fn parse_reset_value<B: BV>(
532    toml: &Value,
533    symbolic_addrs: &HashMap<String, u64>,
534    symtab: &Symtab,
535) -> Result<Reset<B>, String> {
536    let value_str = toml.as_str().ok_or_else(|| format!("Register reset value must be a string {}", toml))?;
537
538    let lexer = exp_lexer::ExpLexer::new(value_str);
539    if let Ok(exp) = exp_parser::ExpParser::new().parse(symbolic_addrs, &HashMap::new(), symtab, &HashMap::new(), lexer)
540    {
541        Ok(exp::reset_eval(&exp))
542    } else {
543        Err(format!("Could not parse register value {}", value_str))
544    }
545}
546
547pub fn parse_reset_registers<B: BV>(
548    toml: &Value,
549    symbolic_addrs: &HashMap<String, u64>,
550    symtab: &Symtab,
551    isa: &ISAConfig<B>,
552) -> Result<HashMap<Loc<Name>, Reset<B>>, String> {
553    let mut symbolic_addrs = symbolic_addrs.clone();
554    symbolic_addrs.insert("page_table_base".to_string(), isa.page_table_base);
555    symbolic_addrs.insert("s2_page_table_base".to_string(), isa.s2_page_table_base);
556    
557    if let Some(resets) = toml.as_table() {
558        resets
559            .into_iter()
560            .map(|(register, value)| {
561                let lexer = Lexer::new(&register);
562                if let Ok(loc) = LocParser::new().parse::<B, _, _>(lexer) {
563                    if let Some(loc) = symtab.get_loc(&loc) {
564                        Ok((loc, parse_reset_value(value, &symbolic_addrs, symtab)?))
565                    } else {
566                        Err(format!("Could not find register {} when parsing register reset information", register))
567                    }
568                } else {
569                    Err(format!("Could not parse register {} when parsing register reset information", register))
570                }
571            })
572            .collect()
573    } else {
574        Err("registers.reset should be a table of <register> = <value> pairs".to_string())
575    }
576}
577
578fn parse_thread_initialization<B: BV>(
579    thread: &Value,
580    symbolic_addrs: &HashMap<String, u64>,
581    objdump: &str,
582    symtab: &Symtab,
583    isa: &ISAConfig<B>,
584) -> Result<(Vec<(Name, u64)>, HashMap<Loc<Name>, Reset<B>>), String> {
585    let init = thread
586        .get("init")
587        .and_then(Value::as_table)
588        .ok_or_else(|| "Thread init must be a list of register name/value pairs".to_string())?;
589    let init = init
590        .iter()
591        .map(|(reg, value)| parse_init(reg, value, symbolic_addrs, objdump, symtab, isa))
592        .collect::<Result<_, _>>()?;
593
594    let reset = if let Some(reset) = thread.get("reset") {
595        parse_reset_registers(reset, symbolic_addrs, symtab, isa)?
596    } else {
597        HashMap::new()
598    };
599
600    Ok((init, reset))
601}
602
603fn parse_self_modify_region<B: BV>(toml_region: &Value, objdump: &str) -> Result<Region<B>, String> {
604    let table = toml_region.as_table().ok_or_else(|| "Each self_modify element must be a TOML table".to_string())?;
605    let address = table
606        .get("address")
607        .and_then(Value::as_str)
608        .ok_or_else(|| "self_modify element must have a `address` field".to_string())?;
609    let address = label_from_objdump(&address[0..(address.len() - 1)], objdump)
610        .ok_or_else(|| "address not parseable in self_modify element")?;
611
612    let bytes = table
613        .get("bytes")
614        .and_then(Value::as_integer)
615        .ok_or_else(|| "self_modify element must have a `bytes` field".to_string())?;
616    let upper = address + (bytes as u64);
617
618    let values = table
619        .get("values")
620        .and_then(Value::as_array)
621        .ok_or_else(|| "self_modify element must have a `values` field".to_string())?;
622    let values = values
623        .iter()
624        .map(|v| v.as_str().and_then(B::from_str).map(|bv| (bv.lower_u64(), bv.len())))
625        .collect::<Option<Vec<_>>>()
626        .ok_or_else(|| "Could not parse `values` field")?;
627
628    Ok(Region::Constrained(
629        address..upper,
630        Arc::new(move |solver: &mut Solver<B>| {
631            use isla_lib::smt::smtlib::{Def, Exp, Ty, bits64};
632            let v = solver.fresh();
633            let exp: Exp = values.iter().fold(Exp::Bool(false), |exp, (bits, len)| {
634                Exp::Or(Box::new(Exp::Eq(Box::new(Exp::Var(v)), Box::new(bits64(*bits, *len)))), Box::new(exp))
635            });
636            solver.add(Def::DeclareConst(v, Ty::BitVec(bytes as u32 * 8)));
637            solver.add(Def::Assert(exp));
638            v
639        }),
640    ))
641}
642
643fn parse_self_modify<B: BV>(toml: &Value, objdump: &str) -> Result<Vec<Region<B>>, String> {
644    if let Some(value) = toml.get("self_modify") {
645        let array = value.as_array().ok_or_else(|| "self_modify section must be a TOML array".to_string())?;
646        Ok(array.iter().map(|v| parse_self_modify_region(v, objdump)).collect::<Result<_, _>>()?)
647    } else {
648        Ok(Vec::new())
649    }
650}
651
652fn parse_extra<'v>(extra: (&'v String, &'v Value)) -> Result<UnassembledSection<'v>, String> {
653    let addr =
654        extra.1.get("address").and_then(|addr| addr.as_str()).ok_or_else(|| format!("No address in {}", extra.0))?;
655    let code = extra.1.get("code").and_then(|code| code.as_str()).ok_or_else(|| format!("No code in {}", extra.0))?;
656    Ok(UnassembledSection { name: &extra.0, address: parse_address(addr)?, code })
657}
658
659#[derive(Clone)]
660pub struct AssembledThread<B> {
661    pub name: ThreadName,
662    pub inits: Vec<(Name, u64)>,
663    pub reset: HashMap<Loc<Name>, Reset<B>>,
664    pub code: Vec<u8>,
665}
666
667impl<B: BV> fmt::Debug for AssembledThread<B> {
668    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
669        f.debug_struct("AssembledThread").field("name", &self.name).field("code", &self.code).finish()
670    }
671}
672
673pub struct Litmus<B> {
674    pub name: String,
675    pub hash: Option<String>,
676    pub symbolic_addrs: HashMap<String, u64>,
677    pub symbolic_locations: HashMap<String, u64>,
678    pub symbolic_sizeof: HashMap<String, u32>,
679    pub assembled: Vec<AssembledThread<B>>,
680    pub sections: Vec<(u64, Vec<u8>)>,
681    pub self_modify_regions: Vec<Region<B>>,
682    pub objdump: String,
683    pub final_assertion: exp::Exp,
684}
685
686impl<B: BV> Litmus<B> {
687    pub fn log(&self) {
688        log!(log::LITMUS, &format!("Litmus test name: {}", self.name));
689        log!(log::LITMUS, &format!("Litmus test hash: {:?}", self.hash));
690        log!(log::LITMUS, &format!("Litmus test symbolic addresses: {:?}", self.symbolic_addrs));
691        log!(log::LITMUS, &format!("Litmus test data: {:#?}", self.assembled));
692        log!(log::LITMUS, &format!("Litmus test final assertion: {:?}", self.final_assertion));
693    }
694
695    pub fn parse(contents: &str, symtab: &Symtab, isa: &ISAConfig<B>) -> Result<Self, String> {
696        let litmus_toml = match contents.parse::<Value>() {
697            Ok(toml) => toml,
698            Err(e) => return Err(format!("Error when parsing litmus: {}", e)),
699        };
700
701        let name = litmus_toml
702            .get("name")
703            .and_then(|n| n.as_str().map(str::to_string))
704            .ok_or_else(|| "No name found in litmus file".to_string())?;
705
706        let hash = litmus_toml.get("hash").map(|h| h.to_string());
707
708        let symbolic = litmus_toml
709            .get("symbolic")
710            .and_then(Value::as_array)
711            .ok_or("No symbolic addresses found in litmus file")?;
712        let symbolic_addrs = symbolic
713            .iter()
714            .enumerate()
715            .map(|(i, sym_addr)| match sym_addr.as_str() {
716                Some(sym_addr) => {
717                    Ok((sym_addr.to_string(), isa.symbolic_addr_base + (i as u64 * isa.symbolic_addr_stride)))
718                }
719                None => Err("Symbolic addresses must be strings"),
720            })
721            .collect::<Result<_, _>>()?;
722
723        let symbolic_locations = parse_symbolic_locations(&litmus_toml, &symbolic_addrs)?;
724        let symbolic_sizeof = parse_symbolic_types(&litmus_toml)?;
725
726        let threads = litmus_toml.get("thread").and_then(|t| t.as_table()).ok_or("No threads found in litmus file")?;
727
728        let code: Vec<(ThreadName, &str)> = threads
729            .iter()
730            .map(|(thread_name, thread)| {
731                thread
732                    .get("code")
733                    .and_then(|code| code.as_str().map(|code| (thread_name.to_string(), code)))
734                    .ok_or_else(|| format!("No code found for thread {}", thread_name))
735            })
736            .collect::<Result<_, _>>()?;
737
738        let empty_table = toml::value::Map::new();
739        let sections: &Table = litmus_toml.get("section").and_then(|t| t.as_table()).unwrap_or_else(|| &empty_table);
740        let mut sections: Vec<UnassembledSection<'_>> = sections.iter().map(parse_extra).collect::<Result<_, _>>()?;
741        sections.sort_unstable_by_key(|section| section.address);
742
743        let (mut assembled, sections, objdump) = assemble(&code, &sections, true, isa)?;
744
745        let mut inits: Vec<(Vec<(Name, u64)>, HashMap<Loc<Name>, Reset<B>>)> = threads
746            .iter()
747            .map(|(_, thread)| parse_thread_initialization(thread, &symbolic_addrs, &objdump, symtab, isa))
748            .collect::<Result<_, _>>()?;
749
750        let assembled = assembled
751            .drain(..)
752            .zip(inits.drain(..))
753            .map(|((name, code), (inits, reset))| AssembledThread { name, inits, reset, code })
754            .collect();
755
756        let self_modify_regions = parse_self_modify::<B>(&litmus_toml, &objdump)?;
757
758        let fin = litmus_toml.get("final").ok_or("No final section found in litmus file")?;
759        let final_assertion = (match fin.get("assertion").and_then(Value::as_str) {
760            Some(assertion) => {
761                let lexer = exp_lexer::ExpLexer::new(&assertion);
762                exp_parser::ExpParser::new()
763                    .parse(&symbolic_addrs, &symbolic_sizeof, symtab, &isa.register_renames, lexer)
764                    .map_err(|error| error.to_string())
765            }
766            None => Err("No final.assertion found in litmus file".to_string()),
767        })?;
768
769        Ok(Litmus {
770            name,
771            hash,
772            symbolic_addrs,
773            symbolic_locations,
774            symbolic_sizeof,
775            assembled,
776            sections,
777            self_modify_regions,
778            objdump,
779            final_assertion,
780        })
781    }
782
783    pub fn from_file<P>(path: P, symtab: &Symtab, isa: &ISAConfig<B>) -> Result<Self, String>
784    where
785        P: AsRef<Path>,
786    {
787        let mut contents = String::new();
788        match File::open(&path) {
789            Ok(mut handle) => match handle.read_to_string(&mut contents) {
790                Ok(_) => (),
791                Err(e) => return Err(format!("Unexpected failure while reading litmus: {}", e)),
792            },
793            Err(e) => return Err(format!("Error when loading litmus '{}': {}", path.as_ref().display(), e)),
794        };
795
796        Self::parse(&contents, symtab, isa)
797    }
798}