use shape_vm::bytecode::FunctionHash;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub enum FunctionEntry {
Native(*const u8),
Interpreted(u16),
Pending(u16),
}
unsafe impl Send for FunctionEntry {}
pub struct MixedFunctionTable {
entries: Vec<FunctionEntry>,
hash_to_entry: HashMap<FunctionHash, usize>,
}
impl MixedFunctionTable {
pub fn new() -> Self {
Self {
entries: Vec::new(),
hash_to_entry: HashMap::new(),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: Vec::with_capacity(capacity),
hash_to_entry: HashMap::with_capacity(capacity),
}
}
pub fn insert(&mut self, id: usize, entry: FunctionEntry) {
if id >= self.entries.len() {
self.entries.resize(id + 1, FunctionEntry::Interpreted(0));
}
self.entries[id] = entry;
}
pub fn get(&self, id: usize) -> Option<&FunctionEntry> {
self.entries.get(id)
}
pub fn insert_by_hash(&mut self, hash: FunctionHash, entry: FunctionEntry) {
let id = self.entries.len();
self.entries.push(entry);
self.hash_to_entry.insert(hash, id);
}
pub fn get_by_hash(&self, hash: &FunctionHash) -> Option<&FunctionEntry> {
self.hash_to_entry
.get(hash)
.and_then(|&id| self.entries.get(id))
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn native_count(&self) -> usize {
self.entries
.iter()
.filter(|e| matches!(e, FunctionEntry::Native(_)))
.count()
}
pub fn interpreted_count(&self) -> usize {
self.entries
.iter()
.filter(|e| matches!(e, FunctionEntry::Interpreted(_)))
.count()
}
pub fn pending_count(&self) -> usize {
self.entries
.iter()
.filter(|e| matches!(e, FunctionEntry::Pending(_)))
.count()
}
pub fn iter(&self) -> impl Iterator<Item = (usize, &FunctionEntry)> {
self.entries.iter().enumerate()
}
pub fn promote_to_native(&mut self, id: usize, ptr: *const u8) {
if id < self.entries.len() {
self.entries[id] = FunctionEntry::Native(ptr);
}
}
}
impl Default for MixedFunctionTable {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_table() {
let table = MixedFunctionTable::new();
assert!(table.is_empty());
assert_eq!(table.len(), 0);
assert_eq!(table.native_count(), 0);
assert_eq!(table.interpreted_count(), 0);
}
#[test]
fn insert_and_get() {
let mut table = MixedFunctionTable::new();
let fake_ptr = 0xDEAD_BEEF as *const u8;
table.insert(0, FunctionEntry::Native(fake_ptr));
table.insert(1, FunctionEntry::Interpreted(1));
table.insert(2, FunctionEntry::Pending(2));
assert_eq!(table.len(), 3);
assert_eq!(table.native_count(), 1);
assert_eq!(table.interpreted_count(), 1);
assert_eq!(table.pending_count(), 1);
assert!(matches!(table.get(0), Some(FunctionEntry::Native(_))));
assert!(matches!(table.get(1), Some(FunctionEntry::Interpreted(1))));
assert!(matches!(table.get(2), Some(FunctionEntry::Pending(2))));
assert!(table.get(3).is_none());
}
#[test]
fn insert_by_hash_and_lookup() {
let mut table = MixedFunctionTable::new();
let hash = FunctionHash([42u8; 32]);
let fake_ptr = 0xCAFE as *const u8;
table.insert_by_hash(hash, FunctionEntry::Native(fake_ptr));
assert!(matches!(
table.get_by_hash(&hash),
Some(FunctionEntry::Native(_))
));
assert!(table.get_by_hash(&FunctionHash::ZERO).is_none());
}
#[test]
fn promote_pending_to_native() {
let mut table = MixedFunctionTable::new();
table.insert(0, FunctionEntry::Pending(0));
assert_eq!(table.pending_count(), 1);
let fake_ptr = 0xBEEF as *const u8;
table.promote_to_native(0, fake_ptr);
assert_eq!(table.pending_count(), 0);
assert_eq!(table.native_count(), 1);
}
#[test]
fn sparse_insert_fills_gaps() {
let mut table = MixedFunctionTable::new();
table.insert(5, FunctionEntry::Native(0x1 as *const u8));
assert_eq!(table.len(), 6);
assert!(matches!(table.get(0), Some(FunctionEntry::Interpreted(0))));
assert!(matches!(table.get(4), Some(FunctionEntry::Interpreted(0))));
assert!(matches!(table.get(5), Some(FunctionEntry::Native(_))));
}
}