use super::concrete_type::{ClosureTypeId, ConcreteType};
use super::struct_layout::{FieldInfo, FieldKind};
use crate::heap_value::{
AtomicData, ChannelData, DequeData, HashMapData, HashSetData, HeapKind, HeapValue,
IoHandleData, LazyData, MatrixData, MatrixSliceData, MutexData, NativeViewData,
PriorityQueueData, RangeData, TableViewData, TaskGroupData, TemporalData,
TraitObjectStorage, TypedObjectStorage,
};
use crate::native_kind::NativeKind;
use std::collections::HashMap;
use std::sync::Arc;
#[repr(C)]
pub struct SharedCell {
pub state: std::sync::atomic::AtomicU8,
_pad: [u8; 7],
pub value: std::cell::UnsafeCell<u64>,
kind: NativeKind,
}
unsafe impl Send for SharedCell {}
unsafe impl Sync for SharedCell {}
const _: () = {
assert!(std::mem::align_of::<SharedCell>() == 8);
assert!(std::mem::offset_of!(SharedCell, state) == 0);
assert!(std::mem::offset_of!(SharedCell, value) == 8);
};
pub const SHARED_CELL_STATE_OFFSET: i32 = 0;
pub const SHARED_CELL_VALUE_OFFSET: i32 = 8;
const _: () = {
assert!(SHARED_CELL_VALUE_OFFSET as usize == std::mem::offset_of!(SharedCell, value));
assert!(SHARED_CELL_STATE_OFFSET as usize == std::mem::offset_of!(SharedCell, state));
};
pub const SHARED_CELL_LOCKED: u8 = 1;
pub const SHARED_CELL_UNLOCKED: u8 = 0;
impl SharedCell {
#[inline]
pub fn new(value: u64, kind: NativeKind) -> Self {
Self {
state: std::sync::atomic::AtomicU8::new(SHARED_CELL_UNLOCKED),
_pad: [0; 7],
value: std::cell::UnsafeCell::new(value),
kind,
}
}
#[inline]
pub fn kind(&self) -> NativeKind {
self.kind
}
#[inline]
pub fn lock(&self) -> SharedCellGuard<'_> {
use std::sync::atomic::Ordering;
if self
.state
.compare_exchange(
SHARED_CELL_UNLOCKED,
SHARED_CELL_LOCKED,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
{
return SharedCellGuard { cell: self };
}
self.lock_contended();
SharedCellGuard { cell: self }
}
#[cold]
#[inline(never)]
pub fn lock_contended(&self) {
use std::sync::atomic::Ordering;
loop {
while self.state.load(Ordering::Relaxed) != SHARED_CELL_UNLOCKED {
std::hint::spin_loop();
}
if self
.state
.compare_exchange_weak(
SHARED_CELL_UNLOCKED,
SHARED_CELL_LOCKED,
Ordering::Acquire,
Ordering::Relaxed,
)
.is_ok()
{
return;
}
}
}
#[inline]
pub unsafe fn unlock(&self) {
use std::sync::atomic::Ordering;
self.state.store(SHARED_CELL_UNLOCKED, Ordering::Release);
}
}
impl Drop for SharedCell {
fn drop(&mut self) {
let bits = unsafe { *self.value.get() };
if bits == 0 {
return;
}
unsafe {
match self.kind {
NativeKind::String => {
Arc::decrement_strong_count(bits as *const String);
}
NativeKind::StringV2 => {
use crate::v2::heap_element::HeapElement;
crate::v2::string_obj::StringObj::release_elem(
bits as *const crate::v2::string_obj::StringObj,
);
}
NativeKind::DecimalV2 => {
use crate::v2::heap_element::HeapElement;
crate::v2::decimal_obj::DecimalObj::release_elem(
bits as *const crate::v2::decimal_obj::DecimalObj,
);
}
NativeKind::Ptr(hk) => match hk {
HeapKind::String => {
Arc::decrement_strong_count(bits as *const String);
}
HeapKind::TypedArray => {
crate::v2::typed_array::release_v2_typed_array(bits as *mut u8);
}
HeapKind::TypedObject => {
use crate::v2::heap_element::HeapElement;
TypedObjectStorage::release_elem(
bits as *const TypedObjectStorage,
);
}
HeapKind::HashMap => {
Arc::decrement_strong_count(
bits as *const crate::heap_value::HashMapKindedRef,
);
}
HeapKind::HashSet => {
Arc::decrement_strong_count(bits as *const HashSetData);
}
HeapKind::Deque => {
Arc::decrement_strong_count(bits as *const DequeData);
}
HeapKind::Channel => {
Arc::decrement_strong_count(bits as *const ChannelData);
}
HeapKind::Mutex => {
Arc::decrement_strong_count(bits as *const MutexData);
}
HeapKind::Atomic => {
Arc::decrement_strong_count(bits as *const AtomicData);
}
HeapKind::Lazy => {
Arc::decrement_strong_count(bits as *const LazyData);
}
HeapKind::TraitObject => {
use crate::v2::heap_element::HeapElement;
TraitObjectStorage::release_elem(
bits as *const TraitObjectStorage,
);
}
HeapKind::Decimal => {
Arc::decrement_strong_count(bits as *const rust_decimal::Decimal);
}
HeapKind::BigInt => {
Arc::decrement_strong_count(bits as *const i64);
}
HeapKind::DataTable => {
Arc::decrement_strong_count(bits as *const crate::datatable::DataTable);
}
HeapKind::IoHandle => {
Arc::decrement_strong_count(bits as *const IoHandleData);
}
HeapKind::NativeView => {
Arc::decrement_strong_count(bits as *const NativeViewData);
}
HeapKind::Content => {
Arc::decrement_strong_count(bits as *const crate::content::ContentNode);
}
HeapKind::Instant => {
Arc::decrement_strong_count(bits as *const std::time::Instant);
}
HeapKind::Temporal => {
Arc::decrement_strong_count(bits as *const TemporalData);
}
HeapKind::TableView => {
Arc::decrement_strong_count(bits as *const TableViewData);
}
HeapKind::TaskGroup => {
Arc::decrement_strong_count(bits as *const TaskGroupData);
}
HeapKind::FilterExpr => {
Arc::decrement_strong_count(bits as *const crate::value::FilterNode);
}
HeapKind::Reference => {
Arc::decrement_strong_count(bits as *const crate::reference::RefTarget);
}
HeapKind::Iterator => {
Arc::decrement_strong_count(
bits as *const crate::iterator_state::IteratorState,
);
}
HeapKind::PriorityQueue => {
Arc::decrement_strong_count(bits as *const PriorityQueueData);
}
HeapKind::Range => {
Arc::decrement_strong_count(bits as *const RangeData);
}
HeapKind::Result => {
Arc::decrement_strong_count(
bits as *const crate::heap_value::ResultData,
);
}
HeapKind::Option => {
Arc::decrement_strong_count(
bits as *const crate::heap_value::OptionData,
);
}
HeapKind::Char => {
}
HeapKind::Closure => {
Arc::decrement_strong_count(bits as *const HeapValue);
}
HeapKind::Future => {
}
HeapKind::ModuleFn => {
}
HeapKind::SharedCell => {
Arc::decrement_strong_count(bits as *const SharedCell);
}
HeapKind::Matrix => {
Arc::decrement_strong_count(bits as *const MatrixData);
}
HeapKind::MatrixSlice => {
Arc::decrement_strong_count(bits as *const MatrixSliceData);
}
HeapKind::NativeScalar => {
debug_assert!(
false,
"SharedCell::drop: NativeScalar kinded carrier pending \
phase-2c kinded redesign (ADR-006 §2.7.4)"
);
}
},
NativeKind::Float64
| NativeKind::NullableFloat64
| NativeKind::Int8
| NativeKind::NullableInt8
| NativeKind::UInt8
| NativeKind::NullableUInt8
| NativeKind::Int16
| NativeKind::NullableInt16
| NativeKind::UInt16
| NativeKind::NullableUInt16
| NativeKind::Int32
| NativeKind::NullableInt32
| NativeKind::UInt32
| NativeKind::NullableUInt32
| NativeKind::Int64
| NativeKind::NullableInt64
| NativeKind::UInt64
| NativeKind::NullableUInt64
| NativeKind::IntSize
| NativeKind::NullableIntSize
| NativeKind::UIntSize
| NativeKind::NullableUIntSize
| NativeKind::Bool
| NativeKind::Float32
| NativeKind::Char
| NativeKind::Null => {}
}
}
}
}
pub struct SharedCellGuard<'a> {
cell: &'a SharedCell,
}
impl<'a> std::ops::Deref for SharedCellGuard<'a> {
type Target = u64;
#[inline]
fn deref(&self) -> &u64 {
unsafe { &*self.cell.value.get() }
}
}
impl<'a> std::ops::DerefMut for SharedCellGuard<'a> {
#[inline]
fn deref_mut(&mut self) -> &mut u64 {
unsafe { &mut *self.cell.value.get() }
}
}
impl<'a> Drop for SharedCellGuard<'a> {
#[inline]
fn drop(&mut self) {
unsafe { self.cell.unlock() };
}
}
impl std::fmt::Debug for SharedCell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("SharedCell").finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum CaptureKind {
Immutable,
OwnedMutable,
Shared,
}
pub const HEAP_CLOSURE_HEADER_SIZE: usize = 16;
pub const STACK_CLOSURE_HEADER_SIZE: usize = 8;
#[repr(C)]
pub struct TypedClosureHeader {
pub header: super::heap_header::HeapHeader, pub function_id: u32, pub type_id: u32, }
#[repr(C)]
pub struct StackClosure {
pub function_id: u32, pub type_id: u32, }
const _: () = {
assert!(std::mem::size_of::<StackClosure>() == 8);
assert!(std::mem::size_of::<TypedClosureHeader>() == 16);
};
#[derive(Debug, Clone)]
pub struct ClosureLayout {
pub capture_types: Vec<ConcreteType>,
pub captures: Vec<FieldInfo>,
pub capture_kinds: Vec<CaptureKind>,
pub capture_native_kinds: Vec<NativeKind>,
pub heap_capture_mask: u64,
pub owned_mutable_capture_mask: u64,
pub shared_capture_mask: u64,
pub captures_size: usize,
pub captures_align: usize,
}
pub fn native_kind_from_concrete_type(ty: &ConcreteType) -> NativeKind {
match ty {
ConcreteType::F64 => NativeKind::Float64,
ConcreteType::I64 => NativeKind::Int64,
ConcreteType::I32 => NativeKind::Int32,
ConcreteType::I16 => NativeKind::Int16,
ConcreteType::I8 => NativeKind::Int8,
ConcreteType::U64 => NativeKind::UInt64,
ConcreteType::U32 => NativeKind::UInt32,
ConcreteType::U16 => NativeKind::UInt16,
ConcreteType::U8 => NativeKind::UInt8,
ConcreteType::Bool => NativeKind::Bool,
ConcreteType::String => NativeKind::String,
ConcreteType::Array(_) => NativeKind::Ptr(HeapKind::TypedArray),
ConcreteType::HashMap(_, _) => NativeKind::Ptr(HeapKind::HashMap),
ConcreteType::Struct(_) => NativeKind::Ptr(HeapKind::TypedObject),
ConcreteType::Enum(_) => NativeKind::Ptr(HeapKind::TypedObject),
ConcreteType::Closure(_) => NativeKind::Ptr(HeapKind::Closure),
ConcreteType::Function(_) => NativeKind::Ptr(HeapKind::Closure),
ConcreteType::Pointer(_) => NativeKind::Ptr(HeapKind::NativeView),
ConcreteType::Tuple(_) => NativeKind::Ptr(HeapKind::TypedObject),
ConcreteType::Decimal => NativeKind::Ptr(HeapKind::Decimal),
ConcreteType::BigInt => NativeKind::Ptr(HeapKind::BigInt),
ConcreteType::DateTime => NativeKind::Ptr(HeapKind::Temporal),
ConcreteType::Option(_) => NativeKind::Ptr(HeapKind::TypedObject),
ConcreteType::Result(_, _) => NativeKind::Ptr(HeapKind::TypedObject),
ConcreteType::HashSet(_) => NativeKind::Ptr(HeapKind::HashSet),
ConcreteType::Deque(_) => NativeKind::Ptr(HeapKind::Deque),
ConcreteType::PriorityQueue => NativeKind::Ptr(HeapKind::PriorityQueue),
ConcreteType::Channel(_) => NativeKind::Ptr(HeapKind::Channel),
ConcreteType::Mutex(_) => NativeKind::Ptr(HeapKind::Mutex),
ConcreteType::Atomic => NativeKind::Ptr(HeapKind::Atomic),
ConcreteType::Lazy(_) => NativeKind::Ptr(HeapKind::Lazy),
ConcreteType::F32 => NativeKind::Float32,
ConcreteType::Char => NativeKind::Char,
ConcreteType::Void => panic!(
"ClosureLayout: ConcreteType::Void is not a well-formed capture type \
(ADR-006 §2.7.8 / Q10 — kinds must be concrete at construction; \
no Bool-default fallback)"
),
}
}
impl ClosureLayout {
pub fn from_capture_types(capture_types: &[ConcreteType], kinds: &[CaptureKind]) -> Self {
let native_kinds: Vec<NativeKind> = capture_types
.iter()
.map(native_kind_from_concrete_type)
.collect();
Self::from_capture_types_with_native_kinds(capture_types, kinds, &native_kinds)
}
pub fn from_capture_types_with_native_kinds(
capture_types: &[ConcreteType],
kinds: &[CaptureKind],
native_kinds: &[NativeKind],
) -> Self {
assert_eq!(
capture_types.len(),
kinds.len(),
"from_capture_types_with_native_kinds: capture_types ({}) and kinds ({}) must have equal length",
capture_types.len(),
kinds.len()
);
assert_eq!(
capture_types.len(),
native_kinds.len(),
"from_capture_types_with_native_kinds: capture_types ({}) and native_kinds ({}) must have equal length \
(ADR-006 §2.7.8 / Q10 — lockstep parallel-`Vec<NativeKind>` invariant)",
capture_types.len(),
native_kinds.len()
);
if capture_types.len() > 64 {
panic!(
"closure has {} captures; capture masks are limited to 64 captures",
capture_types.len()
);
}
let mut current_offset: usize = 0;
let mut captures = Vec::with_capacity(capture_types.len());
let mut heap_mask: u64 = 0;
let mut owned_mutable_mask: u64 = 0;
let mut shared_mask: u64 = 0;
let mut max_align: usize = 1;
for (i, (ty, capture_kind)) in capture_types.iter().zip(kinds.iter()).enumerate() {
let kind = match capture_kind {
CaptureKind::Immutable => ty.to_field_kind(),
CaptureKind::OwnedMutable | CaptureKind::Shared => FieldKind::Ptr,
};
let align = kind.alignment();
let size = kind.size();
current_offset = (current_offset + align - 1) & !(align - 1);
captures.push(FieldInfo {
name: format!("capture_{i}"),
kind,
offset: current_offset,
size,
});
match capture_kind {
CaptureKind::Immutable => {
if kind == FieldKind::Ptr {
heap_mask |= 1u64 << i;
}
}
CaptureKind::OwnedMutable => {
owned_mutable_mask |= 1u64 << i;
}
CaptureKind::Shared => {
shared_mask |= 1u64 << i;
}
}
if align > max_align {
max_align = align;
}
current_offset += size;
}
debug_assert_eq!(
heap_mask & owned_mutable_mask,
0,
"heap/owned_mutable masks overlap"
);
debug_assert_eq!(heap_mask & shared_mask, 0, "heap/shared masks overlap");
debug_assert_eq!(
owned_mutable_mask & shared_mask,
0,
"owned_mutable/shared masks overlap"
);
let captures_align = if capture_types.is_empty() {
8
} else {
max_align.max(8)
};
let captures_size = (current_offset + captures_align - 1) & !(captures_align - 1);
ClosureLayout {
capture_types: capture_types.to_vec(),
captures,
capture_kinds: kinds.to_vec(),
capture_native_kinds: native_kinds.to_vec(),
heap_capture_mask: heap_mask,
owned_mutable_capture_mask: owned_mutable_mask,
shared_capture_mask: shared_mask,
captures_size,
captures_align,
}
}
#[inline]
pub fn capture_count(&self) -> usize {
self.captures.len()
}
#[inline]
pub fn capture_offset(&self, i: usize) -> usize {
self.captures[i].offset
}
#[inline]
pub fn capture_kind(&self, i: usize) -> FieldKind {
self.captures[i].kind
}
#[inline]
pub fn capture_inner_kind(&self, i: usize) -> FieldKind {
self.capture_types[i].to_field_kind()
}
#[inline]
pub fn heap_capture_offset(&self, i: usize) -> usize {
HEAP_CLOSURE_HEADER_SIZE + self.captures[i].offset
}
#[inline]
pub fn stack_capture_offset(&self, i: usize) -> usize {
STACK_CLOSURE_HEADER_SIZE + self.captures[i].offset
}
#[inline]
pub fn total_heap_size(&self) -> usize {
HEAP_CLOSURE_HEADER_SIZE + self.captures_size
}
#[inline]
pub fn total_stack_size(&self) -> usize {
STACK_CLOSURE_HEADER_SIZE + self.captures_size
}
#[inline]
pub fn is_heap_capture(&self, i: usize) -> bool {
self.heap_capture_mask & (1u64 << i) != 0
}
#[inline]
pub fn is_owned_mutable_capture(&self, i: usize) -> bool {
self.owned_mutable_capture_mask & (1u64 << i) != 0
}
#[inline]
pub fn is_shared_capture(&self, i: usize) -> bool {
self.shared_capture_mask & (1u64 << i) != 0
}
#[inline]
pub fn capture_storage_kind(&self, i: usize) -> CaptureKind {
self.capture_kinds[i]
}
#[inline]
pub fn capture_native_kind(&self, i: usize) -> NativeKind {
self.capture_native_kinds[i]
}
}
#[derive(Debug, Default, Clone)]
pub struct ClosureRegistry {
layouts: Vec<ClosureLayout>,
signature_to_id: HashMap<(Vec<ConcreteType>, Vec<CaptureKind>), ClosureTypeId>,
}
impl ClosureRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn intern(&mut self, capture_types: Vec<ConcreteType>) -> ClosureTypeId {
let kinds = vec![CaptureKind::Immutable; capture_types.len()];
self.intern_with_kinds(capture_types, kinds)
}
pub fn intern_with_kinds(
&mut self,
capture_types: Vec<ConcreteType>,
capture_kinds: Vec<CaptureKind>,
) -> ClosureTypeId {
assert_eq!(
capture_types.len(),
capture_kinds.len(),
"intern_with_kinds: types and kinds must match in length",
);
let key = (capture_types, capture_kinds);
if let Some(&id) = self.signature_to_id.get(&key) {
return id;
}
let id = ClosureTypeId(self.layouts.len() as u32);
let layout = ClosureLayout::from_capture_types(&key.0, &key.1);
self.layouts.push(layout);
self.signature_to_id.insert(key, id);
id
}
pub fn get(&self, id: ClosureTypeId) -> Option<&ClosureLayout> {
self.layouts.get(id.0 as usize)
}
pub fn len(&self) -> usize {
self.layouts.len()
}
pub fn is_empty(&self) -> bool {
self.layouts.is_empty()
}
pub fn iter(&self) -> impl Iterator<Item = (ClosureTypeId, &ClosureLayout)> {
self.layouts
.iter()
.enumerate()
.map(|(i, l)| (ClosureTypeId(i as u32), l))
}
pub fn lookup(&self, capture_types: &[ConcreteType]) -> Option<ClosureTypeId> {
let kinds = vec![CaptureKind::Immutable; capture_types.len()];
self.signature_to_id
.get(&(capture_types.to_vec(), kinds))
.copied()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::v2::concrete_type::{ConcreteType, StructLayoutId};
fn immutable_layout(types: &[ConcreteType]) -> ClosureLayout {
let kinds = vec![CaptureKind::Immutable; types.len()];
ClosureLayout::from_capture_types(types, &kinds)
}
#[test]
fn test_empty_captures() {
let layout = immutable_layout(&[]);
assert_eq!(layout.capture_count(), 0);
assert_eq!(layout.captures_size, 0);
assert_eq!(layout.captures_align, 8);
assert_eq!(layout.heap_capture_mask, 0);
assert_eq!(layout.total_heap_size(), 16);
assert_eq!(layout.total_stack_size(), 8);
}
#[test]
fn test_single_f64_capture() {
let layout = immutable_layout(&[ConcreteType::F64]);
assert_eq!(layout.capture_count(), 1);
assert_eq!(layout.capture_offset(0), 0);
assert_eq!(layout.capture_kind(0), FieldKind::F64);
assert_eq!(layout.heap_capture_offset(0), 16);
assert_eq!(layout.stack_capture_offset(0), 8);
assert_eq!(layout.captures_size, 8);
assert_eq!(layout.heap_capture_mask, 0);
assert_eq!(layout.total_heap_size(), 24);
assert_eq!(layout.total_stack_size(), 16);
}
#[test]
fn test_two_f64_captures() {
let layout = immutable_layout(&[ConcreteType::F64, ConcreteType::F64]);
assert_eq!(layout.capture_count(), 2);
assert_eq!(layout.capture_offset(0), 0);
assert_eq!(layout.capture_offset(1), 8);
assert_eq!(layout.captures_size, 16);
assert_eq!(layout.heap_capture_mask, 0);
assert_eq!(layout.total_heap_size(), 32);
assert_eq!(layout.total_stack_size(), 24);
}
#[test]
fn test_single_i64_capture() {
let layout = immutable_layout(&[ConcreteType::I64]);
assert_eq!(layout.capture_offset(0), 0);
assert_eq!(layout.capture_kind(0), FieldKind::I64);
assert_eq!(layout.captures_size, 8);
assert_eq!(layout.total_heap_size(), 24);
assert_eq!(layout.total_stack_size(), 16);
}
#[test]
fn test_mixed_f64_i32_ptr() {
let layout =
immutable_layout(&[ConcreteType::F64, ConcreteType::I32, ConcreteType::String]);
assert_eq!(layout.capture_count(), 3);
assert_eq!(layout.capture_offset(0), 0);
assert_eq!(layout.capture_offset(1), 8);
assert_eq!(layout.capture_offset(2), 16);
assert_eq!(layout.capture_kind(0), FieldKind::F64);
assert_eq!(layout.capture_kind(1), FieldKind::I32);
assert_eq!(layout.capture_kind(2), FieldKind::Ptr);
assert_eq!(layout.captures_size, 24);
assert_eq!(layout.heap_capture_mask, 0b100);
assert!(layout.is_heap_capture(2));
assert!(!layout.is_heap_capture(0));
assert!(!layout.is_heap_capture(1));
assert_eq!(layout.total_heap_size(), 40);
assert_eq!(layout.total_stack_size(), 32);
}
#[test]
fn test_single_heap_typed_capture_string() {
let layout = immutable_layout(&[ConcreteType::String]);
assert_eq!(layout.capture_offset(0), 0);
assert_eq!(layout.capture_kind(0), FieldKind::Ptr);
assert_eq!(layout.captures_size, 8);
assert_eq!(layout.heap_capture_mask, 0b1);
assert!(layout.is_heap_capture(0));
assert_eq!(layout.total_heap_size(), 24);
assert_eq!(layout.total_stack_size(), 16);
}
#[test]
fn test_array_capture_is_heap() {
let arr = ConcreteType::Array(Box::new(ConcreteType::I64));
let layout = immutable_layout(&[arr]);
assert_eq!(layout.capture_kind(0), FieldKind::Ptr);
assert_eq!(layout.heap_capture_mask, 0b1);
}
#[test]
fn test_struct_capture_is_heap() {
let s = ConcreteType::placeholder_struct(StructLayoutId(42));
let layout = immutable_layout(&[s]);
assert_eq!(layout.capture_kind(0), FieldKind::Ptr);
assert_eq!(layout.heap_capture_mask, 0b1);
}
#[test]
fn test_small_field_packing() {
let layout = immutable_layout(&[
ConcreteType::Bool,
ConcreteType::I8,
ConcreteType::I16,
ConcreteType::I32,
]);
assert_eq!(layout.capture_offset(0), 0);
assert_eq!(layout.capture_offset(1), 1);
assert_eq!(layout.capture_offset(2), 2);
assert_eq!(layout.capture_offset(3), 4);
assert_eq!(layout.captures_size, 8);
assert_eq!(layout.heap_capture_mask, 0);
}
#[test]
fn test_heap_mask_positions() {
let arr = ConcreteType::Array(Box::new(ConcreteType::F64));
let layout = immutable_layout(&[
ConcreteType::I32,
ConcreteType::String,
ConcreteType::F64,
arr,
]);
assert_eq!(layout.heap_capture_mask, 0b1010);
assert!(!layout.is_heap_capture(0));
assert!(layout.is_heap_capture(1));
assert!(!layout.is_heap_capture(2));
assert!(layout.is_heap_capture(3));
}
#[test]
fn test_offsets_relative_and_absolute_agree() {
let layout =
immutable_layout(&[ConcreteType::F64, ConcreteType::I64, ConcreteType::String]);
for i in 0..layout.capture_count() {
assert_eq!(layout.heap_capture_offset(i), 16 + layout.capture_offset(i));
assert_eq!(layout.stack_capture_offset(i), 8 + layout.capture_offset(i));
}
}
#[test]
fn test_size_rounded_up_for_trailing_small_field() {
let layout = immutable_layout(&[ConcreteType::Bool]);
assert_eq!(layout.captures_size, 8);
assert_eq!(layout.total_heap_size(), 24);
assert_eq!(layout.total_stack_size(), 16);
}
#[test]
fn test_registry_empty() {
let r = ClosureRegistry::new();
assert_eq!(r.len(), 0);
assert!(r.is_empty());
}
#[test]
fn test_registry_same_signature_returns_same_id() {
let mut r = ClosureRegistry::new();
let id_a = r.intern(vec![ConcreteType::I64]);
let id_b = r.intern(vec![ConcreteType::I64]);
assert_eq!(id_a, id_b);
assert_eq!(r.len(), 1);
}
#[test]
fn test_registry_different_signatures_returns_different_ids() {
let mut r = ClosureRegistry::new();
let id_empty = r.intern(vec![]);
let id_i64 = r.intern(vec![ConcreteType::I64]);
let id_f64 = r.intern(vec![ConcreteType::F64]);
let id_i64_f64 = r.intern(vec![ConcreteType::I64, ConcreteType::F64]);
let id_f64_i64 = r.intern(vec![ConcreteType::F64, ConcreteType::I64]);
assert_ne!(id_empty, id_i64);
assert_ne!(id_i64, id_f64);
assert_ne!(id_i64_f64, id_f64_i64, "order matters in the signature");
assert_eq!(r.len(), 5);
}
#[test]
fn test_registry_roundtrip_and_layout_retrieval() {
let mut r = ClosureRegistry::new();
let id = r.intern(vec![ConcreteType::F64, ConcreteType::String]);
let layout = r.get(id).expect("layout should exist");
assert_eq!(layout.capture_count(), 2);
assert_eq!(layout.capture_kind(0), FieldKind::F64);
assert_eq!(layout.capture_kind(1), FieldKind::Ptr);
assert_eq!(layout.heap_capture_mask, 0b10);
}
#[test]
fn test_registry_lookup_without_intern() {
let mut r = ClosureRegistry::new();
assert_eq!(r.lookup(&[ConcreteType::I64]), None);
let id = r.intern(vec![ConcreteType::I64]);
assert_eq!(r.lookup(&[ConcreteType::I64]), Some(id));
assert_eq!(r.lookup(&[ConcreteType::F64]), None);
}
#[test]
fn test_registry_iter() {
let mut r = ClosureRegistry::new();
r.intern(vec![]);
r.intern(vec![ConcreteType::I64]);
r.intern(vec![ConcreteType::F64]);
let collected: Vec<_> = r.iter().collect();
assert_eq!(collected.len(), 3);
assert_eq!(collected[0].0, ClosureTypeId(0));
assert_eq!(collected[1].0, ClosureTypeId(1));
assert_eq!(collected[2].0, ClosureTypeId(2));
}
#[test]
fn test_registry_ids_are_sequential_from_zero() {
let mut r = ClosureRegistry::new();
let a = r.intern(vec![ConcreteType::I64]);
let b = r.intern(vec![ConcreteType::F64]);
let c = r.intern(vec![ConcreteType::Bool]);
assert_eq!(a, ClosureTypeId(0));
assert_eq!(b, ClosureTypeId(1));
assert_eq!(c, ClosureTypeId(2));
}
#[test]
fn test_registry_nested_types_are_distinct() {
let mut r = ClosureRegistry::new();
let arr_i64 = ConcreteType::Array(Box::new(ConcreteType::I64));
let arr_f64 = ConcreteType::Array(Box::new(ConcreteType::F64));
let id1 = r.intern(vec![arr_i64]);
let id2 = r.intern(vec![arr_f64]);
assert_ne!(id1, id2);
}
#[test]
fn test_sizeof_stack_closure_is_8() {
assert_eq!(std::mem::size_of::<StackClosure>(), 8);
}
#[test]
fn test_sizeof_typed_closure_header_is_16() {
assert_eq!(std::mem::size_of::<TypedClosureHeader>(), 16);
}
#[test]
fn test_header_constants() {
assert_eq!(HEAP_CLOSURE_HEADER_SIZE, 16);
assert_eq!(STACK_CLOSURE_HEADER_SIZE, 8);
}
#[test]
fn capture_inner_kind_immutable_matches_capture_kind() {
let kinds = vec![
CaptureKind::Immutable,
CaptureKind::Immutable,
CaptureKind::Immutable,
];
let layout = ClosureLayout::from_capture_types(
&[ConcreteType::I64, ConcreteType::F64, ConcreteType::String],
&kinds,
);
assert_eq!(layout.capture_kind(0), FieldKind::I64);
assert_eq!(layout.capture_inner_kind(0), FieldKind::I64);
assert_eq!(layout.capture_kind(1), FieldKind::F64);
assert_eq!(layout.capture_inner_kind(1), FieldKind::F64);
assert_eq!(layout.capture_kind(2), FieldKind::Ptr);
assert_eq!(layout.capture_inner_kind(2), FieldKind::Ptr);
}
#[test]
fn capture_inner_kind_owned_mutable_returns_interior() {
let kinds = vec![CaptureKind::OwnedMutable];
let layout = ClosureLayout::from_capture_types(&[ConcreteType::I64], &kinds);
assert_eq!(layout.capture_kind(0), FieldKind::Ptr);
assert_eq!(layout.capture_inner_kind(0), FieldKind::I64);
}
#[test]
fn capture_inner_kind_owned_mutable_f64() {
let kinds = vec![CaptureKind::OwnedMutable];
let layout = ClosureLayout::from_capture_types(&[ConcreteType::F64], &kinds);
assert_eq!(layout.capture_kind(0), FieldKind::Ptr);
assert_eq!(layout.capture_inner_kind(0), FieldKind::F64);
}
#[test]
fn capture_inner_kind_shared_returns_interior() {
let kinds = vec![CaptureKind::Shared];
let layout = ClosureLayout::from_capture_types(&[ConcreteType::Bool], &kinds);
assert_eq!(layout.capture_kind(0), FieldKind::Ptr);
assert_eq!(layout.capture_inner_kind(0), FieldKind::Bool);
}
#[test]
fn capture_inner_kind_owned_mutable_ptr() {
let kinds = vec![CaptureKind::OwnedMutable];
let layout = ClosureLayout::from_capture_types(&[ConcreteType::String], &kinds);
assert_eq!(layout.capture_kind(0), FieldKind::Ptr);
assert_eq!(layout.capture_inner_kind(0), FieldKind::Ptr);
}
#[test]
fn capture_native_kinds_inline_scalars() {
let layout = immutable_layout(&[
ConcreteType::F64,
ConcreteType::I64,
ConcreteType::I32,
ConcreteType::Bool,
]);
assert_eq!(layout.capture_native_kinds.len(), 4);
assert_eq!(layout.capture_native_kind(0), NativeKind::Float64);
assert_eq!(layout.capture_native_kind(1), NativeKind::Int64);
assert_eq!(layout.capture_native_kind(2), NativeKind::Int32);
assert_eq!(layout.capture_native_kind(3), NativeKind::Bool);
}
#[test]
fn capture_native_kinds_string() {
let layout = immutable_layout(&[ConcreteType::String]);
assert_eq!(layout.capture_native_kind(0), NativeKind::String);
}
#[test]
fn capture_native_kinds_typed_array() {
let arr = ConcreteType::Array(Box::new(ConcreteType::F64));
let layout = immutable_layout(&[arr]);
assert_eq!(
layout.capture_native_kind(0),
NativeKind::Ptr(HeapKind::TypedArray)
);
}
#[test]
fn capture_native_kinds_struct() {
let s = ConcreteType::placeholder_struct(StructLayoutId(7));
let layout = immutable_layout(&[s]);
assert_eq!(
layout.capture_native_kind(0),
NativeKind::Ptr(HeapKind::TypedObject)
);
}
#[test]
fn capture_native_kinds_lockstep_with_capture_types() {
let layout = immutable_layout(&[
ConcreteType::F64,
ConcreteType::String,
ConcreteType::I32,
]);
assert_eq!(
layout.capture_types.len(),
layout.capture_native_kinds.len()
);
assert_eq!(layout.capture_types.len(), layout.capture_kinds.len());
assert_eq!(layout.capture_types.len(), layout.captures.len());
}
#[test]
fn capture_native_kinds_from_explicit_constructor() {
let types = vec![ConcreteType::Pointer(Box::new(ConcreteType::Void))];
let kinds = vec![CaptureKind::Immutable];
let native_kinds = vec![NativeKind::Ptr(HeapKind::HashMap)];
let layout = ClosureLayout::from_capture_types_with_native_kinds(
&types,
&kinds,
&native_kinds,
);
assert_eq!(
layout.capture_native_kind(0),
NativeKind::Ptr(HeapKind::HashMap)
);
assert_eq!(layout.capture_kind(0), FieldKind::Ptr);
}
#[test]
#[should_panic(expected = "must have equal length")]
fn capture_native_kinds_explicit_constructor_length_mismatch_panics() {
let types = vec![ConcreteType::F64, ConcreteType::I64];
let kinds = vec![CaptureKind::Immutable, CaptureKind::Immutable];
let native_kinds = vec![NativeKind::Float64]; let _ = ClosureLayout::from_capture_types_with_native_kinds(
&types,
&kinds,
&native_kinds,
);
}
#[test]
fn native_kind_from_concrete_type_inline_scalars() {
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::F64),
NativeKind::Float64
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::I64),
NativeKind::Int64
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::I32),
NativeKind::Int32
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::I16),
NativeKind::Int16
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::I8),
NativeKind::Int8
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::U64),
NativeKind::UInt64
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::U32),
NativeKind::UInt32
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::U16),
NativeKind::UInt16
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::U8),
NativeKind::UInt8
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::Bool),
NativeKind::Bool
);
}
#[test]
fn native_kind_from_concrete_type_heap_arms() {
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::String),
NativeKind::String
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::Decimal),
NativeKind::Ptr(HeapKind::Decimal)
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::BigInt),
NativeKind::Ptr(HeapKind::BigInt)
);
assert_eq!(
native_kind_from_concrete_type(&ConcreteType::DateTime),
NativeKind::Ptr(HeapKind::Temporal)
);
}
#[test]
#[should_panic(expected = "Void is not a well-formed capture type")]
fn native_kind_from_concrete_type_void_panics() {
let _ = native_kind_from_concrete_type(&ConcreteType::Void);
}
}