use std::sync::{
atomic::{AtomicBool, AtomicU32, AtomicU64, Ordering},
Arc,
};
use crate::metadata::{tables::TableId, token::Token};
pub use crate::utils::{hash_blob, hash_guid, hash_string};
static NEXT_CHANGE_ID: AtomicU64 = AtomicU64::new(1);
const UNRESOLVED: u32 = u32::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ChangeRefKind {
String,
Blob,
Guid,
UserString,
TableRow(TableId),
}
impl ChangeRefKind {
#[must_use]
pub fn is_heap(&self) -> bool {
matches!(
self,
Self::String | Self::Blob | Self::Guid | Self::UserString
)
}
#[must_use]
pub fn is_table(&self) -> bool {
matches!(self, Self::TableRow(_))
}
#[must_use]
pub fn table_id(&self) -> Option<TableId> {
match self {
Self::TableRow(id) => Some(*id),
_ => None,
}
}
}
#[derive(Debug)]
pub struct ChangeRef {
id: u64,
kind: ChangeRefKind,
content_hash: u64,
resolved: AtomicBool,
resolved_offset: AtomicU32,
resolved_token: AtomicU32,
}
pub type ChangeRefRc = Arc<ChangeRef>;
impl ChangeRef {
#[must_use]
pub fn new_heap(kind: ChangeRefKind, content_hash: u64) -> Self {
debug_assert!(kind.is_heap(), "new_heap called with non-heap kind");
Self {
id: NEXT_CHANGE_ID.fetch_add(1, Ordering::Relaxed),
kind,
content_hash,
resolved: AtomicBool::new(false),
resolved_offset: AtomicU32::new(UNRESOLVED),
resolved_token: AtomicU32::new(UNRESOLVED),
}
}
#[must_use]
pub fn new_table_row(table_id: TableId) -> Self {
Self {
id: NEXT_CHANGE_ID.fetch_add(1, Ordering::Relaxed),
kind: ChangeRefKind::TableRow(table_id),
content_hash: 0, resolved: AtomicBool::new(false),
resolved_offset: AtomicU32::new(UNRESOLVED),
resolved_token: AtomicU32::new(UNRESOLVED),
}
}
#[must_use]
pub fn from_heap_offset(kind: ChangeRefKind, offset: u32) -> Self {
debug_assert!(kind.is_heap(), "from_heap_offset called with non-heap kind");
Self {
id: NEXT_CHANGE_ID.fetch_add(1, Ordering::Relaxed),
kind,
content_hash: 0,
resolved: AtomicBool::new(true),
resolved_offset: AtomicU32::new(offset),
resolved_token: AtomicU32::new(UNRESOLVED),
}
}
#[must_use]
pub fn from_token(token: Token) -> Self {
let table_id =
TableId::from_token_type(token.table()).expect("Token has unrecognized table type");
Self {
id: NEXT_CHANGE_ID.fetch_add(1, Ordering::Relaxed),
kind: ChangeRefKind::TableRow(table_id),
content_hash: 0,
resolved: AtomicBool::new(true),
resolved_offset: AtomicU32::new(UNRESOLVED),
resolved_token: AtomicU32::new(token.value()),
}
}
#[must_use]
pub fn id(&self) -> u64 {
self.id
}
#[must_use]
pub fn kind(&self) -> ChangeRefKind {
self.kind
}
#[must_use]
pub fn content_hash(&self) -> u64 {
self.content_hash
}
#[must_use]
pub fn is_resolved(&self) -> bool {
self.resolved.load(Ordering::Acquire)
}
#[must_use]
pub fn offset(&self) -> Option<u32> {
if !self.kind.is_heap() {
return None;
}
if !self.is_resolved() {
return None;
}
let offset = self.resolved_offset.load(Ordering::Acquire);
if offset == UNRESOLVED {
None
} else {
Some(offset)
}
}
#[must_use]
pub fn token(&self) -> Option<Token> {
if !self.kind.is_table() {
return None;
}
if !self.is_resolved() {
return None;
}
let token_val = self.resolved_token.load(Ordering::Acquire);
if token_val == UNRESOLVED {
None
} else {
Some(Token::new(token_val))
}
}
pub fn resolve_to_offset(&self, offset: u32) {
debug_assert!(
self.kind.is_heap(),
"resolve_to_offset called on non-heap ChangeRef"
);
self.resolved_offset.store(offset, Ordering::Release);
self.resolved.store(true, Ordering::Release);
}
pub fn resolve_to_token(&self, token: Token) {
debug_assert!(
self.kind.is_table(),
"resolve_to_token called on non-table ChangeRef"
);
self.resolved_token.store(token.value(), Ordering::Release);
self.resolved.store(true, Ordering::Release);
}
#[must_use]
pub fn into_rc(self) -> ChangeRefRc {
Arc::new(self)
}
#[must_use]
pub fn placeholder(&self) -> u32 {
if self.kind.is_heap() {
0x8000_0000 | ((self.id & 0x7FFF_FFFF) as u32)
} else {
0x0080_0000 | ((self.id & 0x007F_FFFF) as u32)
}
}
#[must_use]
pub fn is_placeholder(value: u32) -> bool {
value & 0x8000_0000 != 0 || value >= 0x0080_0000
}
#[must_use]
pub fn id_from_placeholder(value: u32) -> Option<u64> {
if value & 0x8000_0000 != 0 {
Some(u64::from(value & 0x7FFF_FFFF))
} else if value >= 0x0080_0000 {
Some(u64::from(value & 0x007F_FFFF))
} else {
None
}
}
#[must_use]
pub fn placeholder_token(&self) -> Option<Token> {
let table_id = self.kind.table_id()?;
Some(Token::from_parts(table_id, self.placeholder()))
}
#[must_use]
pub fn is_placeholder_token(token: Token) -> bool {
token.row() >= 0x0080_0000
}
#[must_use]
pub fn row(&self) -> u32 {
if self.kind.is_table() {
self.placeholder()
} else {
0
}
}
}
impl PartialEq for ChangeRef {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Eq for ChangeRef {}
impl std::hash::Hash for ChangeRef {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
impl ChangeRef {
#[must_use]
pub fn new_string(content_hash: u64) -> ChangeRefRc {
Arc::new(Self::new_heap(ChangeRefKind::String, content_hash))
}
#[must_use]
pub fn new_blob(content_hash: u64) -> ChangeRefRc {
Arc::new(Self::new_heap(ChangeRefKind::Blob, content_hash))
}
#[must_use]
pub fn new_guid(content_hash: u64) -> ChangeRefRc {
Arc::new(Self::new_heap(ChangeRefKind::Guid, content_hash))
}
#[must_use]
pub fn new_userstring(content_hash: u64) -> ChangeRefRc {
Arc::new(Self::new_heap(ChangeRefKind::UserString, content_hash))
}
#[must_use]
pub fn new_row(table_id: TableId) -> ChangeRefRc {
Arc::new(Self::new_table_row(table_id))
}
#[must_use]
pub fn existing_heap(kind: ChangeRefKind, offset: u32) -> ChangeRefRc {
Arc::new(Self::from_heap_offset(kind, offset))
}
#[must_use]
pub fn existing_token(token: Token) -> ChangeRefRc {
Arc::new(Self::from_token(token))
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::thread;
#[test]
fn test_changeref_heap_creation() {
let ref1 = ChangeRef::new_string(12345);
assert_eq!(ref1.kind(), ChangeRefKind::String);
assert!(!ref1.is_resolved());
assert!(ref1.offset().is_none());
assert!(ref1.token().is_none());
}
#[test]
fn test_changeref_table_creation() {
let ref1 = ChangeRef::new_row(TableId::TypeDef);
assert_eq!(ref1.kind(), ChangeRefKind::TableRow(TableId::TypeDef));
assert!(!ref1.is_resolved());
assert!(ref1.offset().is_none());
assert!(ref1.token().is_none());
}
#[test]
fn test_changeref_from_existing_offset() {
let ref1 = ChangeRef::existing_heap(ChangeRefKind::String, 100);
assert!(ref1.is_resolved());
assert_eq!(ref1.offset(), Some(100));
}
#[test]
fn test_changeref_from_existing_token() {
let token = Token::new(0x0200_0001); let ref1 = ChangeRef::existing_token(token);
assert!(ref1.is_resolved());
assert_eq!(ref1.token(), Some(token));
}
#[test]
fn test_changeref_resolve_offset() {
let ref1 = ChangeRef::new_string(12345);
assert!(!ref1.is_resolved());
ref1.resolve_to_offset(500);
assert!(ref1.is_resolved());
assert_eq!(ref1.offset(), Some(500));
}
#[test]
fn test_changeref_resolve_token() {
let ref1 = ChangeRef::new_row(TableId::MethodDef);
assert!(!ref1.is_resolved());
let token = Token::new(0x0600_0042);
ref1.resolve_to_token(token);
assert!(ref1.is_resolved());
assert_eq!(ref1.token(), Some(token));
}
#[test]
fn test_changeref_unique_ids() {
let ref1 = ChangeRef::new_string(100);
let ref2 = ChangeRef::new_string(100);
let ref3 = ChangeRef::new_blob(200);
assert_ne!(ref1.id(), ref2.id());
assert_ne!(ref2.id(), ref3.id());
}
#[test]
fn test_changeref_equality() {
let ref1 = ChangeRef::new_heap(ChangeRefKind::String, 100);
let id1 = ref1.id();
assert_eq!(ref1, ref1);
let ref2 = ChangeRef::new_heap(ChangeRefKind::String, 100);
assert_ne!(ref1, ref2);
assert_eq!(ref1.id(), id1);
}
#[test]
fn test_changeref_kind_methods() {
let string_kind = ChangeRefKind::String;
assert!(string_kind.is_heap());
assert!(!string_kind.is_table());
assert!(string_kind.table_id().is_none());
let table_kind = ChangeRefKind::TableRow(TableId::TypeDef);
assert!(!table_kind.is_heap());
assert!(table_kind.is_table());
assert_eq!(table_kind.table_id(), Some(TableId::TypeDef));
}
#[test]
fn test_hash_functions() {
let hash1 = hash_string("hello");
let hash2 = hash_string("hello");
let hash3 = hash_string("world");
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
let blob1 = hash_blob(&[1, 2, 3]);
let blob2 = hash_blob(&[1, 2, 3]);
let blob3 = hash_blob(&[4, 5, 6]);
assert_eq!(blob1, blob2);
assert_ne!(blob1, blob3);
}
#[test]
fn test_changeref_thread_safety() {
let ref1 = ChangeRef::new_string(100);
let ref1_arc = Arc::new(ref1);
let handles: Vec<_> = (0..10)
.map(|i| {
let ref_clone = Arc::clone(&ref1_arc);
thread::spawn(move || {
let _ = ref_clone.is_resolved();
let _ = ref_clone.offset();
let _ = ref_clone.kind();
if i == 0 {
ref_clone.resolve_to_offset(999);
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert!(ref1_arc.is_resolved());
assert_eq!(ref1_arc.offset(), Some(999));
}
}