Generate a constant hash table for recognizing opcodes.

Use a simple quadratically probed, open addressed hash table. We could use a
parfect hash function, but it would take longer to compute in Python, and this
is not in the critical path performancewise.
This commit is contained in:
Jakob Stoklund Olesen
2016-04-07 20:24:21 -07:00
parent 3dcd2f8e58
commit 24e0828d20
3 changed files with 142 additions and 2 deletions

View File

@@ -8,8 +8,12 @@
use std::fmt::{self, Display, Formatter};
use std::mem;
// The `Opcode` enum and the `opcode_name` function are generated from the meta instruction
// descriptions.
// Include code generated by `meta/gen_instr.py`. This file contains:
//
// - The `pub enum Opcode` definition with all known opcodes,
// - The private `fn opcode_name(Opcode) -> &'static str` function, and
// - The hash table `const OPCODE_HASH_TABLE: [Opcode; N]`.
//
include!(concat!(env!("OUT_DIR"), "/opcodes.rs"));
impl Display for Opcode {
@@ -18,6 +22,46 @@ impl Display for Opcode {
}
}
// A primitive hash function for matching opcodes.
// Must match `meta/constant_hash.py`.
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
}
impl Opcode {
/// Parse an Opcode name from a string.
pub fn from_str(s: &str) -> Option<Opcode> {
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];
if entry == Opcode::NotAnOpcode {
return None;
}
if *opcode_name(entry) == *s {
return Some(entry);
}
// 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;
}
}
}
/// 64-bit immediate integer operand.
///
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
@@ -178,6 +222,13 @@ mod tests {
assert_eq!(format!("{:?}", Opcode::IaddImm), "IaddImm");
assert_eq!(format!("{}", Opcode::IaddImm), "iadd_imm");
// Check the matcher.
assert_eq!(Opcode::from_str("iadd"), Some(Opcode::Iadd));
assert_eq!(Opcode::from_str("iadd_imm"), Some(Opcode::IaddImm));
assert_eq!(Opcode::from_str("iadd\0"), None);
assert_eq!(Opcode::from_str(""), None);
assert_eq!(Opcode::from_str("\0"), None);
}
#[test]