use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct StructLayout {
pub name: String,
pub size: u64,
pub alignment: Option<u64>,
pub members: Vec<MemberLayout>,
pub metrics: LayoutMetrics,
#[serde(skip_serializing_if = "Option::is_none")]
pub source_location: Option<SourceLocation>,
}
#[derive(Debug, Clone, Serialize)]
pub struct MemberLayout {
pub name: String,
pub type_name: String,
pub offset: Option<u64>,
pub size: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bit_offset: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub bit_size: Option<u64>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub is_atomic: bool,
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct LayoutMetrics {
pub total_size: u64,
pub useful_size: u64,
pub padding_bytes: u64,
pub padding_percentage: f64,
pub cache_lines_spanned: u32,
pub cache_line_density: f64,
pub padding_holes: Vec<PaddingHole>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub partial: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub false_sharing: Option<FalseSharingAnalysis>,
}
#[derive(Debug, Clone, Serialize)]
pub struct PaddingHole {
pub offset: u64,
pub size: u64,
pub after_member: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct SourceLocation {
pub file: String,
pub line: u64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct FalseSharingWarning {
pub member_a: String,
pub member_b: String,
pub cache_line: u64,
pub gap_bytes: i64,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct CacheLineSpanningWarning {
pub member: String,
pub type_name: String,
pub offset: u64,
pub size: u64,
pub start_cache_line: u64,
pub end_cache_line: u64,
pub lines_spanned: u64,
}
#[derive(Debug, Clone, Serialize, Default, PartialEq, Eq)]
pub struct FalseSharingAnalysis {
pub atomic_members: Vec<AtomicMember>,
pub warnings: Vec<FalseSharingWarning>,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub spanning_warnings: Vec<CacheLineSpanningWarning>,
}
#[derive(Debug, Clone, Serialize, PartialEq, Eq)]
pub struct AtomicMember {
pub name: String,
pub type_name: String,
pub offset: u64,
pub size: u64,
pub cache_line: u64,
pub end_cache_line: u64,
pub spans_cache_lines: bool,
}
impl StructLayout {
pub fn new(name: String, size: u64, alignment: Option<u64>) -> Self {
Self {
name,
size,
alignment,
members: Vec::new(),
metrics: LayoutMetrics::default(),
source_location: None,
}
}
}
impl MemberLayout {
pub fn new(name: String, type_name: String, offset: Option<u64>, size: Option<u64>) -> Self {
Self { name, type_name, offset, size, bit_offset: None, bit_size: None, is_atomic: false }
}
pub fn with_atomic(mut self, is_atomic: bool) -> Self {
self.is_atomic = is_atomic;
self
}
pub fn end_offset(&self) -> Option<u64> {
match (self.offset, self.size) {
(Some(off), Some(sz)) => off.checked_add(sz),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn member_end_offset_handles_overflow() {
let member = MemberLayout::new("a".to_string(), "u8".to_string(), Some(u64::MAX), Some(1));
assert_eq!(member.end_offset(), None);
}
#[test]
fn member_end_offset_ok() {
let member = MemberLayout::new("a".to_string(), "u8".to_string(), Some(4), Some(2));
assert_eq!(member.end_offset(), Some(6));
}
#[test]
fn member_with_atomic_flag() {
let member = MemberLayout::new("a".to_string(), "u32".to_string(), Some(0), Some(4))
.with_atomic(true);
assert!(member.is_atomic);
}
#[test]
fn struct_new_initializes_fields() {
let s = StructLayout::new("Foo".to_string(), 16, Some(8));
assert_eq!(s.name, "Foo");
assert_eq!(s.size, 16);
assert_eq!(s.alignment, Some(8));
assert!(s.members.is_empty());
}
}