use super::heap_header::{HeapHeader, HEAP_KIND_V2_DECIMAL};
use rust_decimal::Decimal;
#[repr(C)]
pub struct DecimalObj {
pub header: HeapHeader,
pub value: Decimal,
}
impl DecimalObj {
pub fn new(value: Decimal) -> *mut Self {
let layout = std::alloc::Layout::new::<Self>();
let ptr = unsafe { std::alloc::alloc(layout) as *mut Self };
assert!(!ptr.is_null(), "allocation failed for DecimalObj");
unsafe {
(*ptr).header = HeapHeader::new(HEAP_KIND_V2_DECIMAL);
(*ptr).value = value;
}
ptr
}
pub unsafe fn value(ptr: *const Self) -> Decimal {
unsafe { (*ptr).value }
}
pub unsafe fn drop(ptr: *mut Self) {
let layout = std::alloc::Layout::new::<Self>();
unsafe { std::alloc::dealloc(ptr as *mut u8, layout) };
}
pub const OFFSET_VALUE: usize = 8;
}
const _: () = {
assert!(std::mem::size_of::<DecimalObj>() == 24);
assert!(std::mem::align_of::<DecimalObj>() == 4);
};
unsafe impl super::heap_element::HeapElement for DecimalObj {
unsafe fn release_elem(ptr: *const Self) {
if unsafe { super::refcount::v2_release(&(*ptr).header) } {
unsafe { Self::drop(ptr as *mut Self) };
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::prelude::FromPrimitive;
#[test]
fn test_size_of_decimal_obj() {
assert_eq!(std::mem::size_of::<DecimalObj>(), 24);
assert_eq!(std::mem::align_of::<DecimalObj>(), 4);
}
#[test]
fn test_create_and_read_decimal() {
unsafe {
let d = Decimal::from_f64(3.14).unwrap();
let ptr = DecimalObj::new(d);
assert_eq!(DecimalObj::value(ptr), d);
assert_eq!((*ptr).header.kind(), HEAP_KIND_V2_DECIMAL);
assert_eq!((*ptr).header.get_refcount(), 1);
DecimalObj::drop(ptr);
}
}
#[test]
fn test_create_zero_decimal() {
unsafe {
let d = Decimal::ZERO;
let ptr = DecimalObj::new(d);
assert_eq!(DecimalObj::value(ptr), Decimal::ZERO);
DecimalObj::drop(ptr);
}
}
#[test]
fn test_create_max_decimal() {
unsafe {
let d = Decimal::MAX;
let ptr = DecimalObj::new(d);
assert_eq!(DecimalObj::value(ptr), Decimal::MAX);
DecimalObj::drop(ptr);
}
}
#[test]
fn test_drop_does_not_leak() {
unsafe {
for i in 0..200 {
let d = Decimal::from_f64(i as f64 * 0.123).unwrap_or(Decimal::ZERO);
let ptr = DecimalObj::new(d);
DecimalObj::drop(ptr);
}
}
}
#[test]
fn test_field_offsets() {
unsafe {
let ptr = DecimalObj::new(Decimal::ONE);
let base = ptr as usize;
let value_offset = &(*ptr).value as *const _ as usize - base;
assert_eq!(value_offset, DecimalObj::OFFSET_VALUE, "value must be at offset 8");
DecimalObj::drop(ptr);
}
}
#[test]
fn test_refcount_starts_at_one() {
unsafe {
let ptr = DecimalObj::new(Decimal::ONE);
assert_eq!((*ptr).header.get_refcount(), 1);
DecimalObj::drop(ptr);
}
}
#[test]
fn test_refcount_retain_release() {
use crate::v2::refcount::{v2_get_refcount, v2_release, v2_retain};
unsafe {
let ptr = DecimalObj::new(Decimal::ONE);
let header = &(*ptr).header as *const HeapHeader;
assert_eq!(v2_get_refcount(header), 1);
v2_retain(header);
assert_eq!(v2_get_refcount(header), 2);
assert!(!v2_release(header)); assert_eq!(v2_get_refcount(header), 1);
DecimalObj::drop(ptr);
}
}
#[test]
fn test_heap_element_release_elem_to_zero() {
use crate::v2::heap_element::HeapElement;
unsafe {
let ptr = DecimalObj::new(Decimal::ONE);
DecimalObj::release_elem(ptr);
}
}
#[test]
fn test_heap_element_release_elem_held_share() {
use crate::v2::heap_element::HeapElement;
use crate::v2::refcount::{v2_get_refcount, v2_retain};
unsafe {
let ptr = DecimalObj::new(Decimal::ONE);
let header = &(*ptr).header as *const HeapHeader;
v2_retain(header); DecimalObj::release_elem(ptr); assert_eq!(v2_get_refcount(header), 1);
DecimalObj::drop(ptr);
}
}
}