diff --git a/cranelift/src/libreader/lib.rs b/cranelift/src/libreader/lib.rs index 94436e12f1..79f9b23521 100644 --- a/cranelift/src/libreader/lib.rs +++ b/cranelift/src/libreader/lib.rs @@ -8,8 +8,10 @@ extern crate cretonne; pub use parser::{Result, parse_functions, parse_test}; pub use testcommand::{TestCommand, TestOption}; pub use testfile::{TestFile, Details}; +pub use sourcemap::SourceMap; mod lexer; mod parser; mod testcommand; mod testfile; +mod sourcemap; diff --git a/cranelift/src/libreader/parser.rs b/cranelift/src/libreader/parser.rs index 63a635b3f7..54a53310ee 100644 --- a/cranelift/src/libreader/parser.rs +++ b/cranelift/src/libreader/parser.rs @@ -21,6 +21,7 @@ use cretonne::ir::instructions::{InstructionFormat, InstructionData, VariableArg BranchData, ReturnData}; use testfile::{TestFile, Details, Comment}; use testcommand::TestCommand; +use sourcemap; pub use lexer::Location; @@ -539,11 +540,16 @@ impl<'a> Parser<'a> { self.token(); self.comment_entity = None; - // Rewrite references to values and EBBs after parsing everuthing to allow forward + // Rewrite references to values and EBBs after parsing everything to allow forward // references. try!(ctx.rewrite_references()); - Ok((ctx.function, Details { comments: mem::replace(&mut self.comments, Vec::new()) })) + let details = Details { + comments: mem::replace(&mut self.comments, Vec::new()), + map: sourcemap::new(ctx.values, ctx.ebbs, ctx.stack_slots, ctx.jump_tables), + }; + + Ok((ctx.function, details)) } // Parse a function spec. @@ -1307,7 +1313,7 @@ mod tests { #[test] fn comments() { - let (func, Details { comments }) = + let (func, Details { comments, .. }) = Parser::new("; before function comment() { ; decl ss10 = stack_slot 13 ; stackslot. diff --git a/cranelift/src/libreader/sourcemap.rs b/cranelift/src/libreader/sourcemap.rs new file mode 100644 index 0000000000..9a601a3e39 --- /dev/null +++ b/cranelift/src/libreader/sourcemap.rs @@ -0,0 +1,133 @@ +//! Source map for translating source entity names to parsed entities. +//! +//! When the parser reads in a source file, entities like instructions, EBBs, and values get new +//! entity numbers. The parser maintains a mapping from the entity names in the source to the final +//! entity references. +//! +//! The `SourceMap` struct defined in this module makes the same mapping available to parser +//! clients. + +use std::collections::HashMap; +use cretonne::ir::{StackSlot, JumpTable, Ebb, Value}; +use cretonne::ir::entities::AnyEntity; + +/// Mapping from source entity names to entity references that are valid in the parsed function. +#[derive(Debug)] +pub struct SourceMap { + values: HashMap, // vNN, vxNN + ebbs: HashMap, // ebbNN + stack_slots: HashMap, // ssNN + jump_tables: HashMap, // jtNN +} + +/// Read-only interface which is exposed outside the parser crate. +impl SourceMap { + /// Look up an entity by source name. + /// Returns the entity reference corresponding to `name`, if it exists. + pub fn lookup_str(&self, name: &str) -> Option { + split_entity_name(name).and_then(|(ent, num)| { + match ent { + "v" => { + Value::direct_with_number(num) + .and_then(|v| self.values.get(&v).cloned()) + .map(AnyEntity::Value) + } + "vx" => { + Value::table_with_number(num) + .and_then(|v| self.values.get(&v).cloned()) + .map(AnyEntity::Value) + } + "ebb" => { + Ebb::with_number(num) + .and_then(|e| self.ebbs.get(&e).cloned()) + .map(AnyEntity::Ebb) + } + "ss" => self.stack_slots.get(&num).cloned().map(AnyEntity::StackSlot), + "jt" => self.jump_tables.get(&num).cloned().map(AnyEntity::JumpTable), + _ => None, + } + }) + } +} + +/// Get the number of decimal digits at the end of `s`. +fn trailing_digits(s: &str) -> usize { + // It's faster to iterate backwards over bytes, and we're only counting ASCII digits. + s.as_bytes().iter().rev().cloned().take_while(|&b| b'0' <= b && b <= b'9').count() +} + +/// Pre-parse a supposed entity name by splitting it into two parts: A head of lowercase ASCII +/// letters and numeric tail. +fn split_entity_name(name: &str) -> Option<(&str, u32)> { + let (head, tail) = name.split_at(name.len() - trailing_digits(name)); + if tail.len() > 1 && tail.starts_with('0') { + None + } else { + tail.parse().ok().map(|n| (head, n)) + } +} + +/// Create a new SourceMap from all the individual mappings. +pub fn new(values: HashMap, + ebbs: HashMap, + stack_slots: HashMap, + jump_tables: HashMap) + -> SourceMap { + SourceMap { + values: values, + ebbs: ebbs, + stack_slots: stack_slots, + jump_tables: jump_tables, + } +} + +#[cfg(test)] +mod tests { + use super::{trailing_digits, split_entity_name}; + use parse_test; + + #[test] + fn digits() { + assert_eq!(trailing_digits(""), 0); + assert_eq!(trailing_digits("x"), 0); + assert_eq!(trailing_digits("0x"), 0); + assert_eq!(trailing_digits("x1"), 1); + assert_eq!(trailing_digits("1x1"), 1); + assert_eq!(trailing_digits("1x01"), 2); + } + + #[test] + fn entity_name() { + assert_eq!(split_entity_name(""), None); + assert_eq!(split_entity_name("x"), None); + assert_eq!(split_entity_name("x+"), None); + assert_eq!(split_entity_name("x+1"), Some(("x+", 1))); + assert_eq!(split_entity_name("x-1"), Some(("x-", 1))); + assert_eq!(split_entity_name("1"), Some(("", 1))); + assert_eq!(split_entity_name("x1"), Some(("x", 1))); + assert_eq!(split_entity_name("xy0"), Some(("xy", 0))); + // Reject this non-canonical form. + assert_eq!(split_entity_name("inst01"), None); + } + + #[test] + fn details() { + let tf = parse_test("function detail() { + ss10 = stack_slot 13 + jt10 = jump_table ebb0 + ebb0(v4: i32, vx7: i32): + v10 = iadd v4, vx7 + }") + .unwrap(); + let map = &tf.functions[0].1.map; + + assert_eq!(map.lookup_str("v0"), None); + assert_eq!(map.lookup_str("ss1"), None); + assert_eq!(map.lookup_str("ss10").unwrap().to_string(), "ss0"); + assert_eq!(map.lookup_str("jt10").unwrap().to_string(), "jt0"); + assert_eq!(map.lookup_str("ebb0").unwrap().to_string(), "ebb0"); + assert_eq!(map.lookup_str("v4").unwrap().to_string(), "vx0"); + assert_eq!(map.lookup_str("vx7").unwrap().to_string(), "vx1"); + assert_eq!(map.lookup_str("v10").unwrap().to_string(), "v0"); + } +} diff --git a/cranelift/src/libreader/testfile.rs b/cranelift/src/libreader/testfile.rs index 3b4f3d4e16..34cfabce83 100644 --- a/cranelift/src/libreader/testfile.rs +++ b/cranelift/src/libreader/testfile.rs @@ -7,6 +7,7 @@ use cretonne::ir::Function; use cretonne::ir::entities::AnyEntity; use testcommand::TestCommand; +use sourcemap::SourceMap; /// A parsed test case. /// @@ -24,6 +25,7 @@ pub struct TestFile<'a> { #[derive(Debug)] pub struct Details<'a> { pub comments: Vec>, + pub map: SourceMap, } /// A comment in a parsed function.