use std::mem::{align_of, size_of};
use std::ops::{Deref, DerefMut};
use std::sync::LazyLock;
use super::HandleStore;
pub const CACHE_LINE_SIZE: usize = 64;
pub const DOUBLE_CACHE_LINE: usize = 128;
pub const PAGE_SIZE: usize = 4096;
#[repr(C, align(64))]
#[derive(Debug)]
pub struct CacheAligned<T> {
value: T,
}
impl<T> CacheAligned<T> {
#[inline]
pub const fn new(value: T) -> Self {
Self { value }
}
#[inline]
pub fn into_inner(self) -> T {
self.value
}
}
impl<T: Default> Default for CacheAligned<T> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: Clone> Clone for CacheAligned<T> {
fn clone(&self) -> Self {
Self::new(self.value.clone())
}
}
impl<T: Copy> Copy for CacheAligned<T> {}
impl<T> Deref for CacheAligned<T> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T> DerefMut for CacheAligned<T> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
#[repr(C)]
#[derive(Debug)]
pub struct Padded<T, const N: usize> {
value: T,
_pad: [u8; N],
}
impl<T, const N: usize> Padded<T, N> {
#[inline]
pub fn new(value: T) -> Self {
Self {
value,
_pad: [0; N],
}
}
#[inline]
pub fn into_inner(self) -> T {
self.value
}
}
impl<T: Default, const N: usize> Default for Padded<T, N> {
fn default() -> Self {
Self::new(T::default())
}
}
impl<T: Clone, const N: usize> Clone for Padded<T, N> {
fn clone(&self) -> Self {
Self::new(self.value.clone())
}
}
impl<T: Copy, const N: usize> Copy for Padded<T, N> {}
impl<T, const N: usize> Deref for Padded<T, N> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.value
}
}
impl<T, const N: usize> DerefMut for Padded<T, N> {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.value
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct LayoutInfo {
pub size: usize,
pub align: usize,
pub cache_lines: usize,
pub padding: usize,
}
impl LayoutInfo {
#[inline]
pub fn of<T>() -> Self {
let size = size_of::<T>();
let align = align_of::<T>();
let cache_lines = (size + CACHE_LINE_SIZE - 1) / CACHE_LINE_SIZE;
Self {
size,
align,
cache_lines,
padding: 0, }
}
#[inline]
pub fn is_power_of_two_size(&self) -> bool {
self.size.is_power_of_two()
}
#[inline]
pub fn is_naturally_aligned(&self) -> bool {
self.align >= self.size || self.size.is_multiple_of(self.align)
}
#[inline]
pub fn is_cache_aligned(&self) -> bool {
self.align >= CACHE_LINE_SIZE
}
#[inline]
pub fn fits_in_cache_line(&self) -> bool {
self.size <= CACHE_LINE_SIZE
}
}
pub fn analyze_layout<T>(name: &str) -> LayoutInfo {
let info = LayoutInfo::of::<T>();
eprintln!(
"Layout of {}: size={}, align={}, cache_lines={}, cache_aligned={}, fits_in_line={}",
name,
info.size,
info.align,
info.cache_lines,
info.is_cache_aligned(),
info.fits_in_cache_line()
);
info
}
#[inline]
pub const fn cache_padding(size: usize) -> usize {
let remainder = size % CACHE_LINE_SIZE;
if remainder == 0 {
0
} else {
CACHE_LINE_SIZE - remainder
}
}
#[inline]
pub const fn round_to_cache_line(size: usize) -> usize {
(size + CACHE_LINE_SIZE - 1) & !(CACHE_LINE_SIZE - 1)
}
#[inline]
pub const fn round_to_page(size: usize) -> usize {
(size + PAGE_SIZE - 1) & !(PAGE_SIZE - 1)
}
#[macro_export]
macro_rules! assert_layout {
($ty:ty, size = $size:expr) => {
const _: () = assert!(
std::mem::size_of::<$ty>() == $size,
concat!(
"Size assertion failed for ",
stringify!($ty),
": expected ",
stringify!($size)
)
);
};
($ty:ty, align = $align:expr) => {
const _: () = assert!(
std::mem::align_of::<$ty>() == $align,
concat!(
"Alignment assertion failed for ",
stringify!($ty),
": expected ",
stringify!($align)
)
);
};
($ty:ty, size = $size:expr, align = $align:expr) => {
$crate::assert_layout!($ty, size = $size);
$crate::assert_layout!($ty, align = $align);
};
}
#[macro_export]
macro_rules! assert_cache_lines {
($ty:ty, $lines:expr) => {
const _: () = assert!(
std::mem::size_of::<$ty>() <= $lines * $crate::ffi::struct_layout::CACHE_LINE_SIZE,
concat!(
"Cache line assertion failed for ",
stringify!($ty),
": exceeds ",
stringify!($lines),
" cache lines"
)
);
};
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct PackedPoint {
pub x: f32,
pub y: f32,
}
const _: () = assert!(size_of::<PackedPoint>() == 8);
const _: () = assert!(64 / size_of::<PackedPoint>() == 8);
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct PackedRect {
pub x0: f32,
pub y0: f32,
pub x1: f32,
pub y1: f32,
}
const _: () = assert!(size_of::<PackedRect>() == 16);
const _: () = assert!(64 / size_of::<PackedRect>() == 4);
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PackedMatrix {
pub a: f32,
pub b: f32,
pub c: f32,
pub d: f32,
pub e: f32,
pub f: f32,
}
const _: () = assert!(size_of::<PackedMatrix>() == 24);
impl Default for PackedMatrix {
fn default() -> Self {
Self::IDENTITY
}
}
impl PackedMatrix {
pub const IDENTITY: Self = Self {
a: 1.0,
b: 0.0,
c: 0.0,
d: 1.0,
e: 0.0,
f: 0.0,
};
#[inline]
pub fn new(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
Self { a, b, c, d, e, f }
}
#[inline]
pub fn transform_point(&self, x: f32, y: f32) -> (f32, f32) {
(
x * self.a + y * self.c + self.e,
x * self.b + y * self.d + self.f,
)
}
#[inline]
pub fn concat(&self, other: &Self) -> Self {
Self {
a: self.a * other.a + self.b * other.c,
b: self.a * other.b + self.b * other.d,
c: self.c * other.a + self.d * other.c,
d: self.c * other.b + self.d * other.d,
e: self.e * other.a + self.f * other.c + other.e,
f: self.e * other.b + self.f * other.d + other.f,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Default)]
pub struct PackedQuad {
pub ul: PackedPoint, pub ur: PackedPoint, pub ll: PackedPoint, pub lr: PackedPoint, }
const _: () = assert!(size_of::<PackedQuad>() == 32);
const _: () = assert!(64 / size_of::<PackedQuad>() == 2);
impl PackedQuad {
#[inline]
pub fn from_rect(r: &PackedRect) -> Self {
Self {
ul: PackedPoint { x: r.x0, y: r.y0 },
ur: PackedPoint { x: r.x1, y: r.y0 },
ll: PackedPoint { x: r.x0, y: r.y1 },
lr: PackedPoint { x: r.x1, y: r.y1 },
}
}
#[inline]
pub fn bounds(&self) -> PackedRect {
PackedRect {
x0: self.ul.x.min(self.ur.x).min(self.ll.x).min(self.lr.x),
y0: self.ul.y.min(self.ur.y).min(self.ll.y).min(self.lr.y),
x1: self.ul.x.max(self.ur.x).max(self.ll.x).max(self.lr.x),
y1: self.ul.y.max(self.ur.y).max(self.ll.y).max(self.lr.y),
}
}
}
#[repr(C, align(64))]
#[derive(Debug, Clone, Copy)]
pub struct CacheLineColor {
pub c: f32, pub m: f32, pub y: f32, pub k: f32, pub alpha: f32, pub colorspace: u32, _pad: [u8; 40], }
impl Default for CacheLineColor {
fn default() -> Self {
Self {
c: 0.0,
m: 0.0,
y: 0.0,
k: 0.0,
alpha: 1.0,
colorspace: 0,
_pad: [0; 40],
}
}
}
const _: () = assert!(size_of::<CacheLineColor>() == 64);
const _: () = assert!(align_of::<CacheLineColor>() == 64);
#[repr(C)]
#[derive(Debug, Clone, Copy, Default)]
pub struct FfiLayoutInfo {
pub size: usize,
pub align: usize,
pub cache_lines: usize,
pub is_cache_aligned: i32,
pub fits_in_cache_line: i32,
}
impl From<LayoutInfo> for FfiLayoutInfo {
fn from(info: LayoutInfo) -> Self {
Self {
size: info.size,
align: info.align,
cache_lines: info.cache_lines,
is_cache_aligned: info.is_cache_aligned() as i32,
fits_in_cache_line: info.fits_in_cache_line() as i32,
}
}
}
pub static LAYOUT_INFOS: LazyLock<HandleStore<LayoutInfo>> = LazyLock::new(HandleStore::new);
use std::ffi::c_int;
#[unsafe(no_mangle)]
pub extern "C" fn fz_cache_line_size() -> usize {
CACHE_LINE_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_page_size() -> usize {
PAGE_SIZE
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_cache_padding(size: usize) -> usize {
cache_padding(size)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_round_to_cache_line(size: usize) -> usize {
round_to_cache_line(size)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_round_to_page(size: usize) -> usize {
round_to_page(size)
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_layout_point() -> FfiLayoutInfo {
LayoutInfo::of::<PackedPoint>().into()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_layout_rect() -> FfiLayoutInfo {
LayoutInfo::of::<PackedRect>().into()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_layout_matrix() -> FfiLayoutInfo {
LayoutInfo::of::<PackedMatrix>().into()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_layout_quad() -> FfiLayoutInfo {
LayoutInfo::of::<PackedQuad>().into()
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_fits_in_cache_lines(size: usize, lines: usize) -> c_int {
(size <= lines * CACHE_LINE_SIZE) as c_int
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_is_cache_aligned(ptr: *const std::ffi::c_void) -> c_int {
(ptr as usize).is_multiple_of(CACHE_LINE_SIZE) as c_int
}
#[unsafe(no_mangle)]
pub extern "C" fn fz_is_page_aligned(ptr: *const std::ffi::c_void) -> c_int {
(ptr as usize).is_multiple_of(PAGE_SIZE) as c_int
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_aligned() {
let aligned: CacheAligned<u64> = CacheAligned::new(42);
assert_eq!(*aligned, 42);
assert_eq!(align_of::<CacheAligned<u64>>(), 64);
let ptr = &aligned as *const _ as usize;
assert_eq!(ptr % 64, 0, "CacheAligned should be 64-byte aligned");
}
#[test]
fn test_padded() {
let padded: Padded<u32, 60> = Padded::new(123);
assert_eq!(*padded, 123);
assert_eq!(size_of::<Padded<u32, 60>>(), 64); }
#[test]
fn test_layout_info() {
let info = LayoutInfo::of::<u64>();
assert_eq!(info.size, 8);
assert_eq!(info.align, 8);
assert!(info.fits_in_cache_line());
}
#[test]
fn test_cache_padding() {
assert_eq!(cache_padding(0), 0);
assert_eq!(cache_padding(1), 63);
assert_eq!(cache_padding(32), 32);
assert_eq!(cache_padding(64), 0);
assert_eq!(cache_padding(65), 63);
}
#[test]
fn test_round_to_cache_line() {
assert_eq!(round_to_cache_line(0), 0);
assert_eq!(round_to_cache_line(1), 64);
assert_eq!(round_to_cache_line(64), 64);
assert_eq!(round_to_cache_line(65), 128);
assert_eq!(round_to_cache_line(100), 128);
}
#[test]
fn test_round_to_page() {
assert_eq!(round_to_page(0), 0);
assert_eq!(round_to_page(1), 4096);
assert_eq!(round_to_page(4096), 4096);
assert_eq!(round_to_page(4097), 8192);
}
#[test]
fn test_packed_point() {
let p = PackedPoint { x: 1.0, y: 2.0 };
assert_eq!(size_of_val(&p), 8);
}
#[test]
fn test_packed_rect() {
let r = PackedRect {
x0: 0.0,
y0: 0.0,
x1: 100.0,
y1: 100.0,
};
assert_eq!(size_of_val(&r), 16);
}
#[test]
fn test_packed_matrix() {
let m = PackedMatrix::IDENTITY;
assert_eq!(size_of_val(&m), 24);
let (x, y) = m.transform_point(10.0, 20.0);
assert_eq!(x, 10.0);
assert_eq!(y, 20.0);
}
#[test]
fn test_packed_matrix_concat() {
let scale = PackedMatrix::new(2.0, 0.0, 0.0, 2.0, 0.0, 0.0);
let translate = PackedMatrix::new(1.0, 0.0, 0.0, 1.0, 10.0, 20.0);
let combined = scale.concat(&translate);
let (x, y) = combined.transform_point(5.0, 5.0);
assert_eq!(x, 20.0); assert_eq!(y, 30.0); }
#[test]
fn test_packed_quad() {
let r = PackedRect {
x0: 0.0,
y0: 0.0,
x1: 10.0,
y1: 20.0,
};
let q = PackedQuad::from_rect(&r);
assert_eq!(q.ul.x, 0.0);
assert_eq!(q.ul.y, 0.0);
assert_eq!(q.lr.x, 10.0);
assert_eq!(q.lr.y, 20.0);
let bounds = q.bounds();
assert_eq!(bounds.x0, 0.0);
assert_eq!(bounds.y0, 0.0);
assert_eq!(bounds.x1, 10.0);
assert_eq!(bounds.y1, 20.0);
}
#[test]
fn test_cache_line_color() {
let c = CacheLineColor::default();
assert_eq!(size_of_val(&c), 64);
assert_eq!(align_of_val(&c), 64);
}
#[test]
fn test_ffi_cache_line_size() {
assert_eq!(fz_cache_line_size(), 64);
}
#[test]
fn test_ffi_layout_point() {
let info = fz_layout_point();
assert_eq!(info.size, 8);
assert_eq!(info.fits_in_cache_line, 1);
}
#[test]
fn test_ffi_layout_rect() {
let info = fz_layout_rect();
assert_eq!(info.size, 16);
assert_eq!(info.fits_in_cache_line, 1);
}
#[test]
fn test_ffi_layout_matrix() {
let info = fz_layout_matrix();
assert_eq!(info.size, 24);
assert_eq!(info.fits_in_cache_line, 1);
}
#[test]
fn test_ffi_is_cache_aligned() {
let aligned: CacheAligned<u64> = CacheAligned::new(0);
let ptr = &*aligned as *const u64 as *const std::ffi::c_void;
assert_eq!(fz_is_cache_aligned(ptr), 1);
}
#[test]
fn test_ffi_fits_in_cache_lines() {
assert_eq!(fz_fits_in_cache_lines(64, 1), 1);
assert_eq!(fz_fits_in_cache_lines(65, 1), 0);
assert_eq!(fz_fits_in_cache_lines(128, 2), 1);
assert_eq!(fz_fits_in_cache_lines(129, 2), 0);
}
}