Add a generic implementation of quadratic hash table probing.

We have multiple pre-computed constant hash tables that all use the same
quadratic probing algorithm.

Add a constant_hash Rust module to match the meta/constant_hash.py module.

Move the simple_hash() function into constant_hash. Its Python equivalent is in
the constant_hash.py module.
This commit is contained in:
Jakob Stoklund Olesen
2016-08-18 12:32:46 -07:00
parent f17ea61593
commit 722a3a6ae6
5 changed files with 90 additions and 42 deletions

View File

@@ -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<K: Copy + Eq> {
/// 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<K>;
}
/// 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<K: Copy + Eq, T: Table<K> + ?Sized>(table: &T, key: K, hash: usize) -> Option<usize> {
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);
}
}

View File

@@ -64,30 +64,25 @@ impl FromStr for Opcode {
/// Parse an Opcode name from a string.
fn from_str(s: &str) -> Result<Opcode, &'static str> {
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]),
}
}
}

View File

@@ -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;

View File

@@ -23,7 +23,7 @@
use std::fmt;
use std::result;
use simple_hash::simple_hash;
use constant_hash::simple_hash;
/// A string-based configurator for settings groups.
///

View File

@@ -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);
}
}