diff --git a/cranelift/src/libcretonne/constant_hash.rs b/cranelift/src/libcretonne/constant_hash.rs new file mode 100644 index 0000000000..fd8115c7fa --- /dev/null +++ b/cranelift/src/libcretonne/constant_hash.rs @@ -0,0 +1,74 @@ +//! Runtime support for precomputed constant hash tables. +//! +//! The `meta/constant_hash.py` Python module can generate constant hash tables using open +//! addressing and quadratic probing. The hash tables are arrays that are guaranteed to: +//! +//! - Have a power-of-two size. +//! - Contain at least one empty slot. +//! +//! This module provides runtime support for lookups in these tables. + +/// Trait that must be implemented by the entries in a constant hash table. +pub trait Table { + /// Get the number of entries in this table which must be a power of two. + fn len(&self) -> usize; + + /// Get the key corresponding to the entry at `idx`, or `None` if the entry is empty. + /// The `idx` must be in range. + fn key(&self, idx: usize) -> Option; +} + + +/// Look for `key` in `table`. +/// +/// The provided `hash` value must have been computed from `key` using the same hash function that +/// was used to construct the table. +/// +/// Returns the table index containing the found entry, or `None` if no entry could be found. +pub fn probe + ?Sized>(table: &T, key: K, hash: usize) -> Option { + debug_assert!(table.len().is_power_of_two()); + let mask = table.len() - 1; + + let mut idx = hash; + let mut step = 0; + + loop { + idx &= mask; + + match table.key(idx) { + None => return None, + Some(k) if k == key => return Some(idx), + _ => {} + } + + // Quadratic probing. + step += 1; + // When `table.len()` is a power of two, it can be proven that `idx` will visit all + // entries. This means that this loop will always terminate if the hash table has even + // one unused entry. + debug_assert!(step < table.len()); + idx += step; + } +} + +/// A primitive hash function for matching opcodes. +/// Must match `meta/constant_hash.py`. +pub fn simple_hash(s: &str) -> usize { + let mut h: u32 = 5381; + for c in s.chars() { + h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); + } + h as usize +} + +#[cfg(test)] +mod tests { + use super::simple_hash; + + #[test] + fn basic() { + // c.f. meta/constant_hash.py tests. + assert_eq!(simple_hash("Hello"), 0x2fa70c01); + assert_eq!(simple_hash("world"), 0x5b0c31d5); + } +} diff --git a/cranelift/src/libcretonne/ir/instructions.rs b/cranelift/src/libcretonne/ir/instructions.rs index eee47b8d66..ca43a3dc31 100644 --- a/cranelift/src/libcretonne/ir/instructions.rs +++ b/cranelift/src/libcretonne/ir/instructions.rs @@ -64,30 +64,25 @@ impl FromStr for Opcode { /// Parse an Opcode name from a string. fn from_str(s: &str) -> Result { - use simple_hash::simple_hash; - let tlen = OPCODE_HASH_TABLE.len(); - assert!(tlen.is_power_of_two()); - let mut idx = simple_hash(s) as usize; - let mut step: usize = 0; - loop { - idx = idx % tlen; - let entry = OPCODE_HASH_TABLE[idx]; + use constant_hash::{Table, simple_hash, probe}; - if entry == Opcode::NotAnOpcode { - return Err("Unknown opcode"); + impl<'a> Table<&'a str> for [Opcode] { + fn len(&self) -> usize { + self.len() } - if *opcode_name(entry) == *s { - return Ok(entry); + fn key(&self, idx: usize) -> Option<&'a str> { + if self[idx] == Opcode::NotAnOpcode { + None + } else { + Some(opcode_name(self[idx])) + } } + } - // Quadratic probing. - step += 1; - // When `tlen` is a power of two, it can be proven that idx will visit all entries. - // This means that this loop will always terminate if the hash table has even one - // unused entry. - assert!(step < tlen); - idx += step; + match probe::<&str, [Opcode]>(&OPCODE_HASH_TABLE, s, simple_hash(s)) { + None => Err("Unknown opcode"), + Some(i) => Ok(OPCODE_HASH_TABLE[i]), } } } diff --git a/cranelift/src/libcretonne/lib.rs b/cranelift/src/libcretonne/lib.rs index a8cd09ab8d..a4ed7dfecb 100644 --- a/cranelift/src/libcretonne/lib.rs +++ b/cranelift/src/libcretonne/lib.rs @@ -15,6 +15,6 @@ pub mod dominator_tree; pub mod entity_map; pub mod settings; -mod simple_hash; +mod constant_hash; #[cfg(test)]pub mod test_utils; diff --git a/cranelift/src/libcretonne/settings.rs b/cranelift/src/libcretonne/settings.rs index c65c5eeb68..b3cf494954 100644 --- a/cranelift/src/libcretonne/settings.rs +++ b/cranelift/src/libcretonne/settings.rs @@ -23,7 +23,7 @@ use std::fmt; use std::result; -use simple_hash::simple_hash; +use constant_hash::{probe, simple_hash}; /// A string-based configurator for settings groups. /// @@ -75,27 +75,12 @@ impl Builder { /// Look up a descriptor by name. fn lookup(&self, name: &str) -> Result<(usize, detail::Detail)> { - let table = self.template.hash_table; - let descs = self.template.descriptors; - let mask = table.len() - 1; - assert!((mask + 1).is_power_of_two()); - - let mut idx = simple_hash(name) as usize; - let mut step: usize = 0; - - loop { - idx = idx & mask; - let entry = table[idx] as usize; - if entry >= descs.len() { - return Err(Error::BadName); + match probe(self.template, name, simple_hash(name)) { + None => Err(Error::BadName), + Some(entry) => { + let d = &self.template.descriptors[self.template.hash_table[entry] as usize]; + Ok((d.offset as usize, d.detail)) } - let desc = &descs[entry]; - if desc.name == name { - return Ok((desc.offset as usize, desc.detail)); - } - step += 1; - assert!(step <= mask); - idx += step; } } } @@ -167,6 +152,7 @@ pub type Result = result::Result; /// code in other modules. pub mod detail { use std::fmt; + use constant_hash; /// An instruction group template. pub struct Template { @@ -207,6 +193,22 @@ pub mod detail { } } + /// The template contains a hash table for by-name lookup. + impl<'a> constant_hash::Table<&'a str> for Template { + fn len(&self) -> usize { + self.hash_table.len() + } + + fn key(&self, idx: usize) -> Option<&'a str> { + let e = self.hash_table[idx] as usize; + if e < self.descriptors.len() { + Some(self.descriptors[e].name) + } else { + None + } + } + } + /// A setting descriptor holds the information needed to generically set and print a setting. /// /// Each settings group will be represented as a constant DESCRIPTORS array. diff --git a/cranelift/src/libcretonne/simple_hash.rs b/cranelift/src/libcretonne/simple_hash.rs deleted file mode 100644 index 99cd80015c..0000000000 --- a/cranelift/src/libcretonne/simple_hash.rs +++ /dev/null @@ -1,21 +0,0 @@ -/// A primitive hash function for matching opcodes. -/// Must match `meta/constant_hash.py`. -pub fn simple_hash(s: &str) -> u32 { - let mut h: u32 = 5381; - for c in s.chars() { - h = (h ^ c as u32).wrapping_add(h.rotate_right(6)); - } - h -} - -#[cfg(test)] -mod tests { - use super::simple_hash; - - #[test] - fn basic() { - // c.f. meta/constant_hash.py tests. - assert_eq!(simple_hash("Hello"), 0x2fa70c01); - assert_eq!(simple_hash("world"), 0x5b0c31d5); - } -}