isla_lib/
config.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
30//! This module loads a TOML file containing configuration for a specific instruction set
31//! architecture.
32
33use sha2::{Digest, Sha256};
34use std::collections::{HashMap, HashSet};
35use std::env;
36use std::fs::File;
37use std::io::prelude::*;
38use std::path::{Path, PathBuf};
39use std::process::Command;
40use std::sync::Arc;
41use toml::Value;
42
43use crate::bitvector::BV;
44use crate::ir::{Loc, Name, Reset, Symtab, Val};
45use crate::lexer::Lexer;
46use crate::value_parser::{LocParser, ValParser};
47use crate::zencode;
48
49/// We make use of various external tools like an assembler/objdump utility. We want to make sure
50/// they are available.
51fn find_tool_path<P>(program: P) -> Result<PathBuf, String>
52where
53    P: AsRef<Path>,
54{
55    env::var_os("PATH")
56        .and_then(|paths| {
57            env::split_paths(&paths)
58                .filter_map(|dir| {
59                    let full_path = dir.join(&program);
60                    if full_path.is_file() {
61                        Some(full_path)
62                    } else {
63                        None
64                    }
65                })
66                .next()
67        })
68        .ok_or_else(|| format!("Tool {} not found in $PATH", program.as_ref().display()))
69}
70
71#[derive(Debug)]
72pub struct Tool {
73    pub executable: PathBuf,
74    pub options: Vec<String>,
75}
76
77impl Tool {
78    pub fn command(&self) -> Command {
79        let mut cmd = Command::new(&self.executable);
80        cmd.args(&self.options);
81        cmd
82    }
83}
84
85fn get_tool_path(config: &Value, tool: &str) -> Result<Tool, String> {
86    match config.get(tool) {
87        Some(Value::String(tool)) => {
88            let mut words = tool.split_whitespace();
89            let program = words.next().ok_or_else(|| format!("Configuration option {} cannot be empty", tool))?;
90            Ok(Tool { executable: find_tool_path(program)?, options: words.map(|w| w.to_string()).collect() })
91        }
92        _ => Err(format!("Configuration option {} must be specified", tool)),
93    }
94}
95
96/// Get the program counter from the ISA config, and map it to the
97/// correct register identifer in the symbol table.
98fn get_program_counter(config: &Value, symtab: &Symtab) -> Result<Name, String> {
99    match config.get("pc") {
100        Some(Value::String(register)) => match symtab.get(&zencode::encode(&register)) {
101            Some(symbol) => Ok(symbol),
102            None => Err(format!("Register {} does not exist in supplied architecture", register)),
103        },
104        _ => Err("Configuration file must specify the program counter via `pc = \"REGISTER_NAME\"`".to_string()),
105    }
106}
107
108/// Get the program counter from the ISA config, and map it to the
109/// correct register identifer in the symbol table.
110fn get_ifetch_read_kind(config: &Value, symtab: &Symtab) -> Result<Name, String> {
111    match config.get("ifetch") {
112        Some(Value::String(rk)) => match symtab.get(&zencode::encode(&rk)) {
113            Some(symbol) => Ok(symbol),
114            None => Err(format!("Read kind {} does not exist in supplied architecture", rk)),
115        },
116        _ => Err("Configuration file must specify a read_kind for instruction-fetch events".to_string()),
117    }
118}
119
120fn get_exclusives(config: &Value, exclusives_type: &str, symtab: &Symtab) -> Result<Vec<Name>, String> {
121    match config.get(exclusives_type) {
122        Some(Value::Array(exclusives)) => exclusives
123            .iter()
124            .map(|v| {
125                let kind = v.as_str().ok_or_else(|| "Each exclusive must be a string value")?;
126                match symtab.get(&zencode::encode(kind)) {
127                    Some(symbol) => Ok(symbol),
128                    None => Err(format!("Exclusive kind {} does not exist in supplied architecture", kind)),
129                }
130            })
131            .collect::<Result<_, _>>(),
132        _ => Err("Configuration file must specify some exclusives".to_string()),
133    }
134}
135
136#[derive(Debug)]
137pub enum Kind<A> {
138    Read(A),
139    Write(A),
140    CacheOp(A),
141}
142
143macro_rules! event_kinds_in_table {
144    ($events: ident, $kind: path, $event_str: expr, $result: ident, $symtab: ident) => {
145        for (k, sets) in $events {
146            let k = $symtab
147                .get(&zencode::encode(k))
148                .ok_or_else(|| format!(concat!("Could not find ", $event_str, "_kind {} in architecture"), k))?;
149            let sets = match sets.as_str() {
150                Some(set) => vec![set],
151                None => sets
152                    .as_array()
153                    .and_then(|sets| sets.iter().map(|set| set.as_str()).collect::<Option<Vec<_>>>())
154                    .ok_or_else(|| {
155                        format!(concat!(
156                            "Each ",
157                            $event_str,
158                            "_kind in [",
159                            $event_str,
160                            "s] must specify at least one cat set"
161                        ))
162                    })?,
163            };
164            for set in sets.into_iter() {
165                match $result.get_mut(set) {
166                    None => {
167                        $result.insert(set.to_string(), vec![$kind(k)]);
168                    }
169                    Some(kinds) => kinds.push($kind(k)),
170                }
171            }
172        }
173    };
174}
175
176fn get_event_sets(config: &Value, symtab: &Symtab) -> Result<HashMap<String, Vec<Kind<Name>>>, String> {
177    let reads =
178        config.get("reads").and_then(Value::as_table).ok_or_else(|| "Config file has no [reads] table".to_string())?;
179    let writes = config
180        .get("writes")
181        .and_then(Value::as_table)
182        .ok_or_else(|| "Config file must has no [writes] table".to_string())?;
183    let cache_ops = config
184        .get("cache_ops")
185        .and_then(Value::as_table)
186        .ok_or_else(|| "Config file must has no [cache_ops] table".to_string())?;
187
188    let mut result: HashMap<String, Vec<Kind<Name>>> = HashMap::new();
189
190    event_kinds_in_table!(reads, Kind::Read, "read", result, symtab);
191    event_kinds_in_table!(writes, Kind::Write, "write", result, symtab);
192    event_kinds_in_table!(cache_ops, Kind::CacheOp, "cache_op", result, symtab);
193
194    Ok(result)
195}
196
197fn get_table_value(config: &Value, table: &str, key: &str) -> Result<u64, String> {
198    config
199        .get(table)
200        .and_then(|threads| threads.get(key).and_then(|value| value.as_str()))
201        .ok_or_else(|| format!("No {}.{} found in config", table, key))
202        .and_then(|value| {
203            if value.len() >= 2 && &value[0..2] == "0x" {
204                u64::from_str_radix(&value[2..], 16)
205            } else {
206                u64::from_str_radix(value, 10)
207            }
208            .map_err(|e| format!("Could not parse {} as a 64-bit unsigned integer in {}.{}: {}", value, table, key, e))
209        })
210}
211
212fn from_toml_value<B: BV>(value: &Value) -> Result<Val<B>, String> {
213    match value {
214        Value::Boolean(b) => Ok(Val::Bool(*b)),
215        Value::Integer(i) => Ok(Val::I128(*i as i128)),
216        Value::String(s) => match ValParser::new().parse(Lexer::new(&s)) {
217            Ok(value) => Ok(value),
218            Err(e) => Err(format!("Parse error when reading register value from configuration: {}", e)),
219        },
220        _ => Err(format!("Could not parse TOML value {} as register value", value)),
221    }
222}
223
224fn get_default_registers<B: BV>(config: &Value, symtab: &Symtab) -> Result<HashMap<Name, Val<B>>, String> {
225    let defaults = config
226        .get("registers")
227        .and_then(|registers| registers.as_table())
228        .and_then(|registers| registers.get("defaults"));
229
230    if let Some(defaults) = defaults {
231        if let Some(defaults) = defaults.as_table() {
232            defaults
233                .into_iter()
234                .map(|(register, value)| {
235                    if let Some(register) = symtab.get(&zencode::encode(register)) {
236                        match from_toml_value(value) {
237                            Ok(value) => Ok((register, value)),
238                            Err(e) => Err(e),
239                        }
240                    } else {
241                        Err(format!(
242                            "Could not find register {} when parsing registers.defaults in configuration",
243                            register
244                        ))
245                    }
246                })
247                .collect()
248        } else {
249            Err("registers.defaults should be a table of <register> = <value> pairs".to_string())
250        }
251    } else {
252        Ok(HashMap::new())
253    }
254}
255
256pub fn reset_to_toml_value<B: BV>(value: &Value) -> Result<Reset<B>, String> {
257    if let Err(e) = from_toml_value::<B>(value) {
258        return Err(e);
259    };
260
261    let value = value.clone();
262    Ok(Arc::new(move |_, _| Ok(from_toml_value(&value).unwrap())))
263}
264
265pub fn toml_reset_registers<B: BV>(toml: &Value, symtab: &Symtab) -> Result<HashMap<Loc<Name>, Reset<B>>, String> {
266    if let Some(defaults) = toml.as_table() {
267        defaults
268            .into_iter()
269            .map(|(register, value)| {
270                let lexer = Lexer::new(&register);
271                if let Ok(loc) = LocParser::new().parse::<B, _, _>(lexer) {
272                    if let Some(loc) = symtab.get_loc(&loc) {
273                        Ok((loc, reset_to_toml_value(value)?))
274                    } else {
275                        Err(format!("Could not find register {} when parsing register reset information", register))
276                    }
277                } else {
278                    Err(format!("Could not parse register {} when parsing register reset information", register))
279                }
280            })
281            .collect()
282    } else {
283        Err("registers.reset should be a table of <register> = <value> pairs".to_string())
284    }
285}
286
287fn get_reset_registers<B: BV>(config: &Value, symtab: &Symtab) -> Result<HashMap<Loc<Name>, Reset<B>>, String> {
288    let defaults =
289        config.get("registers").and_then(|registers| registers.as_table()).and_then(|registers| registers.get("reset"));
290
291    if let Some(defaults) = defaults {
292        toml_reset_registers(defaults, symtab)
293    } else {
294        Ok(HashMap::new())
295    }
296}
297
298fn get_register_renames(config: &Value, symtab: &Symtab) -> Result<HashMap<String, Name>, String> {
299    let defaults = config
300        .get("registers")
301        .and_then(|registers| registers.as_table())
302        .and_then(|registers| registers.get("renames"));
303
304    if let Some(defaults) = defaults {
305        if let Some(defaults) = defaults.as_table() {
306            defaults
307                .into_iter()
308                .map(|(name, register)| {
309                    if let Some(register) = register.as_str().and_then(|r| symtab.get(&zencode::encode(r))) {
310                        Ok((name.to_string(), register))
311                    } else {
312                        Err(format!(
313                            "Could not find register {} when parsing registers.renames in configuration",
314                            register
315                        ))
316                    }
317                })
318                .collect()
319        } else {
320            Err("registers.names should be a table or <name> = <register> pairs".to_string())
321        }
322    } else {
323        Ok(HashMap::new())
324    }
325}
326
327fn get_ignored_registers(config: &Value, symtab: &Symtab) -> Result<HashSet<Name>, String> {
328    let ignored = config
329        .get("registers")
330        .and_then(|registers| registers.as_table())
331        .and_then(|registers| registers.get("ignore"));
332
333    if let Some(ignored) = ignored {
334        if let Some(ignored) = ignored.as_array() {
335            ignored
336                .iter()
337                .map(|register| {
338                    if let Some(register) = register.as_str().and_then(|r| symtab.get(&zencode::encode(r))) {
339                        Ok(register)
340                    } else {
341                        Err(format!(
342                            "Could not find register {} when parsing registers.ignore in configuration",
343                            register
344                        ))
345                    }
346                })
347                .collect()
348        } else {
349            Err("registers.ignore should be a list of register names".to_string())
350        }
351    } else {
352        Ok(HashSet::new())
353    }
354}
355
356fn get_barriers(config: &Value, symtab: &Symtab) -> Result<HashMap<Name, String>, String> {
357    if let Some(value) = config.get("barriers") {
358        if let Some(table) = value.as_table() {
359            let mut barriers = HashMap::new();
360            for (bk, name) in table.iter() {
361                let bk = match symtab.get(&zencode::encode(bk)) {
362                    Some(bk) => bk,
363                    None => return Err(format!("barrier_kind {} could not be found in the architecture", bk)),
364                };
365                let name = match name.as_str() {
366                    Some(name) => name,
367                    None => return Err(format!("{} must be a string", name)),
368                };
369                barriers.insert(bk, name.to_string());
370            }
371            Ok(barriers)
372        } else {
373            Err("[barriers] Must define a table of barrier_kind = name pairs".to_string())
374        }
375    } else {
376        Ok(HashMap::new())
377    }
378}
379
380pub struct ISAConfig<B> {
381    /// The identifier for the program counter register
382    pub pc: Name,
383    /// The read_kind for instruction fetch events
384    pub ifetch_read_kind: Name,
385    /// Exlusive read_kinds for the architecture
386    pub read_exclusives: Vec<Name>,
387    /// Exlusive write_kinds for the architecture
388    pub write_exclusives: Vec<Name>,
389    /// Map from cat file sets to event kinds
390    pub event_sets: HashMap<String, Vec<Kind<Name>>>,
391    /// A path to an assembler for the architecture
392    pub assembler: Tool,
393    /// A path to an objdump for the architecture
394    pub objdump: Tool,
395    /// A path to a linker for the architecture
396    pub linker: Tool,
397    /// A mapping from sail barrier_kinds to their names in cat memory
398    /// models
399    pub barriers: HashMap<Name, String>,
400    /// The base address for the page tables
401    pub page_table_base: u64,
402    /// The number of bytes in each page
403    pub page_size: u64,
404    /// The base address for the page tables (stage 2)
405    pub s2_page_table_base: u64,
406    /// The number of bytes in each page (stage 2)
407    pub s2_page_size: u64,
408    /// The base address for the threads in a litmus test
409    pub thread_base: u64,
410    /// The top address for the thread memory region
411    pub thread_top: u64,
412    /// The number of bytes between each thread
413    pub thread_stride: u64,
414    /// The first address to use when allocating symbolic addresses
415    pub symbolic_addr_base: u64,
416    /// The number of bytes between each symbolic address
417    pub symbolic_addr_stride: u64,
418    /// Default values for specified registers
419    pub default_registers: HashMap<Name, Val<B>>,
420    /// Reset values for specified registers
421    pub reset_registers: HashMap<Loc<Name>, Reset<B>>,
422    /// Register synonyms to rename
423    pub register_renames: HashMap<String, Name>,
424    /// Registers to ignore during footprint analysis
425    pub ignored_registers: HashSet<Name>,
426    /// Trace any function calls in this set
427    pub probes: HashSet<Name>,
428}
429
430impl<B: BV> ISAConfig<B> {
431    pub fn parse(contents: &str, symtab: &Symtab) -> Result<Self, String> {
432        let config = match contents.parse::<Value>() {
433            Ok(config) => config,
434            Err(e) => return Err(format!("Error when parsing configuration: {}", e)),
435        };
436
437        Ok(ISAConfig {
438            pc: get_program_counter(&config, symtab)?,
439            ifetch_read_kind: get_ifetch_read_kind(&config, symtab)?,
440            read_exclusives: get_exclusives(&config, "read_exclusives", symtab)?,
441            write_exclusives: get_exclusives(&config, "write_exclusives", symtab)?,
442            event_sets: get_event_sets(&config, symtab)?,
443            assembler: get_tool_path(&config, "assembler")?,
444            objdump: get_tool_path(&config, "objdump")?,
445            linker: get_tool_path(&config, "linker")?,
446            barriers: get_barriers(&config, symtab)?,
447            page_table_base: get_table_value(&config, "mmu", "page_table_base")?,
448            page_size: get_table_value(&config, "mmu", "page_size")?,
449            s2_page_table_base: get_table_value(&config, "mmu", "s2_page_table_base")?,
450            s2_page_size: get_table_value(&config, "mmu", "s2_page_size")?,
451            thread_base: get_table_value(&config, "threads", "base")?,
452            thread_top: get_table_value(&config, "threads", "top")?,
453            thread_stride: get_table_value(&config, "threads", "stride")?,
454            symbolic_addr_base: get_table_value(&config, "symbolic_addrs", "base")?,
455            symbolic_addr_stride: get_table_value(&config, "symbolic_addrs", "stride")?,
456            default_registers: get_default_registers(&config, symtab)?,
457            reset_registers: get_reset_registers(&config, symtab)?,
458            register_renames: get_register_renames(&config, symtab)?,
459            ignored_registers: get_ignored_registers(&config, symtab)?,
460            probes: HashSet::new(),
461        })
462    }
463
464    /// Use a default configuration when none is specified
465    pub fn new(symtab: &Symtab) -> Result<Self, String> {
466        Self::parse(include_str!("../default_config.toml"), symtab)
467    }
468
469    /// Load the configuration from a TOML file.
470    pub fn from_file<P>(hasher: &mut Sha256, path: P, symtab: &Symtab) -> Result<Self, String>
471    where
472        P: AsRef<Path>,
473    {
474        let mut contents = String::new();
475        match File::open(&path) {
476            Ok(mut handle) => match handle.read_to_string(&mut contents) {
477                Ok(_) => (),
478                Err(e) => return Err(format!("Unexpected failure while reading config: {}", e)),
479            },
480            Err(e) => return Err(format!("Error when loading config '{}': {}", path.as_ref().display(), e)),
481        };
482        hasher.input(&contents);
483        Self::parse(&contents, symtab)
484    }
485}