1use 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
59mod 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
112struct 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 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
158fn 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 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
242fn 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 {
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 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 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
383pub 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 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 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 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(®ister);
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, §ions, 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}