use crate::context::{RaisedFault, RuntimeContext};
use crate::dynamic_key::DynamicKey;
use crate::gc::GcRef;
use crate::graph::GraphOracle;
use crate::heap::{Heap, Safepoint};
use crate::roots::{NativeScope, Rooted};
use crate::scalars;
use crate::{
collections::VecPayload,
descriptor::{Payload, TypeDescriptor},
repr_c_vec::ReprCVec,
};
pub use praxis_stdlib::abi::{AbiKind, AbiRet, AbiSig, Effect, RuntimeSymbol};
pub const RUNTIME_ABI_VERSION: u32 = 20;
pub fn assert_abi_version() {
assert_eq!(
COMPILER_EXPECTED_ABI_VERSION, RUNTIME_ABI_VERSION,
"compiler/runtime ABI version mismatch: compiler expected \
{COMPILER_EXPECTED_ABI_VERSION}, runtime reports {RUNTIME_ABI_VERSION}. \
This is a build inconsistency; rebuild the workspace."
);
}
const COMPILER_EXPECTED_ABI_VERSION: u32 = 20;
#[must_use]
pub fn address(symbol: RuntimeSymbol) -> *const u8 {
let ptr: *const () = match symbol {
RuntimeSymbol::AllocBool => praxis_alloc_bool as *const (),
RuntimeSymbol::AllocChar => praxis_alloc_char as *const (),
RuntimeSymbol::AllocClosure => praxis_alloc_closure as *const (),
RuntimeSymbol::AllocEnum => praxis_alloc_enum as *const (),
RuntimeSymbol::AllocFloat => praxis_alloc_float as *const (),
RuntimeSymbol::AllocInt => praxis_alloc_int as *const (),
RuntimeSymbol::AllocRecord => praxis_alloc_record as *const (),
RuntimeSymbol::AllocText => praxis_alloc_text as *const (),
RuntimeSymbol::AllocTuple => praxis_alloc_tuple as *const (),
RuntimeSymbol::AllocUnit => praxis_alloc_unit as *const (),
RuntimeSymbol::AllocVarCell => praxis_alloc_var_cell as *const (),
RuntimeSymbol::Assert => praxis_assert as *const (),
RuntimeSymbol::AStarDistance => praxis_a_star_distance as *const (),
RuntimeSymbol::AStarPath => praxis_a_star_path as *const (),
RuntimeSymbol::Bfs => praxis_bfs as *const (),
RuntimeSymbol::BfsDistance => praxis_bfs_distance as *const (),
RuntimeSymbol::BfsPath => praxis_bfs_path as *const (),
RuntimeSymbol::BitsetContains => praxis_bitset_contains as *const (),
RuntimeSymbol::BitsetInsert => praxis_bitset_insert as *const (),
RuntimeSymbol::BitsetIsEmpty => praxis_bitset_is_empty as *const (),
RuntimeSymbol::BitsetItems => praxis_bitset_items as *const (),
RuntimeSymbol::BitsetLen => praxis_bitset_len as *const (),
RuntimeSymbol::BitsetNew => praxis_bitset_new as *const (),
RuntimeSymbol::Breakpoint => praxis_breakpoint as *const (),
RuntimeSymbol::BitsetRemove => praxis_bitset_remove as *const (),
RuntimeSymbol::BoolLoad => praxis_bool_load as *const (),
RuntimeSymbol::CharLoad => praxis_char_load as *const (),
RuntimeSymbol::CharToInt => praxis_char_to_int as *const (),
RuntimeSymbol::CharToText => praxis_char_to_text as *const (),
RuntimeSymbol::CheckFault => praxis_check_fault as *const (),
RuntimeSymbol::ClosureCapture => praxis_closure_capture as *const (),
RuntimeSymbol::ClosureFnPtr => praxis_closure_fn_ptr as *const (),
RuntimeSymbol::ClosureSetCapture => praxis_closure_set_capture as *const (),
RuntimeSymbol::CounterGet => praxis_counter_get as *const (),
RuntimeSymbol::CounterInc => praxis_counter_inc as *const (),
RuntimeSymbol::CounterIsEmpty => praxis_counter_is_empty as *const (),
RuntimeSymbol::CounterLen => praxis_counter_len as *const (),
RuntimeSymbol::CounterKeys => praxis_counter_keys as *const (),
RuntimeSymbol::CounterNew => praxis_counter_new as *const (),
RuntimeSymbol::CounterSet => praxis_counter_set as *const (),
RuntimeSymbol::CounterValues => praxis_counter_values as *const (),
RuntimeSymbol::DequeGet => praxis_deque_get as *const (),
RuntimeSymbol::DequeSet => praxis_deque_set as *const (),
RuntimeSymbol::DequeIsEmpty => praxis_deque_is_empty as *const (),
RuntimeSymbol::DequeLen => praxis_deque_len as *const (),
RuntimeSymbol::DequeNew => praxis_deque_new as *const (),
RuntimeSymbol::DequePopBack => praxis_deque_pop_back as *const (),
RuntimeSymbol::DequePopFront => praxis_deque_pop_front as *const (),
RuntimeSymbol::DequePushBack => praxis_deque_push_back as *const (),
RuntimeSymbol::DequePushFront => praxis_deque_push_front as *const (),
RuntimeSymbol::Dbg => praxis_dbg as *const (),
RuntimeSymbol::Dfs => praxis_dfs as *const (),
RuntimeSymbol::DfsDistance => praxis_dfs_distance as *const (),
RuntimeSymbol::DfsPath => praxis_dfs_path as *const (),
RuntimeSymbol::Dijkstra => praxis_dijkstra as *const (),
RuntimeSymbol::DijkstraDistance => praxis_dijkstra_distance as *const (),
RuntimeSymbol::DijkstraPath => praxis_dijkstra_path as *const (),
RuntimeSymbol::EnumPayload => praxis_enum_payload as *const (),
RuntimeSymbol::EnumSetPayload => praxis_enum_set_payload as *const (),
RuntimeSymbol::EnumTag => praxis_enum_tag as *const (),
RuntimeSymbol::FloatAbs => praxis_float_abs as *const (),
RuntimeSymbol::FloatCeil => praxis_float_ceil as *const (),
RuntimeSymbol::FloatE => praxis_float_e as *const (),
RuntimeSymbol::FloatFloor => praxis_float_floor as *const (),
RuntimeSymbol::FloatIsInfinite => praxis_float_is_infinite as *const (),
RuntimeSymbol::FloatIsNan => praxis_float_is_nan as *const (),
RuntimeSymbol::FloatLoad => praxis_float_load as *const (),
RuntimeSymbol::FloatMax => praxis_float_max as *const (),
RuntimeSymbol::FloatMin => praxis_float_min as *const (),
RuntimeSymbol::FloatPi => praxis_float_pi as *const (),
RuntimeSymbol::FloatRound => praxis_float_round as *const (),
RuntimeSymbol::FloatSign => praxis_float_sign as *const (),
RuntimeSymbol::FloatSqrt => praxis_float_sqrt as *const (),
RuntimeSymbol::FloatToInt => praxis_float_to_int as *const (),
RuntimeSymbol::FloatToText => praxis_float_to_text as *const (),
RuntimeSymbol::FloodFill => praxis_flood_fill as *const (),
RuntimeSymbol::GetInput => praxis_get_input as *const (),
RuntimeSymbol::GridAround4 => praxis_grid_around4 as *const (),
RuntimeSymbol::GridAround8 => praxis_grid_around8 as *const (),
RuntimeSymbol::GridCells => praxis_grid_cells as *const (),
RuntimeSymbol::GridColumn => praxis_grid_column as *const (),
RuntimeSymbol::GridContains => praxis_grid_contains as *const (),
RuntimeSymbol::GridCount4 => praxis_grid_count4 as *const (),
RuntimeSymbol::GridCount4Where => praxis_grid_count4_where as *const (),
RuntimeSymbol::GridCount8 => praxis_grid_count8 as *const (),
RuntimeSymbol::GridCount8Where => praxis_grid_count8_where as *const (),
RuntimeSymbol::GridFind => praxis_grid_find as *const (),
RuntimeSymbol::GridFindAll => praxis_grid_find_all as *const (),
RuntimeSymbol::GridGet => praxis_grid_get as *const (),
RuntimeSymbol::GridHeight => praxis_grid_height as *const (),
RuntimeSymbol::GridNeighbors4 => praxis_grid_neighbors4 as *const (),
RuntimeSymbol::GridNeighbors8 => praxis_grid_neighbors8 as *const (),
RuntimeSymbol::GridFilled => praxis_grid_filled as *const (),
RuntimeSymbol::GridNew => praxis_grid_new as *const (),
RuntimeSymbol::GridPositions => praxis_grid_positions as *const (),
RuntimeSymbol::GridRotateLeft => praxis_grid_rotate_left as *const (),
RuntimeSymbol::GridRotateRight => praxis_grid_rotate_right as *const (),
RuntimeSymbol::GridRow => praxis_grid_row as *const (),
RuntimeSymbol::GridSet => praxis_grid_set as *const (),
RuntimeSymbol::GridTranspose => praxis_grid_transpose as *const (),
RuntimeSymbol::GridWidth => praxis_grid_width as *const (),
RuntimeSymbol::IntAbs => praxis_int_abs as *const (),
RuntimeSymbol::IntAdd => praxis_int_add as *const (),
RuntimeSymbol::IntCheckedAdd => praxis_int_checked_add as *const (),
RuntimeSymbol::IntCheckedMul => praxis_int_checked_mul as *const (),
RuntimeSymbol::IntCheckedSub => praxis_int_checked_sub as *const (),
RuntimeSymbol::IntClamp => praxis_int_clamp as *const (),
RuntimeSymbol::IntDiv => praxis_int_div as *const (),
RuntimeSymbol::IntEq => praxis_int_eq as *const (),
RuntimeSymbol::IntGcd => praxis_int_gcd as *const (),
RuntimeSymbol::IntGe => praxis_int_ge as *const (),
RuntimeSymbol::IntGt => praxis_int_gt as *const (),
RuntimeSymbol::IntLcm => praxis_int_lcm as *const (),
RuntimeSymbol::IntLe => praxis_int_le as *const (),
RuntimeSymbol::IntLoad => praxis_int_load as *const (),
RuntimeSymbol::IntLt => praxis_int_lt as *const (),
RuntimeSymbol::IntMax => praxis_int_max as *const (),
RuntimeSymbol::IntMin => praxis_int_min as *const (),
RuntimeSymbol::IntMul => praxis_int_mul as *const (),
RuntimeSymbol::IntNe => praxis_int_ne as *const (),
RuntimeSymbol::IntNeg => praxis_int_neg as *const (),
RuntimeSymbol::IntRem => praxis_int_rem as *const (),
RuntimeSymbol::IntSaturatingAdd => praxis_int_saturating_add as *const (),
RuntimeSymbol::IntSaturatingMul => praxis_int_saturating_mul as *const (),
RuntimeSymbol::IntSaturatingSub => praxis_int_saturating_sub as *const (),
RuntimeSymbol::IntSign => praxis_int_sign as *const (),
RuntimeSymbol::IntSub => praxis_int_sub as *const (),
RuntimeSymbol::IntToChar => praxis_int_to_char as *const (),
RuntimeSymbol::IntToFloat => praxis_int_to_float as *const (),
RuntimeSymbol::IntToText => praxis_int_to_text as *const (),
RuntimeSymbol::IntWrappingAdd => praxis_int_wrapping_add as *const (),
RuntimeSymbol::IntWrappingMul => praxis_int_wrapping_mul as *const (),
RuntimeSymbol::IntWrappingSub => praxis_int_wrapping_sub as *const (),
RuntimeSymbol::MapContains => praxis_map_contains as *const (),
RuntimeSymbol::RangeGet => praxis_range_get as *const (),
RuntimeSymbol::RangeLen => praxis_range_len as *const (),
RuntimeSymbol::RangeNew => praxis_range_new as *const (),
RuntimeSymbol::RangeNewInclusive => praxis_range_new_inclusive as *const (),
RuntimeSymbol::MapGet => praxis_map_get as *const (),
RuntimeSymbol::MapIndex => praxis_map_index as *const (),
RuntimeSymbol::MapInsert => praxis_map_insert as *const (),
RuntimeSymbol::MapIsEmpty => praxis_map_is_empty as *const (),
RuntimeSymbol::MapKeys => praxis_map_keys as *const (),
RuntimeSymbol::MapLen => praxis_map_len as *const (),
RuntimeSymbol::MapNew => praxis_map_new as *const (),
RuntimeSymbol::MapRemove => praxis_map_remove as *const (),
RuntimeSymbol::MapUpdateMax => praxis_map_update_max as *const (),
RuntimeSymbol::MapUpdateMin => praxis_map_update_min as *const (),
RuntimeSymbol::MapValues => praxis_map_values as *const (),
RuntimeSymbol::MaxHeapIsEmpty => praxis_max_heap_is_empty as *const (),
RuntimeSymbol::MaxHeapItems => praxis_max_heap_items as *const (),
RuntimeSymbol::MaxHeapLen => praxis_max_heap_len as *const (),
RuntimeSymbol::MaxHeapNew => praxis_max_heap_new as *const (),
RuntimeSymbol::MaxHeapPeek => praxis_max_heap_peek as *const (),
RuntimeSymbol::MaxHeapPop => praxis_max_heap_pop as *const (),
RuntimeSymbol::MaxHeapPush => praxis_max_heap_push as *const (),
RuntimeSymbol::MinHeapIsEmpty => praxis_min_heap_is_empty as *const (),
RuntimeSymbol::MinHeapItems => praxis_min_heap_items as *const (),
RuntimeSymbol::MinHeapLen => praxis_min_heap_len as *const (),
RuntimeSymbol::MinHeapNew => praxis_min_heap_new as *const (),
RuntimeSymbol::MinHeapPeek => praxis_min_heap_peek as *const (),
RuntimeSymbol::MinHeapPop => praxis_min_heap_pop as *const (),
RuntimeSymbol::MinHeapPush => praxis_min_heap_push as *const (),
RuntimeSymbol::Panic => praxis_panic as *const (),
RuntimeSymbol::RaiseDivByZeroIf => praxis_raise_div_by_zero_if as *const (),
RuntimeSymbol::RaiseEmptyCollection => praxis_raise_empty_collection as *const (),
RuntimeSymbol::RaiseIntOverflowIf => praxis_raise_int_overflow_if as *const (),
RuntimeSymbol::RaiseStackOverflow => praxis_raise_stack_overflow as *const (),
RuntimeSymbol::RecordField => praxis_record_field as *const (),
RuntimeSymbol::RecordSetField => praxis_record_set_field as *const (),
RuntimeSymbol::RunParser => praxis_run_parser as *const (),
RuntimeSymbol::SetContains => praxis_set_contains as *const (),
RuntimeSymbol::SetInsert => praxis_set_insert as *const (),
RuntimeSymbol::SetIsEmpty => praxis_set_is_empty as *const (),
RuntimeSymbol::SetItems => praxis_set_items as *const (),
RuntimeSymbol::SetLen => praxis_set_len as *const (),
RuntimeSymbol::SetNew => praxis_set_new as *const (),
RuntimeSymbol::SetRemove => praxis_set_remove as *const (),
RuntimeSymbol::SnapshotDebugChain => {
crate::crash_snapshot::praxis_snapshot_debug_chain as *const ()
}
RuntimeSymbol::StructEq => praxis_struct_eq as *const (),
RuntimeSymbol::TextConcat => praxis_text_concat as *const (),
RuntimeSymbol::TextGet => praxis_text_get as *const (),
RuntimeSymbol::TextFloat => praxis_text_float as *const (),
RuntimeSymbol::TextInt => praxis_text_int as *const (),
RuntimeSymbol::TextIsEmpty => praxis_text_is_empty as *const (),
RuntimeSymbol::TextLen => praxis_text_len as *const (),
RuntimeSymbol::TupleGet => praxis_tuple_get as *const (),
RuntimeSymbol::TupleSet => praxis_tuple_set as *const (),
RuntimeSymbol::ValueCmp => praxis_value_cmp as *const (),
RuntimeSymbol::ValueToText => praxis_value_to_text as *const (),
RuntimeSymbol::VarCellGet => praxis_var_cell_get as *const (),
RuntimeSymbol::VarCellSet => praxis_var_cell_set as *const (),
RuntimeSymbol::VecFrequencies => praxis_vec_frequencies as *const (),
RuntimeSymbol::VecGet => praxis_vec_get as *const (),
RuntimeSymbol::VecSet => praxis_vec_set as *const (),
RuntimeSymbol::VecIsEmpty => praxis_vec_is_empty as *const (),
RuntimeSymbol::VecJoin => praxis_vec_join as *const (),
RuntimeSymbol::VecLen => praxis_vec_len as *const (),
RuntimeSymbol::VecChunks => praxis_vec_chunks as *const (),
RuntimeSymbol::VecFilled => praxis_vec_filled as *const (),
RuntimeSymbol::VecNew => praxis_vec_new as *const (),
RuntimeSymbol::VecPush => praxis_vec_push as *const (),
RuntimeSymbol::VecReversed => praxis_vec_reversed as *const (),
RuntimeSymbol::VecSorted => praxis_vec_sorted as *const (),
RuntimeSymbol::VecSortedByKey => praxis_vec_sorted_by_key as *const (),
RuntimeSymbol::VecToText => praxis_vec_to_text as *const (),
RuntimeSymbol::VecUnique => praxis_vec_unique as *const (),
RuntimeSymbol::VecWindows => praxis_vec_windows as *const (),
RuntimeSymbol::WriteStdout => praxis_write_stdout as *const (),
};
ptr as *const u8
}
pub(crate) trait AbiSentinel {
unsafe fn sentinel(ctx: *mut RuntimeContext) -> Self;
}
impl AbiSentinel for () {
unsafe fn sentinel(_ctx: *mut RuntimeContext) {}
}
impl AbiSentinel for i64 {
unsafe fn sentinel(_ctx: *mut RuntimeContext) -> i64 {
0
}
}
impl AbiSentinel for GcRef {
unsafe fn sentinel(ctx: *mut RuntimeContext) -> GcRef {
unsafe { unit_sentinel(ctx) }
}
}
impl<T> AbiSentinel for *mut T {
unsafe fn sentinel(_ctx: *mut RuntimeContext) -> *mut T {
std::ptr::null_mut()
}
}
impl<T> AbiSentinel for *const T {
unsafe fn sentinel(_ctx: *mut RuntimeContext) -> *const T {
std::ptr::null()
}
}
#[cold]
#[inline(never)]
pub(crate) unsafe fn abi_panic_escaped<T: AbiSentinel>(
ctx: *mut RuntimeContext,
wrapper: &'static str,
) -> T {
if ctx.is_null() {
std::process::abort();
}
unsafe { set_fault(ctx, RaisedFault::PANIC) };
let message = format!("internal error: a panic escaped the runtime wrapper `{wrapper}`");
unsafe { set_fault_message(ctx, message.clone()) };
if !panic_fault_is_observable(wrapper) {
eprintln!("{message}");
std::process::abort();
}
unsafe { T::sentinel(ctx) }
}
fn panic_fault_is_observable(wrapper: &str) -> bool {
praxis_stdlib::abi::RuntimeSymbol::from_name(wrapper).is_some_and(|s| s.faults())
}
macro_rules! abi_guard {
($wrapper:expr_2021, $ctx:expr_2021, $body:block) => {{
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(move || $body)) {
Ok(value) => value,
Err(_) => unsafe { crate::abi::abi_panic_escaped($ctx, $wrapper) },
}
}};
}
pub(crate) use abi_guard;
unsafe fn set_fault(ctx: *mut RuntimeContext, fault: RaisedFault) {
if let Some(slot) = unsafe { (*ctx).pending_fault.as_mut() } {
slot.set(fault);
}
}
unsafe fn set_fault_message(ctx: *mut RuntimeContext, text: String) {
if let Some(slot) = unsafe { (*ctx).fault_message.as_mut() } {
slot.set(text);
}
}
#[inline]
unsafe fn heap<'a>(ctx: *mut RuntimeContext) -> &'a Heap {
unsafe { &*(*ctx).heap }
}
#[inline]
fn charge_growth(ctx: *mut RuntimeContext, before: usize, after: usize) {
let Some(grown) = after.checked_sub(before).filter(|g| *g != 0) else {
return;
};
if ctx.is_null() {
return;
}
unsafe { heap(ctx).charge_owned_growth(grown) };
}
unsafe fn maybe_collect(ctx: *mut RuntimeContext) {
if ctx.is_null() {
return;
}
let roots = unsafe { crate::roots::RuntimeRoots::from_context(ctx) };
unsafe { heap(ctx).maybe_collect(&roots) };
}
#[inline]
unsafe fn safepoint<'a>(ctx: *mut RuntimeContext) -> (&'a Heap, Safepoint<'a>) {
let h = unsafe { heap(ctx) };
let roots = unsafe { crate::roots::RuntimeRoots::from_context(ctx) };
let sp = h.pace(&roots);
(h, sp)
}
#[inline]
unsafe fn gc_alloc<T: Copy>(ctx: *mut RuntimeContext, payload: Payload<T>, value: T) -> GcRef {
let (h, sp) = unsafe { safepoint(ctx) };
h.alloc(sp, payload, value)
}
#[inline]
unsafe fn gc_alloc_owned<P>(
ctx: *mut RuntimeContext,
descriptor: &'static TypeDescriptor,
init: impl FnOnce() -> P,
) -> GcRef {
let (h, sp) = unsafe { safepoint(ctx) };
unsafe { h.alloc_payload(sp, descriptor, init()) }
}
#[inline]
unsafe fn bool_ref(ctx: *mut RuntimeContext, value: bool) -> GcRef {
let c = unsafe { &*ctx };
if value { c.true_ref } else { c.false_ref }
}
#[inline]
unsafe fn int_ref(ctx: *mut RuntimeContext, value: i64) -> GcRef {
let (h, sp) = unsafe { safepoint(ctx) };
match crate::small_int::index_of(value) {
Some(i) => {
drop(sp);
unsafe { *(*ctx).small_ints.add(i) }
}
None => h.alloc(sp, scalars::INT_PAYLOAD, value),
}
}
#[inline]
unsafe fn char_ref(ctx: *mut RuntimeContext, code: u32) -> GcRef {
debug_assert!(
crate::scalars::is_valid_char(code),
"char_ref's callers validate first"
);
let (h, sp) = unsafe { safepoint(ctx) };
match crate::small_char::index_of(code) {
Some(i) => {
drop(sp);
unsafe { *(*ctx).small_chars.add(i) }
}
None => h.alloc(sp, scalars::CHAR_PAYLOAD, code),
}
}
#[inline]
unsafe fn text_ref(ctx: *mut RuntimeContext, s: impl Into<Box<str>>) -> GcRef {
unsafe {
gc_alloc_owned(ctx, &crate::text::TEXT, || {
crate::text::TextPayload::owned(s)
})
}
}
#[inline]
unsafe fn read_scalar<T: Copy>(r: GcRef, handle: crate::descriptor::Payload<T>) -> Option<T> {
if !std::ptr::eq(r.descriptor(), handle.descriptor()) {
return None;
}
Some(unsafe { handle.read(r.payload::<u8>()) })
}
#[inline]
unsafe fn int_payload(r: GcRef) -> i64 {
unsafe { read_scalar(r, scalars::INT_PAYLOAD) }
.unwrap_or_else(|| scalar_type_mismatch("int_payload", "Int", r.descriptor().name))
}
#[cold]
#[inline(never)]
fn scalar_type_mismatch(what: &'static str, want: &'static str, found: &'static str) -> ! {
panic!("{what} wants a `{want}` payload; this value is a `{found}` (REP-56)");
}
#[cold]
#[inline(never)]
fn text_bytes_are_not_utf8(len: usize) -> ! {
panic!(
"praxis_alloc_text was handed {len} bytes that are not valid UTF-8; its \
`# Safety` contract requires them to be (ADR-111). A host with untrusted \
bytes must validate them first, as `praxis_get_input` does."
);
}
#[inline]
unsafe fn unit_sentinel(ctx: *mut RuntimeContext) -> GcRef {
unsafe { (*ctx).unit_ref }
}
fn linear_index(i: i64, len: usize) -> Option<usize> {
if i < 0 {
return None;
}
let i = i as usize;
(i < len).then_some(i)
}
fn cell_index(x: i64, y: i64, width: usize, height: usize) -> Option<usize> {
let x = linear_index(x, width)?;
let y = linear_index(y, height)?;
Some(y * width + x)
}
unsafe fn checked_index(ctx: *mut RuntimeContext, i: i64, len: usize) -> Option<usize> {
let idx = linear_index(i, len);
if idx.is_none() {
unsafe { set_fault(ctx, RaisedFault::INDEX_OUT_OF_BOUNDS) };
}
idx
}
unsafe fn checked_cell(
ctx: *mut RuntimeContext,
x: i64,
y: i64,
width: usize,
height: usize,
) -> Option<usize> {
let idx = cell_index(x, y, width, height);
if idx.is_none() {
unsafe { set_fault(ctx, RaisedFault::INDEX_OUT_OF_BOUNDS) };
}
idx
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_int(ctx: *mut RuntimeContext, value: i64) -> GcRef {
abi_guard!("praxis_alloc_int", ctx, {
unsafe { int_ref(ctx, value) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_bool(ctx: *mut RuntimeContext, value: i64) -> GcRef {
abi_guard!("praxis_alloc_bool", ctx, {
let c = unsafe { &*ctx };
if value != 0 { c.true_ref } else { c.false_ref }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_unit(ctx: *mut RuntimeContext) -> GcRef {
abi_guard!("praxis_alloc_unit", ctx, {
unsafe { (*ctx).unit_ref }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_char(ctx: *mut RuntimeContext, value: i64) -> GcRef {
abi_guard!("praxis_alloc_char", ctx, {
unsafe { checked_alloc_char(ctx, value) }
})
}
unsafe fn checked_alloc_char(ctx: *mut RuntimeContext, value: i64) -> GcRef {
let Ok(code) = u32::try_from(value) else {
unsafe { set_fault(ctx, RaisedFault::INVALID_CHAR) };
return unsafe { unit_sentinel(ctx) };
};
if !crate::scalars::is_valid_char(code) {
unsafe { set_fault(ctx, RaisedFault::INVALID_CHAR) };
return unsafe { unit_sentinel(ctx) };
}
unsafe { char_ref(ctx, code) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_text(
ctx: *mut RuntimeContext,
bytes: *const u8,
len: usize,
) -> GcRef {
abi_guard!("praxis_alloc_text", ctx, {
let slice = if bytes.is_null() || len == 0 {
&[]
} else {
unsafe { std::slice::from_raw_parts(bytes, len) }
};
let owned: Box<str> = match std::str::from_utf8(slice) {
Ok(s) => s.into(),
Err(_) => text_bytes_are_not_utf8(len),
};
unsafe { text_ref(ctx, owned) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_load(_ctx: *mut RuntimeContext, r: GcRef) -> i64 {
abi_guard!("praxis_int_load", _ctx, {
unsafe { int_payload(r) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bool_load(_ctx: *mut RuntimeContext, r: GcRef) -> i64 {
abi_guard!("praxis_bool_load", _ctx, {
let byte = unsafe { read_scalar(r, scalars::BOOL_PAYLOAD) }.unwrap_or_else(|| {
scalar_type_mismatch("praxis_bool_load", "Bool", r.descriptor().name)
});
i64::from(byte != 0)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_char_load(_ctx: *mut RuntimeContext, r: GcRef) -> i64 {
abi_guard!("praxis_char_load", _ctx, {
let code = unsafe { read_scalar(r, scalars::CHAR_PAYLOAD) }.unwrap_or_else(|| {
scalar_type_mismatch("praxis_char_load", "Char", r.descriptor().name)
});
i64::from(code)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_float(ctx: *mut RuntimeContext, value: i64) -> GcRef {
abi_guard!("praxis_alloc_float", ctx, {
let f = f64::from_bits(value as u64);
unsafe { gc_alloc(ctx, scalars::FLOAT_PAYLOAD, f) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_load(_ctx: *mut RuntimeContext, r: GcRef) -> i64 {
abi_guard!("praxis_float_load", _ctx, {
unsafe { float_payload(r) }.to_bits() as i64
})
}
unsafe fn float_payload(r: GcRef) -> f64 {
unsafe { read_scalar(r, scalars::FLOAT_PAYLOAD) }
.unwrap_or_else(|| scalar_type_mismatch("float_payload", "Float", r.descriptor().name))
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_to_float(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_int_to_float", ctx, {
let i = unsafe { int_payload(r) };
unsafe { gc_alloc(ctx, scalars::FLOAT_PAYLOAD, i as f64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_char_to_int(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_char_to_int", ctx, {
let code = unsafe { read_scalar(r, scalars::CHAR_PAYLOAD) }.unwrap_or_else(|| {
scalar_type_mismatch("praxis_char_to_int", "Char", r.descriptor().name)
});
unsafe { int_ref(ctx, i64::from(code)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_to_char(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_int_to_char", ctx, {
let value = unsafe { int_payload(r) };
unsafe { checked_alloc_char(ctx, value) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_to_int(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_to_int", ctx, {
let f = unsafe { float_payload(r) };
if f.is_nan() || f.is_infinite() || f < i64::MIN as f64 || f >= i64::MAX as f64 {
unsafe { set_fault(ctx, RaisedFault::FLOAT_TO_INT) };
return unsafe { unit_sentinel(ctx) };
}
unsafe { int_ref(ctx, f as i64) }
})
}
unsafe fn rebox_float(ctx: *mut RuntimeContext, out: f64) -> GcRef {
unsafe { gc_alloc(ctx, scalars::FLOAT_PAYLOAD, out) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_abs(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_abs", ctx, {
let f = unsafe { float_payload(r) };
unsafe { rebox_float(ctx, f.abs()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_sqrt(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_sqrt", ctx, {
let f = unsafe { float_payload(r) };
unsafe { rebox_float(ctx, f.sqrt()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_floor(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_floor", ctx, {
let f = unsafe { float_payload(r) };
unsafe { rebox_float(ctx, f.floor()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_ceil(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_ceil", ctx, {
let f = unsafe { float_payload(r) };
unsafe { rebox_float(ctx, f.ceil()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_round(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_round", ctx, {
let f = unsafe { float_payload(r) };
unsafe { rebox_float(ctx, f.round()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_sign(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_sign", ctx, {
let f = unsafe { float_payload(r) };
let sign = if f.is_nan() || f == 0.0 {
f
} else if f > 0.0 {
1.0
} else {
-1.0
};
unsafe { rebox_float(ctx, sign) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_is_nan(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_is_nan", ctx, {
let result = unsafe { float_payload(r) }.is_nan();
unsafe { bool_ref(ctx, result) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_is_infinite(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_is_infinite", ctx, {
let result = unsafe { float_payload(r) }.is_infinite();
unsafe { bool_ref(ctx, result) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_min(
ctx: *mut RuntimeContext,
lhs: GcRef,
rhs: GcRef,
) -> GcRef {
abi_guard!("praxis_float_min", ctx, {
let a = unsafe { float_payload(lhs) };
let b = unsafe { float_payload(rhs) };
unsafe { rebox_float(ctx, a.min(b)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_max(
ctx: *mut RuntimeContext,
lhs: GcRef,
rhs: GcRef,
) -> GcRef {
abi_guard!("praxis_float_max", ctx, {
let a = unsafe { float_payload(lhs) };
let b = unsafe { float_payload(rhs) };
unsafe { rebox_float(ctx, a.max(b)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_to_text(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_float_to_text", ctx, {
let f = unsafe { float_payload(r) };
let mut s = String::new();
scalars::write_float(&mut s, f);
unsafe { text_ref(ctx, s) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_to_text(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_int_to_text", ctx, {
let v = unsafe { int_payload(r) };
let mut s = String::new();
scalars::write_int(&mut s, v);
unsafe { text_ref(ctx, s) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_char_to_text(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_char_to_text", ctx, {
let code = unsafe { read_scalar(r, scalars::CHAR_PAYLOAD) }.unwrap_or_else(|| {
scalar_type_mismatch("praxis_char_to_text", "Char", r.descriptor().name)
});
let mut s = String::new();
scalars::write_char(&mut s, code);
unsafe { text_ref(ctx, s) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_pi(ctx: *mut RuntimeContext) -> GcRef {
abi_guard!("praxis_float_pi", ctx, {
unsafe { gc_alloc(ctx, scalars::FLOAT_PAYLOAD, core::f64::consts::PI) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_float_e(ctx: *mut RuntimeContext) -> GcRef {
abi_guard!("praxis_float_e", ctx, {
unsafe { gc_alloc(ctx, scalars::FLOAT_PAYLOAD, core::f64::consts::E) }
})
}
macro_rules! checked_int_binop {
($name:ident, $op:tt, $fault:expr_2021) => {
#[doc = concat!("Checked `Int ", stringify!($op), "` (§4.12). On fault sets `pending_fault` and returns Unit.")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(
ctx: *mut RuntimeContext,
lhs: GcRef,
rhs: GcRef,
) -> GcRef {
abi_guard!(stringify!($name), ctx, {
let a = unsafe { int_payload(lhs) };
let b = unsafe { int_payload(rhs) };
match a.$op(b) {
Some(result) => unsafe { int_ref(ctx, result) },
None => {
unsafe { set_fault(ctx, $fault) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
};
}
checked_int_binop!(praxis_int_add, checked_add, RaisedFault::INT_OVERFLOW);
checked_int_binop!(praxis_int_sub, checked_sub, RaisedFault::INT_OVERFLOW);
checked_int_binop!(praxis_int_mul, checked_mul, RaisedFault::INT_OVERFLOW);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_div(ctx: *mut RuntimeContext, lhs: GcRef, rhs: GcRef) -> GcRef {
abi_guard!("praxis_int_div", ctx, {
let a = unsafe { int_payload(lhs) };
let b = unsafe { int_payload(rhs) };
if b == 0 {
unsafe { set_fault(ctx, RaisedFault::DIV_BY_ZERO) };
return unsafe { unit_sentinel(ctx) };
}
if a == i64::MIN && b == -1 {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
return unsafe { unit_sentinel(ctx) };
}
unsafe { int_ref(ctx, a / b) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_rem(ctx: *mut RuntimeContext, lhs: GcRef, rhs: GcRef) -> GcRef {
abi_guard!("praxis_int_rem", ctx, {
let a = unsafe { int_payload(lhs) };
let b = unsafe { int_payload(rhs) };
if b == 0 {
unsafe { set_fault(ctx, RaisedFault::DIV_BY_ZERO) };
return unsafe { unit_sentinel(ctx) };
}
if a == i64::MIN && b == -1 {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
return unsafe { unit_sentinel(ctx) };
}
unsafe { int_ref(ctx, a % b) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_neg(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_int_neg", ctx, {
let a = unsafe { int_payload(r) };
match a.checked_neg() {
Some(result) => unsafe { int_ref(ctx, result) },
None => {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_wrapping_add(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_wrapping_add", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
unsafe { int_ref(ctx, x.wrapping_add(y)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_saturating_add(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_saturating_add", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
unsafe { int_ref(ctx, x.saturating_add(y)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_checked_add(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_checked_add", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
match x.checked_add(y) {
Some(sum) => unsafe {
let scope = NativeScope::new(ctx);
let boxed = int_ref(ctx, sum);
let rooted = scope.root(boxed);
option_some(ctx, rooted.get())
},
None => unsafe { option_none(ctx) },
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_wrapping_sub(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_wrapping_sub", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
unsafe { int_ref(ctx, x.wrapping_sub(y)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_saturating_sub(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_saturating_sub", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
unsafe { int_ref(ctx, x.saturating_sub(y)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_checked_sub(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_checked_sub", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
match x.checked_sub(y) {
Some(difference) => unsafe {
let scope = NativeScope::new(ctx);
let boxed = int_ref(ctx, difference);
let rooted = scope.root(boxed);
option_some(ctx, rooted.get())
},
None => unsafe { option_none(ctx) },
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_wrapping_mul(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_wrapping_mul", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
unsafe { int_ref(ctx, x.wrapping_mul(y)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_saturating_mul(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_saturating_mul", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
unsafe { int_ref(ctx, x.saturating_mul(y)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_checked_mul(
ctx: *mut RuntimeContext,
a: GcRef,
b: GcRef,
) -> GcRef {
abi_guard!("praxis_int_checked_mul", ctx, {
let (x, y) = unsafe { (int_payload(a), int_payload(b)) };
match x.checked_mul(y) {
Some(product) => unsafe {
let scope = NativeScope::new(ctx);
let boxed = int_ref(ctx, product);
let rooted = scope.root(boxed);
option_some(ctx, rooted.get())
},
None => unsafe { option_none(ctx) },
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_abs(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_int_abs", ctx, {
let a = unsafe { int_payload(r) };
match a.checked_abs() {
Some(result) => unsafe { int_ref(ctx, result) },
None => {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_sign(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_int_sign", ctx, {
let a = unsafe { int_payload(r) };
unsafe { int_ref(ctx, a.signum()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_min(
_ctx: *mut RuntimeContext,
lhs: GcRef,
rhs: GcRef,
) -> GcRef {
abi_guard!("praxis_int_min", _ctx, {
let a = unsafe { int_payload(lhs) };
let b = unsafe { int_payload(rhs) };
if b < a { rhs } else { lhs }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_max(
_ctx: *mut RuntimeContext,
lhs: GcRef,
rhs: GcRef,
) -> GcRef {
abi_guard!("praxis_int_max", _ctx, {
let a = unsafe { int_payload(lhs) };
let b = unsafe { int_payload(rhs) };
if b > a { rhs } else { lhs }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_clamp(
ctx: *mut RuntimeContext,
value: GcRef,
low: GcRef,
high: GcRef,
) -> GcRef {
abi_guard!("praxis_int_clamp", ctx, {
let v = unsafe { int_payload(value) };
let lo = unsafe { int_payload(low) };
let hi = unsafe { int_payload(high) };
if lo > hi {
unsafe { set_fault(ctx, RaisedFault::EMPTY_RANGE) };
return unsafe { unit_sentinel(ctx) };
}
if v < lo {
low
} else if v > hi {
high
} else {
value
}
})
}
fn checked_gcd(a: i64, b: i64) -> Option<i64> {
let mut x = (a as i128).abs();
let mut y = (b as i128).abs();
while y != 0 {
let t = x % y;
x = y;
y = t;
}
i64::try_from(x).ok()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_gcd(ctx: *mut RuntimeContext, lhs: GcRef, rhs: GcRef) -> GcRef {
abi_guard!("praxis_int_gcd", ctx, {
let a = unsafe { int_payload(lhs) };
let b = unsafe { int_payload(rhs) };
match checked_gcd(a, b) {
Some(result) => unsafe { int_ref(ctx, result) },
None => {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_int_lcm(ctx: *mut RuntimeContext, lhs: GcRef, rhs: GcRef) -> GcRef {
abi_guard!("praxis_int_lcm", ctx, {
let a = unsafe { int_payload(lhs) };
let b = unsafe { int_payload(rhs) };
if a == 0 || b == 0 {
return unsafe { int_ref(ctx, 0i64) };
}
let result = checked_gcd(a, b)
.map(|g| ((a as i128) / (g as i128) * (b as i128)).abs())
.and_then(|m| i64::try_from(m).ok());
match result {
Some(result) => unsafe { int_ref(ctx, result) },
None => {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
macro_rules! int_cmp {
($name:ident, $op:tt) => {
#[doc = concat!(" `Int ", stringify!($op), "` comparison; returns a Bool GcRef (§4.12).")]
#[unsafe(no_mangle)]
pub unsafe extern "C" fn $name(
ctx: *mut RuntimeContext,
lhs: GcRef,
rhs: GcRef,
) -> GcRef {
abi_guard!(stringify!($name), ctx, {
let a = unsafe { int_payload(lhs) };
let b = unsafe { int_payload(rhs) };
let result = a $op b;
unsafe { bool_ref(ctx, result) }
})
}
};
}
int_cmp!(praxis_int_eq, ==);
int_cmp!(praxis_int_ne, !=);
int_cmp!(praxis_int_lt, <);
int_cmp!(praxis_int_gt, >);
int_cmp!(praxis_int_le, <=);
int_cmp!(praxis_int_ge, >=);
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_check_fault(ctx: *mut RuntimeContext) -> i64 {
abi_guard!("praxis_check_fault", ctx, {
if ctx.is_null() {
return 0;
}
if let Some(fault) = unsafe { (*ctx).pending_fault.as_ref() } {
return fault.is_pending().into();
}
0
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_breakpoint(
ctx: *mut RuntimeContext,
span_start: u32,
span_end: u32,
) {
abi_guard!("praxis_breakpoint", ctx, {
if ctx.is_null() {
return;
}
unsafe { crate::breakpoint::stop(ctx, (span_start, span_end)) };
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_raise_stack_overflow(ctx: *mut RuntimeContext) {
abi_guard!("praxis_raise_stack_overflow", ctx, {
unsafe { set_fault(ctx, RaisedFault::STACK_OVERFLOW) };
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_raise_empty_collection(ctx: *mut RuntimeContext) -> GcRef {
abi_guard!("praxis_raise_empty_collection", ctx, {
unsafe { set_fault(ctx, RaisedFault::EMPTY_COLLECTION) };
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_raise_int_overflow_if(ctx: *mut RuntimeContext, condition: i64) {
abi_guard!("praxis_raise_int_overflow_if", ctx, {
if condition != 0 {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_raise_div_by_zero_if(ctx: *mut RuntimeContext, condition: i64) {
abi_guard!("praxis_raise_div_by_zero_if", ctx, {
if condition != 0 {
unsafe { set_fault(ctx, RaisedFault::DIV_BY_ZERO) };
}
})
}
unsafe fn payload_ref<P>(r: GcRef) -> &'static P {
unsafe { &*r.payload::<P>() }
}
unsafe fn payload_mut<'s, P>(r: Rooted<'s>) -> &'s mut P {
unsafe { &mut *r.get().payload::<P>() }
}
unsafe fn vec_payload(r: GcRef) -> &'static VecPayload {
unsafe { payload_ref::<VecPayload>(r) }
}
unsafe fn vec_payload_mut<'s>(r: Rooted<'s>) -> &'s mut VecPayload {
unsafe { payload_mut::<VecPayload>(r) }
}
unsafe fn vec_of(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
items: impl Iterator<Item = GcRef>,
) -> GcRef {
let items: Vec<GcRef> = items.collect();
let element_descriptor = if element_descriptor.is_null() {
items
.first()
.map_or(std::ptr::null(), |first| first.descriptor() as *const _)
} else {
element_descriptor
};
let result = unsafe { praxis_vec_new(ctx, element_descriptor) };
let scope = unsafe { NativeScope::new(ctx) };
let rp = unsafe { vec_payload_mut(scope.root(result)) };
rp.items.extend(items);
result
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_new(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
) -> GcRef {
abi_guard!("praxis_vec_new", ctx, {
unsafe {
gc_alloc_owned(ctx, &crate::collections::VEC, || VecPayload {
element_descriptor,
items: ReprCVec::new(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_filled(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
count: GcRef,
fill: GcRef,
) -> GcRef {
abi_guard!("praxis_vec_filled", ctx, {
let n = unsafe { int_payload(count) };
let Some(extent) = crate::collections::VecExtent::new(n) else {
unsafe { set_fault(ctx, RaisedFault::INVALID_SIZE) };
return unsafe { unit_sentinel(ctx) };
};
let mut descriptor = element_descriptor;
if !unsafe { adopt_or_reject(ctx, &mut descriptor, fill) } {
return unsafe { unit_sentinel(ctx) };
}
let scope = unsafe { NativeScope::new(ctx) };
let fill = scope.root(fill).get();
unsafe {
gc_alloc_owned(ctx, &crate::collections::VEC, || VecPayload {
element_descriptor: descriptor,
items: ReprCVec::from_vec(vec![fill; extent.len()]),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_record(
ctx: *mut RuntimeContext,
schema_ptr: *const crate::records::RecordSchema,
) -> GcRef {
abi_guard!("praxis_alloc_record", ctx, {
if schema_ptr.is_null() {
return unsafe { unit_sentinel(ctx) };
}
let schema = unsafe { &*schema_ptr };
let arity = schema.fields.len();
let unit = unsafe { unit_sentinel(ctx) };
unsafe {
gc_alloc_owned(ctx, &crate::records::RECORD, || {
crate::records::RecordPayload {
schema: schema_ptr,
items: vec![unit; arity],
}
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_record_set_field(
ctx: *mut RuntimeContext,
record: GcRef,
idx: u32,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_record_set_field", ctx, {
let _ = ctx;
let payload = record.payload::<u8>() as *mut crate::records::RecordPayload;
let rp = unsafe { &mut *payload };
if let Some(slot) = rp.items.get_mut(idx as usize) {
*slot = value;
}
record
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_record_field(
ctx: *mut RuntimeContext,
record: GcRef,
idx: u32,
) -> GcRef {
abi_guard!("praxis_record_field", ctx, {
let payload = record.payload::<u8>() as *const crate::records::RecordPayload;
let rp = unsafe { &*payload };
rp.items
.get(idx as usize)
.copied()
.unwrap_or_else(|| unsafe { unit_sentinel(ctx) })
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_enum(
ctx: *mut RuntimeContext,
schema_ptr: *const crate::enums::EnumSchema,
tag: i64,
) -> GcRef {
abi_guard!("praxis_alloc_enum", ctx, {
if schema_ptr.is_null() || tag < 0 {
return unsafe { unit_sentinel(ctx) };
}
let schema = unsafe { &*schema_ptr };
if schema.variant_at(tag as usize).is_none() {
return unsafe { unit_sentinel(ctx) };
}
let arity = schema.arity_of(tag as usize);
let unit = unsafe { unit_sentinel(ctx) };
let items = vec![unit; arity];
unsafe {
gc_alloc_owned(ctx, &crate::enums::ENUM, || crate::enums::EnumPayload {
schema: schema_ptr,
tag: tag as u32,
items,
})
}
})
}
pub(crate) unsafe fn option_some(ctx: *mut RuntimeContext, value: GcRef) -> GcRef {
unsafe {
let scope = NativeScope::new(ctx);
let rooted = scope.root(value);
let some = praxis_alloc_enum(
ctx,
crate::enums::option_schema(),
crate::enums::OPTION_SOME_TAG,
);
praxis_enum_set_payload(ctx, some, 0, rooted.get());
some
}
}
pub(crate) unsafe fn option_none(ctx: *mut RuntimeContext) -> GcRef {
unsafe {
praxis_alloc_enum(
ctx,
crate::enums::option_schema(),
crate::enums::OPTION_NONE_TAG,
)
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_enum_set_payload(
ctx: *mut RuntimeContext,
enum_value: GcRef,
idx: i64,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_enum_set_payload", ctx, {
let _ = ctx;
let payload = enum_value.payload::<u8>() as *mut crate::enums::EnumPayload;
let ep = unsafe { &mut *payload };
if let Some(slot) = ep.items.get_mut(idx as usize) {
*slot = value;
}
enum_value
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_enum_tag(ctx: *mut RuntimeContext, enum_value: GcRef) -> GcRef {
abi_guard!("praxis_enum_tag", ctx, {
let payload = enum_value.payload::<u8>() as *const crate::enums::EnumPayload;
let tag = unsafe { (*payload).tag as i64 };
unsafe { int_ref(ctx, tag) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_enum_payload(
ctx: *mut RuntimeContext,
enum_value: GcRef,
idx: i64,
) -> GcRef {
abi_guard!("praxis_enum_payload", ctx, {
let payload = enum_value.payload::<u8>() as *const crate::enums::EnumPayload;
let ep = unsafe { &*payload };
ep.items
.get(idx as usize)
.copied()
.unwrap_or_else(|| unsafe { unit_sentinel(ctx) })
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_tuple(
ctx: *mut RuntimeContext,
schema_ptr: *const crate::tuples::TupleSchema,
) -> GcRef {
abi_guard!("praxis_alloc_tuple", ctx, {
if schema_ptr.is_null() {
return unsafe { unit_sentinel(ctx) };
}
let schema = unsafe { &*schema_ptr };
let arity = schema.descriptors.len();
let unit = unsafe { unit_sentinel(ctx) };
unsafe {
gc_alloc_owned(ctx, &crate::tuples::TUPLE, || crate::tuples::TuplePayload {
schema: schema_ptr,
items: vec![unit; arity],
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_tuple_set(
ctx: *mut RuntimeContext,
tuple: GcRef,
idx: i64,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_tuple_set", ctx, {
let _ = ctx;
let payload = tuple.payload::<u8>() as *mut crate::tuples::TuplePayload;
let tp = unsafe { &mut *payload };
if let Some(slot) = tp.items.get_mut(idx as usize) {
*slot = value;
}
tuple
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_tuple_get(
ctx: *mut RuntimeContext,
tuple: GcRef,
idx: i64,
) -> GcRef {
abi_guard!("praxis_tuple_get", ctx, {
let payload = tuple.payload::<u8>() as *const crate::tuples::TuplePayload;
let tp = unsafe { &*payload };
tp.items
.get(idx as usize)
.copied()
.unwrap_or_else(|| unsafe { unit_sentinel(ctx) })
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_struct_eq(ctx: *mut RuntimeContext, a: GcRef, b: GcRef) -> i64 {
abi_guard!("praxis_struct_eq", ctx, {
let _ = ctx;
let desc = a.descriptor();
if !std::ptr::eq(desc, b.descriptor()) {
return 0;
}
match desc.equals {
Some(eq) => {
let pa = a.payload::<u8>() as *const u8;
let pb = b.payload::<u8>() as *const u8;
if unsafe { eq(pa, pb) } { 1 } else { 0 }
}
None => 0,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_value_cmp(ctx: *mut RuntimeContext, a: GcRef, b: GcRef) -> i64 {
abi_guard!("praxis_value_cmp", ctx, {
let desc = a.descriptor();
if !std::ptr::eq(desc, b.descriptor()) {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return 0;
}
let Some(compare) = desc.compare else {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return 0;
};
let ordering = unsafe {
compare(
a.payload::<u8>() as *const u8,
b.payload::<u8>() as *const u8,
)
};
match ordering {
std::cmp::Ordering::Less => -1,
std::cmp::Ordering::Equal => 0,
std::cmp::Ordering::Greater => 1,
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_closure(
ctx: *mut RuntimeContext,
fn_ptr: *const u8,
n_captures: i64,
) -> GcRef {
abi_guard!("praxis_alloc_closure", ctx, {
let unit = unsafe { unit_sentinel(ctx) };
let env = vec![unit; n_captures as usize];
unsafe {
gc_alloc_owned(ctx, &crate::closures::CLOSURE, || {
crate::closures::ClosurePayload { fn_ptr, env }
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_closure_set_capture(
ctx: *mut RuntimeContext,
closure: GcRef,
idx: i64,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_closure_set_capture", ctx, {
let _ = ctx;
let payload = closure.payload::<u8>() as *mut crate::closures::ClosurePayload;
let cp = unsafe { &mut *payload };
if let Some(slot) = cp.env.get_mut(idx as usize) {
*slot = value;
}
closure
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_closure_fn_ptr(
ctx: *mut RuntimeContext,
closure: GcRef,
) -> *const u8 {
abi_guard!("praxis_closure_fn_ptr", ctx, {
let _ = ctx;
let payload = closure.payload::<u8>() as *const crate::closures::ClosurePayload;
unsafe { (*payload).fn_ptr }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_closure_capture(
ctx: *mut RuntimeContext,
closure: GcRef,
idx: i64,
) -> GcRef {
abi_guard!("praxis_closure_capture", ctx, {
let payload = closure.payload::<u8>() as *const crate::closures::ClosurePayload;
let cp = unsafe { &*payload };
cp.env
.get(idx as usize)
.copied()
.unwrap_or_else(|| unsafe { unit_sentinel(ctx) })
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_alloc_var_cell(ctx: *mut RuntimeContext, value: GcRef) -> GcRef {
abi_guard!("praxis_alloc_var_cell", ctx, {
unsafe {
gc_alloc_owned(ctx, &crate::var_cell::VAR_CELL, || {
crate::var_cell::VarCellPayload { value }
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_var_cell_get(ctx: *mut RuntimeContext, cell: GcRef) -> GcRef {
abi_guard!("praxis_var_cell_get", ctx, {
let _ = ctx;
let payload = cell.payload::<u8>() as *const crate::var_cell::VarCellPayload;
unsafe { (*payload).value }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_var_cell_set(
ctx: *mut RuntimeContext,
cell: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_var_cell_set", ctx, {
let _ = ctx;
let payload = cell.payload::<u8>() as *mut crate::var_cell::VarCellPayload;
unsafe {
(*payload).value = value;
}
cell
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_push(
ctx: *mut RuntimeContext,
vec: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_vec_push", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { vec_payload_mut(scope.root(vec)) };
if !unsafe { adopt_or_reject(ctx, &mut p.element_descriptor, value) } {
return unsafe { unit_sentinel(ctx) };
}
let before = p.owned_bytes();
p.items.push(value);
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
unsafe fn adopt_or_reject(
ctx: *mut RuntimeContext,
element_descriptor: &mut *const TypeDescriptor,
value: GcRef,
) -> bool {
let pushed = value.descriptor();
if element_descriptor.is_null() {
*element_descriptor = pushed;
return true;
}
if std::ptr::eq(*element_descriptor, pushed) {
return true;
}
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
false
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_len(ctx: *mut RuntimeContext, vec: GcRef) -> GcRef {
abi_guard!("praxis_vec_len", ctx, {
let p = unsafe { vec_payload(vec) };
let len = p.items.len() as i64;
unsafe { int_ref(ctx, len) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_get(
ctx: *mut RuntimeContext,
vec: GcRef,
index: GcRef,
) -> GcRef {
abi_guard!("praxis_vec_get", ctx, {
let p = unsafe { vec_payload(vec) };
let idx = unsafe { int_payload(index) };
let Some(idx) = (unsafe { checked_index(ctx, idx, p.items.len()) }) else {
return unsafe { unit_sentinel(ctx) };
};
p.items[idx]
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_set(
ctx: *mut RuntimeContext,
vec: GcRef,
index: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_vec_set", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { vec_payload_mut(scope.root(vec)) };
let idx = unsafe { int_payload(index) };
let Some(idx) = (unsafe { checked_index(ctx, idx, p.items.len()) }) else {
return unsafe { unit_sentinel(ctx) };
};
if !unsafe { adopt_or_reject(ctx, &mut p.element_descriptor, value) } {
return unsafe { unit_sentinel(ctx) };
}
p.items[idx] = value;
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_is_empty(ctx: *mut RuntimeContext, vec: GcRef) -> GcRef {
abi_guard!("praxis_vec_is_empty", ctx, {
let p = unsafe { vec_payload(vec) };
let empty = p.items.is_empty();
unsafe { bool_ref(ctx, empty) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_sorted(ctx: *mut RuntimeContext, vec: GcRef) -> GcRef {
abi_guard!("praxis_vec_sorted", ctx, {
let p = unsafe { vec_payload(vec) };
let mut items: Vec<GcRef> = p.items.to_vec();
if items.len() > 1 {
let desc = items[0].descriptor();
if !items.iter().all(|i| std::ptr::eq(i.descriptor(), desc)) {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return unsafe { unit_sentinel(ctx) };
}
let Some(compare) = desc.compare else {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return unsafe { unit_sentinel(ctx) };
};
items.sort_by(|a, b| {
unsafe {
compare(
a.payload::<u8>() as *const u8,
b.payload::<u8>() as *const u8,
)
}
});
}
unsafe { vec_of(ctx, p.element_descriptor, items.into_iter()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_sorted_by_key(
ctx: *mut RuntimeContext,
vec: GcRef,
key: GcRef,
) -> GcRef {
abi_guard!("praxis_vec_sorted_by_key", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let _receiver = scope.root(vec);
let p = unsafe { vec_payload(vec) };
let element_descriptor = p.element_descriptor;
let items: Vec<GcRef> = p.items.to_vec();
for item in &items {
scope.root(*item);
}
let mut decorated: Vec<(GcRef, GcRef)> = Vec::with_capacity(items.len());
for item in items {
let Some(k) = (unsafe { call_unary_closure(ctx, key, item) }) else {
return unsafe { unit_sentinel(ctx) };
};
decorated.push((scope.root(k).get(), item));
}
if decorated.len() > 1 {
let desc = decorated[0].0.descriptor();
if !decorated
.iter()
.all(|(k, _)| std::ptr::eq(k.descriptor(), desc))
{
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return unsafe { unit_sentinel(ctx) };
}
let Some(compare) = desc.compare else {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return unsafe { unit_sentinel(ctx) };
};
decorated.sort_by(|(a, _), (b, _)| {
unsafe {
compare(
a.payload::<u8>() as *const u8,
b.payload::<u8>() as *const u8,
)
}
});
}
unsafe {
vec_of(
ctx,
element_descriptor,
decorated.into_iter().map(|(_, item)| item),
)
}
})
}
unsafe fn call_unary_closure(
ctx: *mut RuntimeContext,
closure: GcRef,
arg: GcRef,
) -> Option<GcRef> {
if !std::ptr::eq(closure.descriptor(), &crate::closures::CLOSURE) {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return None;
}
let fn_ptr = unsafe { (*closure.payload::<crate::closures::ClosurePayload>()).fn_ptr };
let result = unsafe {
let f: unsafe extern "C" fn(*mut RuntimeContext, GcRef, GcRef) -> GcRef =
std::mem::transmute(fn_ptr);
f(ctx, closure, arg)
};
if unsafe { praxis_check_fault(ctx) } != 0 {
return None;
}
Some(result)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_unique(ctx: *mut RuntimeContext, vec: GcRef) -> GcRef {
abi_guard!("praxis_vec_unique", ctx, {
let p = unsafe { vec_payload(vec) };
let mut seen: std::collections::HashSet<DynamicKey> = std::collections::HashSet::new();
let mut kept: Vec<GcRef> = Vec::new();
for item in &p.items {
if seen.insert(DynamicKey::new(*item)) {
kept.push(*item);
}
}
unsafe { vec_of(ctx, p.element_descriptor, kept.into_iter()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_reversed(ctx: *mut RuntimeContext, vec: GcRef) -> GcRef {
abi_guard!("praxis_vec_reversed", ctx, {
let p = unsafe { vec_payload(vec) };
let items: Vec<GcRef> = p.items.iter().rev().copied().collect();
unsafe { vec_of(ctx, p.element_descriptor, items.into_iter()) }
})
}
fn group_size(n: i64) -> Option<usize> {
if n <= 0 {
return None;
}
usize::try_from(n).ok()
}
unsafe fn vec_of_groups(
ctx: *mut RuntimeContext,
vec: GcRef,
groups: impl Iterator<Item = (usize, usize)>,
) -> GcRef {
let element_descriptor = unsafe { vec_payload(vec) }.element_descriptor;
let outer = unsafe { praxis_vec_new(ctx, &crate::collections::VEC as *const _) };
let scope = unsafe { NativeScope::new(ctx) };
let op = unsafe { vec_payload_mut(scope.root(outer)) };
for (start, end) in groups {
let items: Vec<GcRef> = unsafe { vec_payload(vec) }.items[start..end].to_vec();
let inner = unsafe { vec_of(ctx, element_descriptor, items.into_iter()) };
op.items.push(inner);
}
outer
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_chunks(
ctx: *mut RuntimeContext,
vec: GcRef,
n: GcRef,
) -> GcRef {
abi_guard!("praxis_vec_chunks", ctx, {
let Some(size) = group_size(unsafe { int_payload(n) }) else {
unsafe { set_fault(ctx, RaisedFault::INVALID_SIZE) };
return unsafe { unit_sentinel(ctx) };
};
let len = unsafe { vec_payload(vec) }.items.len();
let groups = (0..len)
.step_by(size)
.map(move |s| (s, (s + size).min(len)));
unsafe { vec_of_groups(ctx, vec, groups) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_windows(
ctx: *mut RuntimeContext,
vec: GcRef,
n: GcRef,
) -> GcRef {
abi_guard!("praxis_vec_windows", ctx, {
let Some(size) = group_size(unsafe { int_payload(n) }) else {
unsafe { set_fault(ctx, RaisedFault::INVALID_SIZE) };
return unsafe { unit_sentinel(ctx) };
};
let len = unsafe { vec_payload(vec) }.items.len();
let starts = if size <= len { len - size + 1 } else { 0 };
let groups = (0..starts).map(move |s| (s, s + size));
unsafe { vec_of_groups(ctx, vec, groups) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_join(
ctx: *mut RuntimeContext,
vec: GcRef,
sep: GcRef,
) -> GcRef {
abi_guard!("praxis_vec_join", ctx, {
let p = unsafe { vec_payload(vec) };
if !p
.items
.iter()
.all(|item| std::ptr::eq(item.descriptor(), &crate::text::TEXT))
{
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return unsafe { unit_sentinel(ctx) };
}
let separator = unsafe { text_str(sep) };
let mut joined = String::new();
for (i, item) in p.items.iter().enumerate() {
if i > 0 {
joined.push_str(separator);
}
joined.push_str(unsafe { text_str(*item) });
}
unsafe { text_ref(ctx, joined) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_to_text(ctx: *mut RuntimeContext, vec: GcRef) -> GcRef {
abi_guard!("praxis_vec_to_text", ctx, {
let p = unsafe { vec_payload(vec) };
let mut rendered = String::new();
for item in &p.items {
let Some(code) = (unsafe { read_scalar(*item, scalars::CHAR_PAYLOAD) }) else {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return unsafe { unit_sentinel(ctx) };
};
scalars::write_char(&mut rendered, code);
}
unsafe { text_ref(ctx, rendered) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_vec_frequencies(ctx: *mut RuntimeContext, vec: GcRef) -> GcRef {
abi_guard!("praxis_vec_frequencies", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let _receiver = scope.root(vec);
let p = unsafe { vec_payload(vec) };
let mut counts: Vec<(DynamicKey, i64)> = Vec::new();
let mut index: std::collections::HashMap<DynamicKey, usize> =
std::collections::HashMap::new();
for item in &p.items {
let key = DynamicKey::new(*item);
match index.get(&key) {
Some(at) => counts[*at].1 += 1,
None => {
index.insert(key, counts.len());
counts.push((key, 1));
}
}
}
let counter = unsafe { praxis_counter_new(ctx, p.element_descriptor) };
let rooted = scope.root(counter);
for (key, count) in counts {
let boxed = unsafe { int_ref(ctx, count) };
unsafe { counter_payload_mut(rooted) }
.entries
.insert(key, boxed);
}
counter
})
}
use crate::collections::DequePayload;
unsafe fn deque_payload(r: GcRef) -> &'static DequePayload {
unsafe { payload_ref::<DequePayload>(r) }
}
unsafe fn deque_payload_mut<'s>(r: Rooted<'s>) -> &'s mut DequePayload {
unsafe { payload_mut::<DequePayload>(r) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_new(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
) -> GcRef {
abi_guard!("praxis_deque_new", ctx, {
unsafe {
gc_alloc_owned(ctx, &crate::collections::DEQUE, || DequePayload {
element_descriptor,
items: std::collections::VecDeque::new(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_push_front(
ctx: *mut RuntimeContext,
deque: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_deque_push_front", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { deque_payload_mut(scope.root(deque)) };
if !unsafe { adopt_or_reject(ctx, &mut p.element_descriptor, value) } {
return unsafe { unit_sentinel(ctx) };
}
let before = p.owned_bytes();
p.items.push_front(value);
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_push_back(
ctx: *mut RuntimeContext,
deque: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_deque_push_back", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { deque_payload_mut(scope.root(deque)) };
if !unsafe { adopt_or_reject(ctx, &mut p.element_descriptor, value) } {
return unsafe { unit_sentinel(ctx) };
}
let before = p.owned_bytes();
p.items.push_back(value);
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_pop_front(ctx: *mut RuntimeContext, deque: GcRef) -> GcRef {
abi_guard!("praxis_deque_pop_front", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { deque_payload_mut(scope.root(deque)) };
match p.items.pop_front() {
Some(v) => v,
None => {
unsafe { set_fault(ctx, RaisedFault::EMPTY_COLLECTION) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_pop_back(ctx: *mut RuntimeContext, deque: GcRef) -> GcRef {
abi_guard!("praxis_deque_pop_back", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { deque_payload_mut(scope.root(deque)) };
match p.items.pop_back() {
Some(v) => v,
None => {
unsafe { set_fault(ctx, RaisedFault::EMPTY_COLLECTION) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_len(ctx: *mut RuntimeContext, deque: GcRef) -> GcRef {
abi_guard!("praxis_deque_len", ctx, {
let p = unsafe { deque_payload(deque) };
let len = p.items.len() as i64;
unsafe { int_ref(ctx, len) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_get(
ctx: *mut RuntimeContext,
deque: GcRef,
index: GcRef,
) -> GcRef {
abi_guard!("praxis_deque_get", ctx, {
let p = unsafe { deque_payload(deque) };
let idx = unsafe { int_payload(index) };
let Some(idx) = (unsafe { checked_index(ctx, idx, p.items.len()) }) else {
return unsafe { unit_sentinel(ctx) };
};
p.items[idx]
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_set(
ctx: *mut RuntimeContext,
deque: GcRef,
index: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_deque_set", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { deque_payload_mut(scope.root(deque)) };
let idx = unsafe { int_payload(index) };
let Some(idx) = (unsafe { checked_index(ctx, idx, p.items.len()) }) else {
return unsafe { unit_sentinel(ctx) };
};
if !unsafe { adopt_or_reject(ctx, &mut p.element_descriptor, value) } {
return unsafe { unit_sentinel(ctx) };
}
p.items[idx] = value;
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_deque_is_empty(ctx: *mut RuntimeContext, deque: GcRef) -> GcRef {
abi_guard!("praxis_deque_is_empty", ctx, {
let p = unsafe { deque_payload(deque) };
let empty = p.items.is_empty();
unsafe { bool_ref(ctx, empty) }
})
}
use crate::maps::{CounterPayload, MapPayload, SetPayload};
unsafe fn map_payload(r: GcRef) -> &'static MapPayload {
unsafe { payload_ref::<MapPayload>(r) }
}
unsafe fn map_payload_mut<'s>(r: Rooted<'s>) -> &'s mut MapPayload {
unsafe { payload_mut::<MapPayload>(r) }
}
unsafe fn set_payload(r: GcRef) -> &'static SetPayload {
unsafe { payload_ref::<SetPayload>(r) }
}
unsafe fn set_payload_mut<'s>(r: Rooted<'s>) -> &'s mut SetPayload {
unsafe { payload_mut::<SetPayload>(r) }
}
unsafe fn counter_payload(r: GcRef) -> &'static CounterPayload {
unsafe { payload_ref::<CounterPayload>(r) }
}
unsafe fn counter_payload_mut<'s>(r: Rooted<'s>) -> &'s mut CounterPayload {
unsafe { payload_mut::<CounterPayload>(r) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_new(
ctx: *mut RuntimeContext,
key_descriptor: *const TypeDescriptor,
) -> GcRef {
abi_guard!("praxis_map_new", ctx, {
let value_descriptor: *const TypeDescriptor = std::ptr::null();
unsafe {
gc_alloc_owned(ctx, &crate::maps::MAP, || MapPayload {
key_descriptor,
value_descriptor,
entries: std::collections::HashMap::new(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_insert(
ctx: *mut RuntimeContext,
map: GcRef,
key: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_map_insert", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { map_payload_mut(scope.root(map)) };
let val_desc = value.descriptor();
match p.value() {
None => p.value_descriptor = val_desc,
Some(known) if !std::ptr::eq(known, val_desc) => {
p.value_descriptor = std::ptr::null();
}
Some(_) => {}
}
let before = p.owned_bytes();
p.entries.insert(DynamicKey::new(key), value);
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_get(ctx: *mut RuntimeContext, map: GcRef, key: GcRef) -> GcRef {
abi_guard!("praxis_map_get", ctx, {
let found = {
let p = unsafe { map_payload(map) };
p.entries.get(&DynamicKey::new(key)).copied()
};
match found {
Some(v) => unsafe { option_some(ctx, v) },
None => unsafe { option_none(ctx) },
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_index(
ctx: *mut RuntimeContext,
map: GcRef,
key: GcRef,
) -> GcRef {
abi_guard!("praxis_map_index", ctx, {
let p = unsafe { map_payload(map) };
match p.entries.get(&DynamicKey::new(key)) {
Some(v) => *v,
None => {
unsafe { set_fault(ctx, RaisedFault::INDEX_OUT_OF_BOUNDS) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_contains(
ctx: *mut RuntimeContext,
map: GcRef,
key: GcRef,
) -> GcRef {
abi_guard!("praxis_map_contains", ctx, {
let p = unsafe { map_payload(map) };
let present = p.entries.contains_key(&DynamicKey::new(key));
unsafe { bool_ref(ctx, present) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_remove(
ctx: *mut RuntimeContext,
map: GcRef,
key: GcRef,
) -> GcRef {
abi_guard!("praxis_map_remove", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { map_payload_mut(scope.root(map)) };
p.entries.remove(&DynamicKey::new(key));
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_len(ctx: *mut RuntimeContext, map: GcRef) -> GcRef {
abi_guard!("praxis_map_len", ctx, {
let p = unsafe { map_payload(map) };
unsafe { int_ref(ctx, p.entries.len() as i64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_keys(ctx: *mut RuntimeContext, map: GcRef) -> GcRef {
abi_guard!("praxis_map_keys", ctx, {
let key_desc = unsafe { map_payload(map) }.key_descriptor;
let rows = unsafe { crate::maps::ordered_entries(&map_payload(map).entries) };
unsafe { vec_of(ctx, key_desc, rows.into_iter().map(|(k, _)| k)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_values(ctx: *mut RuntimeContext, map: GcRef) -> GcRef {
abi_guard!("praxis_map_values", ctx, {
let val_desc = unsafe { map_payload(map) }.value_descriptor;
let rows = unsafe { crate::maps::ordered_entries(&map_payload(map).entries) };
unsafe { vec_of(ctx, val_desc, rows.into_iter().map(|(_, v)| v)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_is_empty(ctx: *mut RuntimeContext, map: GcRef) -> GcRef {
abi_guard!("praxis_map_is_empty", ctx, {
let p = unsafe { map_payload(map) };
unsafe { bool_ref(ctx, p.entries.is_empty()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_update_min(
ctx: *mut RuntimeContext,
map: GcRef,
key: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_map_update_min", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { map_payload_mut(scope.root(map)) };
let cand = unsafe { int_payload(value) };
match p.entries.get_mut(&DynamicKey::new(key)) {
Some(existing) => {
let cur = unsafe { int_payload(*existing) };
if cand < cur {
*existing = value;
}
}
None => {
p.entries.insert(DynamicKey::new(key), value);
}
}
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_map_update_max(
ctx: *mut RuntimeContext,
map: GcRef,
key: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_map_update_max", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { map_payload_mut(scope.root(map)) };
let cand = unsafe { int_payload(value) };
match p.entries.get_mut(&DynamicKey::new(key)) {
Some(existing) => {
let cur = unsafe { int_payload(*existing) };
if cand > cur {
*existing = value;
}
}
None => {
p.entries.insert(DynamicKey::new(key), value);
}
}
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_set_new(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
) -> GcRef {
abi_guard!("praxis_set_new", ctx, {
unsafe {
gc_alloc_owned(ctx, &crate::maps::SET, || SetPayload {
element_descriptor,
entries: std::collections::HashSet::new(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_set_insert(
ctx: *mut RuntimeContext,
set: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_set_insert", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { set_payload_mut(scope.root(set)) };
let before = p.owned_bytes();
p.entries.insert(DynamicKey::new(value));
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_set_remove(
ctx: *mut RuntimeContext,
set: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_set_remove", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { set_payload_mut(scope.root(set)) };
p.entries.remove(&DynamicKey::new(value));
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_set_contains(
ctx: *mut RuntimeContext,
set: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_set_contains", ctx, {
let p = unsafe { set_payload(set) };
let present = p.entries.contains(&DynamicKey::new(value));
unsafe { bool_ref(ctx, present) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_set_len(ctx: *mut RuntimeContext, set: GcRef) -> GcRef {
abi_guard!("praxis_set_len", ctx, {
let p = unsafe { set_payload(set) };
unsafe { int_ref(ctx, p.entries.len() as i64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_set_is_empty(ctx: *mut RuntimeContext, set: GcRef) -> GcRef {
abi_guard!("praxis_set_is_empty", ctx, {
let p = unsafe { set_payload(set) };
unsafe { bool_ref(ctx, p.entries.is_empty()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_set_items(ctx: *mut RuntimeContext, set: GcRef) -> GcRef {
abi_guard!("praxis_set_items", ctx, {
let elem_desc = unsafe { set_payload(set) }.element_descriptor;
let members = unsafe { crate::maps::ordered_members(&set_payload(set).entries) };
unsafe { vec_of(ctx, elem_desc, members.into_iter()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_counter_new(
ctx: *mut RuntimeContext,
key_descriptor: *const TypeDescriptor,
) -> GcRef {
abi_guard!("praxis_counter_new", ctx, {
unsafe {
gc_alloc_owned(ctx, &crate::maps::COUNTER, || CounterPayload {
key_descriptor,
entries: std::collections::HashMap::new(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_counter_get(
ctx: *mut RuntimeContext,
counter: GcRef,
key: GcRef,
) -> GcRef {
abi_guard!("praxis_counter_get", ctx, {
let p = unsafe { counter_payload(counter) };
let count = match p.entries.get(&DynamicKey::new(key)) {
Some(v) => unsafe { int_payload(*v) },
None => 0, };
unsafe { int_ref(ctx, count) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_counter_inc(
ctx: *mut RuntimeContext,
counter: GcRef,
key: GcRef,
) -> GcRef {
abi_guard!("praxis_counter_inc", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { counter_payload_mut(scope.root(counter)) };
let dk = DynamicKey::new(key);
match p.entries.get_mut(&dk) {
Some(v) => {
let cur = unsafe { int_payload(*v) };
let Some(next) = cur.checked_add(1) else {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
return unsafe { unit_sentinel(ctx) };
};
*v = unsafe { int_ref(ctx, next) };
}
None => {
let one = unsafe { int_ref(ctx, 1_i64) };
p.entries.insert(dk, one);
}
}
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_counter_set(
ctx: *mut RuntimeContext,
counter: GcRef,
key: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_counter_set", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { counter_payload_mut(scope.root(counter)) };
let before = p.owned_bytes();
p.entries.insert(DynamicKey::new(key), value);
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_counter_keys(ctx: *mut RuntimeContext, counter: GcRef) -> GcRef {
abi_guard!("praxis_counter_keys", ctx, {
let key_desc = unsafe { counter_payload(counter) }.key_descriptor;
let rows = unsafe { crate::maps::ordered_entries(&counter_payload(counter).entries) };
unsafe { vec_of(ctx, key_desc, rows.into_iter().map(|(k, _)| k)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_counter_values(ctx: *mut RuntimeContext, counter: GcRef) -> GcRef {
abi_guard!("praxis_counter_values", ctx, {
let rows = unsafe { crate::maps::ordered_entries(&counter_payload(counter).entries) };
unsafe { vec_of(ctx, &scalars::INT, rows.into_iter().map(|(_, v)| v)) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_counter_len(ctx: *mut RuntimeContext, counter: GcRef) -> GcRef {
abi_guard!("praxis_counter_len", ctx, {
let p = unsafe { counter_payload(counter) };
unsafe { int_ref(ctx, p.entries.len() as i64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_counter_is_empty(
ctx: *mut RuntimeContext,
counter: GcRef,
) -> GcRef {
abi_guard!("praxis_counter_is_empty", ctx, {
let p = unsafe { counter_payload(counter) };
unsafe { bool_ref(ctx, p.entries.is_empty()) }
})
}
use crate::heaps::{HeapEntry, MaxHeapPayload, MinHeapPayload};
use std::collections::BinaryHeap;
unsafe fn max_heap_payload_mut<'s>(r: Rooted<'s>) -> &'s mut MaxHeapPayload {
unsafe { payload_mut::<MaxHeapPayload>(r) }
}
unsafe fn max_heap_payload(r: GcRef) -> &'static MaxHeapPayload {
unsafe { payload_ref::<MaxHeapPayload>(r) }
}
unsafe fn min_heap_payload_mut<'s>(r: Rooted<'s>) -> &'s mut MinHeapPayload {
unsafe { payload_mut::<MinHeapPayload>(r) }
}
unsafe fn min_heap_payload(r: GcRef) -> &'static MinHeapPayload {
unsafe { payload_ref::<MinHeapPayload>(r) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_max_heap_new(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
) -> GcRef {
abi_guard!("praxis_max_heap_new", ctx, {
unsafe {
gc_alloc_owned(ctx, &crate::heaps::MAX_HEAP, || MaxHeapPayload {
element_descriptor,
items: BinaryHeap::new(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_max_heap_push(
ctx: *mut RuntimeContext,
heap_ref: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_max_heap_push", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { max_heap_payload_mut(scope.root(heap_ref)) };
let before = p.owned_bytes();
p.items.push(HeapEntry {
value,
descriptor: value.descriptor(),
});
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_max_heap_pop(ctx: *mut RuntimeContext, heap_ref: GcRef) -> GcRef {
abi_guard!("praxis_max_heap_pop", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { max_heap_payload_mut(scope.root(heap_ref)) };
match p.items.pop() {
Some(e) => e.value,
None => {
unsafe { set_fault(ctx, RaisedFault::EMPTY_COLLECTION) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_max_heap_peek(ctx: *mut RuntimeContext, heap_ref: GcRef) -> GcRef {
abi_guard!("praxis_max_heap_peek", ctx, {
let p = unsafe { max_heap_payload(heap_ref) };
match p.items.peek() {
Some(e) => e.value,
None => {
unsafe { set_fault(ctx, RaisedFault::EMPTY_COLLECTION) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_max_heap_len(ctx: *mut RuntimeContext, heap_ref: GcRef) -> GcRef {
abi_guard!("praxis_max_heap_len", ctx, {
let p = unsafe { max_heap_payload(heap_ref) };
unsafe { int_ref(ctx, p.items.len() as i64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_max_heap_items(ctx: *mut RuntimeContext, heap_ref: GcRef) -> GcRef {
abi_guard!("praxis_max_heap_items", ctx, {
let p = unsafe { max_heap_payload(heap_ref) };
let items = crate::heaps::in_pop_order(&p.items, |e| e.value);
unsafe { vec_of(ctx, p.element_descriptor, items.into_iter()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_max_heap_is_empty(
ctx: *mut RuntimeContext,
heap_ref: GcRef,
) -> GcRef {
abi_guard!("praxis_max_heap_is_empty", ctx, {
let p = unsafe { max_heap_payload(heap_ref) };
unsafe { bool_ref(ctx, p.items.is_empty()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_min_heap_new(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
) -> GcRef {
abi_guard!("praxis_min_heap_new", ctx, {
unsafe {
gc_alloc_owned(ctx, &crate::heaps::MIN_HEAP, || MinHeapPayload {
element_descriptor,
items: BinaryHeap::new(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_min_heap_push(
ctx: *mut RuntimeContext,
heap_ref: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_min_heap_push", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { min_heap_payload_mut(scope.root(heap_ref)) };
let before = p.owned_bytes();
p.items.push(std::cmp::Reverse(HeapEntry {
value,
descriptor: value.descriptor(),
}));
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_min_heap_pop(ctx: *mut RuntimeContext, heap_ref: GcRef) -> GcRef {
abi_guard!("praxis_min_heap_pop", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { min_heap_payload_mut(scope.root(heap_ref)) };
match p.items.pop() {
Some(e) => e.0.value,
None => {
unsafe { set_fault(ctx, RaisedFault::EMPTY_COLLECTION) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_min_heap_peek(ctx: *mut RuntimeContext, heap_ref: GcRef) -> GcRef {
abi_guard!("praxis_min_heap_peek", ctx, {
let p = unsafe { min_heap_payload(heap_ref) };
match p.items.peek() {
Some(e) => e.0.value,
None => {
unsafe { set_fault(ctx, RaisedFault::EMPTY_COLLECTION) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_min_heap_len(ctx: *mut RuntimeContext, heap_ref: GcRef) -> GcRef {
abi_guard!("praxis_min_heap_len", ctx, {
let p = unsafe { min_heap_payload(heap_ref) };
unsafe { int_ref(ctx, p.items.len() as i64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_min_heap_items(ctx: *mut RuntimeContext, heap_ref: GcRef) -> GcRef {
abi_guard!("praxis_min_heap_items", ctx, {
let p = unsafe { min_heap_payload(heap_ref) };
let items = crate::heaps::in_pop_order(&p.items, |e| e.0.value);
unsafe { vec_of(ctx, p.element_descriptor, items.into_iter()) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_min_heap_is_empty(
ctx: *mut RuntimeContext,
heap_ref: GcRef,
) -> GcRef {
abi_guard!("praxis_min_heap_is_empty", ctx, {
let p = unsafe { min_heap_payload(heap_ref) };
unsafe { bool_ref(ctx, p.items.is_empty()) }
})
}
use crate::bitset::{BitIndex, BitSetPayload};
unsafe fn bitset_payload(r: GcRef) -> &'static BitSetPayload {
unsafe { payload_ref::<BitSetPayload>(r) }
}
unsafe fn bitset_payload_mut<'s>(r: Rooted<'s>) -> &'s mut BitSetPayload {
unsafe { payload_mut::<BitSetPayload>(r) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bitset_new(ctx: *mut RuntimeContext) -> GcRef {
abi_guard!("praxis_bitset_new", ctx, {
unsafe {
gc_alloc_owned(ctx, &crate::bitset::BITSET, || BitSetPayload {
words: ReprCVec::new(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bitset_insert(
ctx: *mut RuntimeContext,
bs: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_bitset_insert", ctx, {
unsafe { maybe_collect(ctx) };
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { bitset_payload_mut(scope.root(bs)) };
let i = unsafe { int_payload(value) };
let Some(index) = BitIndex::new(i) else {
unsafe { set_fault(ctx, RaisedFault::INVALID_SIZE) };
return unsafe { unit_sentinel(ctx) };
};
let before = p.owned_bytes();
p.insert(index);
charge_growth(ctx, before, p.owned_bytes());
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bitset_remove(
ctx: *mut RuntimeContext,
bs: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_bitset_remove", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { bitset_payload_mut(scope.root(bs)) };
let i = unsafe { int_payload(value) };
if let Some(index) = BitIndex::new(i) {
p.remove(index);
}
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bitset_contains(
ctx: *mut RuntimeContext,
bs: GcRef,
value: GcRef,
) -> i64 {
abi_guard!("praxis_bitset_contains", ctx, {
let p = unsafe { bitset_payload(bs) };
let i = unsafe { int_payload(value) };
let present = BitIndex::new(i).is_some_and(|index| p.contains(index));
i64::from(present)
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bitset_len(ctx: *mut RuntimeContext, bs: GcRef) -> GcRef {
abi_guard!("praxis_bitset_len", ctx, {
let p = unsafe { bitset_payload(bs) };
unsafe { int_ref(ctx, p.count() as i64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bitset_items(ctx: *mut RuntimeContext, bs: GcRef) -> GcRef {
abi_guard!("praxis_bitset_items", ctx, {
let members: Vec<i64> = unsafe { bitset_payload(bs) }.members().collect();
let result = unsafe { praxis_vec_new(ctx, &scalars::INT as *const _) };
let scope = unsafe { NativeScope::new(ctx) };
let rooted = scope.root(result);
for value in members {
let boxed = unsafe { int_ref(ctx, value) };
unsafe { vec_payload_mut(rooted) }.items.push(boxed);
}
result
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bitset_is_empty(ctx: *mut RuntimeContext, bs: GcRef) -> GcRef {
abi_guard!("praxis_bitset_is_empty", ctx, {
let p = unsafe { bitset_payload(bs) };
unsafe { bool_ref(ctx, p.count() == 0) }
})
}
use crate::collections::{GridExtent, GridPayload};
unsafe fn grid_payload(r: GcRef) -> &'static GridPayload {
unsafe { payload_ref::<GridPayload>(r) }
}
unsafe fn grid_payload_mut<'s>(r: Rooted<'s>) -> &'s mut GridPayload {
unsafe { payload_mut::<GridPayload>(r) }
}
unsafe fn alloc_point(ctx: *mut RuntimeContext, x: i64, y: i64) -> GcRef {
let scope = unsafe { NativeScope::new(ctx) };
let schema = crate::tuples::point_schema();
let schema_ptr = schema as *const crate::tuples::TupleSchema;
let tup = scope.root(unsafe { praxis_alloc_tuple(ctx, schema_ptr) });
let x_ref = scope.root(unsafe { int_ref(ctx, x) });
unsafe { praxis_tuple_set(ctx, tup.get(), 0, x_ref.get()) };
let y_ref = unsafe { int_ref(ctx, y) };
unsafe { praxis_tuple_set(ctx, tup.get(), 1, y_ref) };
tup.get()
}
fn grid_xy(idx: usize, width: usize) -> (i64, i64) {
((idx % width) as i64, (idx / width) as i64)
}
unsafe fn point_xy(point: GcRef) -> (i64, i64) {
let tp = point.payload::<crate::tuples::TuplePayload>() as *const crate::tuples::TuplePayload;
let pt = unsafe { &*tp };
unsafe { (int_payload(pt.items[0]), int_payload(pt.items[1])) }
}
fn grid_height(items_len: usize, width: usize) -> usize {
items_len.checked_div(width).unwrap_or(0)
}
fn grid_neighbor(
px: i64,
py: i64,
dx: i64,
dy: i64,
width: usize,
height: usize,
) -> Option<(i64, i64)> {
let nx = px.checked_add(dx)?;
let ny = py.checked_add(dy)?;
(nx >= 0 && ny >= 0 && (nx as usize) < width && (ny as usize) < height).then_some((nx, ny))
}
unsafe fn default_cell(
ctx: *mut RuntimeContext,
descriptor: *const TypeDescriptor,
) -> Option<GcRef> {
use crate::descriptor::BuiltinTypeId as B;
let builtin = unsafe { descriptor.as_ref() }?.as_builtin()?;
unsafe {
match builtin {
B::Unit => Some(unit_sentinel(ctx)),
B::Bool => Some(bool_ref(ctx, false)),
B::Int => Some(int_ref(ctx, 0_i64)),
B::Byte => Some(gc_alloc(ctx, scalars::BYTE_PAYLOAD, 0_u8)),
B::Char => Some(char_ref(ctx, 0_u32)),
B::Float => Some(gc_alloc(ctx, scalars::FLOAT_PAYLOAD, 0.0_f64)),
B::Text => Some(praxis_alloc_text(ctx, std::ptr::null(), 0)),
B::Vec
| B::Deque
| B::Grid
| B::Map
| B::Set
| B::Counter
| B::MinHeap
| B::MaxHeap
| B::BitSet
| B::Range
| B::Tuple
| B::Record
| B::Enum
| B::Closure
| B::VarCell => None,
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_new(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
width: i64,
height: i64,
) -> GcRef {
abi_guard!("praxis_grid_new", ctx, {
let Some(extent) = GridExtent::new(width, height) else {
unsafe { set_fault(ctx, RaisedFault::INVALID_SIZE) };
return unsafe { unit_sentinel(ctx) };
};
let cells = if extent.cells() == 0 {
Vec::new()
} else {
let Some(fill) = (unsafe { default_cell(ctx, element_descriptor) }) else {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return unsafe { unit_sentinel(ctx) };
};
vec![fill; extent.cells()]
};
unsafe {
gc_alloc_owned(ctx, &crate::collections::GRID, || GridPayload {
element_descriptor,
items: cells,
width: extent.width(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_filled(
ctx: *mut RuntimeContext,
element_descriptor: *const TypeDescriptor,
width: GcRef,
height: GcRef,
fill: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_filled", ctx, {
let (w, h) = unsafe { (int_payload(width), int_payload(height)) };
let Some(extent) = GridExtent::new(w, h) else {
unsafe { set_fault(ctx, RaisedFault::INVALID_SIZE) };
return unsafe { unit_sentinel(ctx) };
};
let mut descriptor = element_descriptor;
if !unsafe { adopt_or_reject(ctx, &mut descriptor, fill) } {
return unsafe { unit_sentinel(ctx) };
}
let scope = unsafe { NativeScope::new(ctx) };
let fill = scope.root(fill).get();
unsafe {
gc_alloc_owned(ctx, &crate::collections::GRID, || GridPayload {
element_descriptor: descriptor,
items: vec![fill; extent.cells()],
width: extent.width(),
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_width(ctx: *mut RuntimeContext, grid: GcRef) -> GcRef {
abi_guard!("praxis_grid_width", ctx, {
let p = unsafe { grid_payload(grid) };
unsafe { int_ref(ctx, p.width as i64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_height(ctx: *mut RuntimeContext, grid: GcRef) -> GcRef {
abi_guard!("praxis_grid_height", ctx, {
let p = unsafe { grid_payload(grid) };
let height = grid_height(p.items.len(), p.width);
unsafe { int_ref(ctx, height as i64) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_get(
ctx: *mut RuntimeContext,
grid: GcRef,
x: GcRef,
y: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_get", ctx, {
let p = unsafe { grid_payload(grid) };
let (xi, yi) = (unsafe { int_payload(x) }, unsafe { int_payload(y) });
let height = grid_height(p.items.len(), p.width);
let Some(idx) = (unsafe { checked_cell(ctx, xi, yi, p.width, height) }) else {
return unsafe { unit_sentinel(ctx) };
};
p.items[idx]
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_set(
ctx: *mut RuntimeContext,
grid: GcRef,
x: GcRef,
y: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_set", ctx, {
let scope = unsafe { NativeScope::new(ctx) };
let p = unsafe { grid_payload_mut(scope.root(grid)) };
let (xi, yi) = (unsafe { int_payload(x) }, unsafe { int_payload(y) });
let height = grid_height(p.items.len(), p.width);
let Some(idx) = (unsafe { checked_cell(ctx, xi, yi, p.width, height) }) else {
return unsafe { unit_sentinel(ctx) };
};
if !unsafe { adopt_or_reject(ctx, &mut p.element_descriptor, value) } {
return unsafe { unit_sentinel(ctx) };
}
p.items[idx] = value;
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_contains(
ctx: *mut RuntimeContext,
grid: GcRef,
x: GcRef,
y: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_contains", ctx, {
let p = unsafe { grid_payload(grid) };
let (xi, yi) = (unsafe { int_payload(x) }, unsafe { int_payload(y) });
let height = grid_height(p.items.len(), p.width);
let inside = cell_index(xi, yi, p.width, height).is_some();
unsafe { bool_ref(ctx, inside) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_neighbors4(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_neighbors4", ctx, {
let p = unsafe { grid_payload(grid) };
let (px, py) = unsafe { point_xy(point) };
let height = grid_height(p.items.len(), p.width);
let result = unsafe { praxis_vec_new(ctx, &crate::tuples::TUPLE as *const _) };
let scope = unsafe { NativeScope::new(ctx) };
let rp = unsafe { vec_payload_mut(scope.root(result)) };
for (dx, dy) in [(0i64, -1), (0, 1), (-1, 0), (1, 0)] {
if let Some((nx, ny)) = grid_neighbor(px, py, dx, dy, p.width, height) {
let pt_ref = unsafe { alloc_point(ctx, nx, ny) };
rp.items.push(pt_ref);
}
}
result
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_neighbors8(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_neighbors8", ctx, {
let p = unsafe { grid_payload(grid) };
let (px, py) = unsafe { point_xy(point) };
let height = grid_height(p.items.len(), p.width);
let result = unsafe { praxis_vec_new(ctx, &crate::tuples::TUPLE as *const _) };
let scope = unsafe { NativeScope::new(ctx) };
let rp = unsafe { vec_payload_mut(scope.root(result)) };
for dy in -1i64..=1 {
for dx in -1i64..=1 {
if dx == 0 && dy == 0 {
continue;
}
if let Some((nx, ny)) = grid_neighbor(px, py, dx, dy, p.width, height) {
let pt_ref = unsafe { alloc_point(ctx, nx, ny) };
rp.items.push(pt_ref);
}
}
}
result
})
}
unsafe fn grid_around(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
schema: &'static crate::records::RecordSchema,
directions: &'static [crate::records::Direction],
) -> GcRef {
let (width, height) = unsafe {
let p = grid_payload(grid);
(p.width, grid_height(p.items.len(), p.width))
};
let (px, py) = unsafe { point_xy(point) };
let scope = unsafe { NativeScope::new(ctx) };
let record = scope.root(unsafe { praxis_alloc_record(ctx, schema) });
for (i, d) in directions.iter().enumerate() {
let field = match grid_neighbor(px, py, d.dx, d.dy, width, height) {
Some((nx, ny)) => unsafe { option_some(ctx, alloc_point(ctx, nx, ny)) },
None => unsafe { option_none(ctx) },
};
unsafe { praxis_record_set_field(ctx, record.get(), i as u32, field) };
}
record.get()
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_around4(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_around4", ctx, {
unsafe {
grid_around(
ctx,
grid,
point,
crate::records::around4_schema(),
crate::records::AROUND4_DIRECTIONS,
)
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_around8(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_around8", ctx, {
unsafe {
grid_around(
ctx,
grid,
point,
crate::records::around8_schema(),
crate::records::AROUND8_DIRECTIONS,
)
}
})
}
unsafe fn grid_count_equal(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
value: GcRef,
directions: &'static [crate::records::Direction],
) -> GcRef {
let p = unsafe { grid_payload(grid) };
let height = grid_height(p.items.len(), p.width);
let (px, py) = unsafe { point_xy(point) };
let eq = value.descriptor().equals;
let mut n = 0_i64;
for d in directions {
let Some((nx, ny)) = grid_neighbor(px, py, d.dx, d.dy, p.width, height) else {
continue;
};
let cell = p.items[ny as usize * p.width + nx as usize];
let matches = match eq {
Some(equals) => unsafe {
equals(
cell.payload::<u8>() as *const u8,
value.payload::<u8>() as *const u8,
)
},
None => cell == value,
};
n += i64::from(matches);
}
unsafe { int_ref(ctx, n) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_count4(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_count4", ctx, {
unsafe { grid_count_equal(ctx, grid, point, value, crate::records::AROUND4_DIRECTIONS) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_count8(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_count8", ctx, {
unsafe { grid_count_equal(ctx, grid, point, value, crate::records::AROUND8_DIRECTIONS) }
})
}
unsafe fn grid_count_where(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
pred: GcRef,
directions: &'static [crate::records::Direction],
) -> GcRef {
let scope = unsafe { NativeScope::new(ctx) };
let cells: Vec<GcRef> = unsafe {
let p = grid_payload(grid);
let height = grid_height(p.items.len(), p.width);
let (px, py) = point_xy(point);
directions
.iter()
.filter_map(|d| grid_neighbor(px, py, d.dx, d.dy, p.width, height))
.map(|(nx, ny)| {
scope
.root(p.items[ny as usize * p.width + nx as usize])
.get()
})
.collect()
};
let mut n = 0_i64;
for cell in cells {
let Some(answer) = (unsafe { call_unary_closure(ctx, pred, cell) }) else {
return unsafe { unit_sentinel(ctx) };
};
let Some(byte) = (unsafe { read_scalar(answer, scalars::BOOL_PAYLOAD) }) else {
unsafe { set_fault(ctx, RaisedFault::TYPE_MISMATCH) };
return unsafe { unit_sentinel(ctx) };
};
n += i64::from(byte != 0);
}
unsafe { int_ref(ctx, n) }
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_count4_where(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
pred: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_count4_where", ctx, {
unsafe { grid_count_where(ctx, grid, point, pred, crate::records::AROUND4_DIRECTIONS) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_count8_where(
ctx: *mut RuntimeContext,
grid: GcRef,
point: GcRef,
pred: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_count8_where", ctx, {
unsafe { grid_count_where(ctx, grid, point, pred, crate::records::AROUND8_DIRECTIONS) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_positions(ctx: *mut RuntimeContext, grid: GcRef) -> GcRef {
abi_guard!("praxis_grid_positions", ctx, {
unsafe { maybe_collect(ctx) };
let p = unsafe { grid_payload(grid) };
let result = unsafe { praxis_vec_new(ctx, &crate::tuples::TUPLE as *const _) };
let scope = unsafe { NativeScope::new(ctx) };
let rp = unsafe { vec_payload_mut(scope.root(result)) };
for i in 0..p.items.len() {
let (x, y) = grid_xy(i, p.width);
rp.items.push(unsafe { alloc_point(ctx, x, y) });
}
result
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_cells(ctx: *mut RuntimeContext, grid: GcRef) -> GcRef {
abi_guard!("praxis_grid_cells", ctx, {
let p = unsafe { grid_payload(grid) };
let result = unsafe { praxis_vec_new(ctx, p.element_descriptor) };
let scope = unsafe { NativeScope::new(ctx) };
let rp = unsafe { vec_payload_mut(scope.root(result)) };
for cell in p.items.iter() {
rp.items.push(*cell);
}
result
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_row(ctx: *mut RuntimeContext, grid: GcRef, y: GcRef) -> GcRef {
abi_guard!("praxis_grid_row", ctx, {
let p = unsafe { grid_payload(grid) };
let yi = unsafe { int_payload(y) };
let height = grid_height(p.items.len(), p.width);
let Some(row) = (unsafe { checked_index(ctx, yi, height) }) else {
return unsafe { unit_sentinel(ctx) };
};
let start = row * p.width;
let result = unsafe { praxis_vec_new(ctx, p.element_descriptor) };
let scope = unsafe { NativeScope::new(ctx) };
let rp = unsafe { vec_payload_mut(scope.root(result)) };
for x in 0..p.width {
rp.items.push(p.items[start + x]);
}
result
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_column(
ctx: *mut RuntimeContext,
grid: GcRef,
x: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_column", ctx, {
let p = unsafe { grid_payload(grid) };
let xi = unsafe { int_payload(x) };
let Some(col) = (unsafe { checked_index(ctx, xi, p.width) }) else {
return unsafe { unit_sentinel(ctx) };
};
let result = unsafe { praxis_vec_new(ctx, p.element_descriptor) };
let scope = unsafe { NativeScope::new(ctx) };
let rp = unsafe { vec_payload_mut(scope.root(result)) };
let mut idx = col;
while idx < p.items.len() {
rp.items.push(p.items[idx]);
idx += p.width;
}
result
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_find(
ctx: *mut RuntimeContext,
grid: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_find", ctx, {
let p = unsafe { grid_payload(grid) };
let val_desc = value.descriptor();
let eq = val_desc.equals;
for (i, cell) in p.items.iter().enumerate() {
let matches = match eq {
Some(equals) => {
let a = cell.payload::<u8>() as *const u8;
let b = value.payload::<u8>() as *const u8;
unsafe { equals(a, b) }
}
None => *cell == value,
};
if matches {
let (x, y) = grid_xy(i, p.width);
return unsafe { option_some(ctx, alloc_point(ctx, x, y)) };
}
}
unsafe { option_none(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_find_all(
ctx: *mut RuntimeContext,
grid: GcRef,
value: GcRef,
) -> GcRef {
abi_guard!("praxis_grid_find_all", ctx, {
unsafe { maybe_collect(ctx) };
let p = unsafe { grid_payload(grid) };
let val_desc = value.descriptor();
let eq = val_desc.equals;
let result = unsafe { praxis_vec_new(ctx, &crate::tuples::TUPLE as *const _) };
let scope = unsafe { NativeScope::new(ctx) };
let rp = unsafe { vec_payload_mut(scope.root(result)) };
for (i, cell) in p.items.iter().enumerate() {
let matches = match eq {
Some(equals) => {
let a = cell.payload::<u8>() as *const u8;
let b = value.payload::<u8>() as *const u8;
unsafe { equals(a, b) }
}
None => *cell == value,
};
if matches {
let (x, y) = grid_xy(i, p.width);
rp.items.push(unsafe { alloc_point(ctx, x, y) });
}
}
result
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_transpose(ctx: *mut RuntimeContext, grid: GcRef) -> GcRef {
abi_guard!("praxis_grid_transpose", ctx, {
let p = unsafe { grid_payload(grid) };
let height = grid_height(p.items.len(), p.width);
let new_width = height;
let new_height = p.width;
let mut cells = Vec::with_capacity(p.items.len());
for y in 0..new_height {
for x in 0..new_width {
cells.push(p.items[x * p.width + y]);
}
}
let _ = ctx;
unsafe {
gc_alloc_owned(ctx, &crate::collections::GRID, || GridPayload {
element_descriptor: p.element_descriptor,
items: cells,
width: new_width,
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_rotate_left(ctx: *mut RuntimeContext, grid: GcRef) -> GcRef {
abi_guard!("praxis_grid_rotate_left", ctx, {
let p = unsafe { grid_payload(grid) };
let height = grid_height(p.items.len(), p.width);
let new_width = height;
let new_height = p.width;
let mut cells = Vec::with_capacity(p.items.len());
for y in 0..new_height {
for x in 0..new_width {
let ox = p.width - 1 - y;
let oy = x;
cells.push(p.items[oy * p.width + ox]);
}
}
unsafe {
gc_alloc_owned(ctx, &crate::collections::GRID, || GridPayload {
element_descriptor: p.element_descriptor,
items: cells,
width: new_width,
})
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_grid_rotate_right(ctx: *mut RuntimeContext, grid: GcRef) -> GcRef {
abi_guard!("praxis_grid_rotate_right", ctx, {
let p = unsafe { grid_payload(grid) };
let height = grid_height(p.items.len(), p.width);
let new_width = height;
let new_height = p.width;
let mut cells = Vec::with_capacity(p.items.len());
for y in 0..new_height {
for x in 0..new_width {
let ox = y;
let oy = height - 1 - x;
cells.push(p.items[oy * p.width + ox]);
}
}
unsafe {
gc_alloc_owned(ctx, &crate::collections::GRID, || GridPayload {
element_descriptor: p.element_descriptor,
items: cells,
width: new_width,
})
}
})
}
unsafe fn text_str(r: GcRef) -> &'static str {
let payload = r.payload::<crate::text::TextPayload>() as *const crate::text::TextPayload;
unsafe { crate::text::text_str(payload) }
}
#[inline]
unsafe fn text_payload(r: GcRef) -> *const crate::text::TextPayload {
r.payload::<crate::text::TextPayload>() as *const crate::text::TextPayload
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_text_len(ctx: *mut RuntimeContext, text: GcRef) -> GcRef {
abi_guard!("praxis_text_len", ctx, {
let len = unsafe { crate::text::text_char_count(text_payload(text)) } as i64;
unsafe { int_ref(ctx, len) }
})
}
fn whole_trimmed(s: &str, run: fn(&[u8]) -> (&str, usize)) -> Option<&str> {
let trimmed = s.trim();
let (text, len) = run(trimmed.as_bytes());
(!text.is_empty() && len == trimmed.len()).then_some(trimmed)
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_text_int(ctx: *mut RuntimeContext, text: GcRef) -> GcRef {
abi_guard!("praxis_text_int", ctx, {
let s = unsafe { text_str(text) };
match whole_trimmed(s, crate::parser::take_int_run).and_then(|t| t.parse::<i64>().ok()) {
Some(n) => unsafe {
let boxed = int_ref(ctx, n);
option_some(ctx, boxed)
},
None => unsafe { option_none(ctx) },
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_text_float(ctx: *mut RuntimeContext, text: GcRef) -> GcRef {
abi_guard!("praxis_text_float", ctx, {
let s = unsafe { text_str(text) };
match whole_trimmed(s, crate::parser::take_float_run).and_then(|t| t.parse::<f64>().ok()) {
Some(x) => unsafe {
let boxed = praxis_alloc_float(ctx, x.to_bits() as i64);
option_some(ctx, boxed)
},
None => unsafe { option_none(ctx) },
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_text_is_empty(ctx: *mut RuntimeContext, text: GcRef) -> GcRef {
abi_guard!("praxis_text_is_empty", ctx, {
let empty = unsafe { crate::text::text_bytes(text_payload(text)) }.is_empty();
unsafe { bool_ref(ctx, empty) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_text_concat(ctx: *mut RuntimeContext, a: GcRef, b: GcRef) -> GcRef {
abi_guard!("praxis_text_concat", ctx, {
let left = unsafe { text_str(a) };
let right = unsafe { text_str(b) };
let mut joined = String::with_capacity(left.len() + right.len());
joined.push_str(left);
joined.push_str(right);
unsafe { text_ref(ctx, joined) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_value_to_text(ctx: *mut RuntimeContext, value: GcRef) -> GcRef {
abi_guard!("praxis_value_to_text", ctx, {
let mut s = String::new();
value.format(&mut s);
unsafe { text_ref(ctx, s) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_text_get(
ctx: *mut RuntimeContext,
text: GcRef,
index: GcRef,
) -> GcRef {
abi_guard!("praxis_text_get", ctx, {
let payload = unsafe { text_payload(text) };
let idx = unsafe { int_payload(index) };
if idx < 0 {
unsafe { set_fault(ctx, RaisedFault::INDEX_OUT_OF_BOUNDS) };
return unsafe { unit_sentinel(ctx) };
}
if let Some(bytes) = unsafe { crate::text::text_ascii_bytes(payload) } {
return match bytes.get(idx as usize) {
Some(&b) => unsafe { char_ref(ctx, u32::from(b)) },
None => {
unsafe { set_fault(ctx, RaisedFault::INDEX_OUT_OF_BOUNDS) };
unsafe { unit_sentinel(ctx) }
}
};
}
let s = unsafe { text_str(text) };
match s.chars().nth(idx as usize) {
Some(ch) => {
unsafe { char_ref(ctx, ch as u32) }
}
None => {
unsafe { set_fault(ctx, RaisedFault::INDEX_OUT_OF_BOUNDS) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_write_stdout(ctx: *mut RuntimeContext, value: GcRef) -> GcRef {
abi_guard!("praxis_write_stdout", ctx, {
use std::io::Write;
let mut out = String::new();
value.format(&mut out);
let _ = std::io::stdout().write_all(out.as_bytes());
let _ = std::io::stdout().write_all(b"\n");
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_dbg(_ctx: *mut RuntimeContext, value: GcRef) -> GcRef {
abi_guard!("praxis_dbg", _ctx, {
use std::io::Write;
let mut rendered = String::new();
value.format(&mut rendered);
let _ = std::io::stderr().write_all(rendered.as_bytes());
let _ = std::io::stderr().write_all(b"\n");
value
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_panic(ctx: *mut RuntimeContext, value: GcRef) -> GcRef {
abi_guard!("praxis_panic", ctx, {
let mut message = String::new();
value.format(&mut message);
unsafe { set_fault_message(ctx, message) };
unsafe { set_fault(ctx, RaisedFault::PANIC) };
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_assert(ctx: *mut RuntimeContext, condition: GcRef) -> GcRef {
abi_guard!("praxis_assert", ctx, {
if !unsafe { crate::immortal::read_bool(condition) } {
unsafe { set_fault(ctx, RaisedFault::ASSERT_FAILED) };
}
unsafe { unit_sentinel(ctx) }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_range_new(
ctx: *mut RuntimeContext,
start: GcRef,
end: GcRef,
) -> GcRef {
abi_guard!("praxis_range_new", ctx, {
let a = unsafe { int_payload(start) };
let b = unsafe { int_payload(end) };
unsafe {
gc_alloc(
ctx,
crate::range::RANGE_PAYLOAD,
crate::range::RangeVal::new(a, b),
)
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_range_new_inclusive(
ctx: *mut RuntimeContext,
start: GcRef,
end: GcRef,
) -> GcRef {
abi_guard!("praxis_range_new_inclusive", ctx, {
let a = unsafe { int_payload(start) };
let b = unsafe { int_payload(end) };
unsafe {
gc_alloc(
ctx,
crate::range::RANGE_PAYLOAD,
crate::range::RangeVal::new_inclusive(a, b),
)
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_range_len(ctx: *mut RuntimeContext, r: GcRef) -> GcRef {
abi_guard!("praxis_range_len", ctx, {
let range = unsafe { &*r.payload::<crate::range::RangeVal>() };
match i64::try_from(range.len()) {
Ok(len) => unsafe { int_ref(ctx, len) },
Err(_) => {
unsafe { set_fault(ctx, RaisedFault::INT_OVERFLOW) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_range_get(
ctx: *mut RuntimeContext,
r: GcRef,
index: GcRef,
) -> GcRef {
abi_guard!("praxis_range_get", ctx, {
let range = unsafe { &*r.payload::<crate::range::RangeVal>() };
let i = unsafe { int_payload(index) };
match range.get(i) {
Some(value) => unsafe { int_ref(ctx, value) },
None => {
unsafe { set_fault(ctx, RaisedFault::INDEX_OUT_OF_BOUNDS) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_get_input(ctx: *mut RuntimeContext) -> GcRef {
abi_guard!("praxis_get_input", ctx, {
if let Some(read) = crate::input::take_input_reader() {
let bytes = read();
let Ok(text) = std::str::from_utf8(&bytes) else {
unsafe { set_fault(ctx, RaisedFault::INVALID_TEXT) };
return unsafe { (*ctx).input_source };
};
let text = unsafe { praxis_alloc_text(ctx, text.as_ptr(), text.len()) };
unsafe { (*ctx).input_source = text };
}
unsafe { (*ctx).input_source }
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_run_parser(
ctx: *mut RuntimeContext,
plan_index_gc: GcRef,
input: GcRef,
) -> GcRef {
abi_guard!("praxis_run_parser", ctx, {
if input.descriptor().id() != crate::text::TEXT.id() {
unsafe { crate::parser::clear_parse_detail(ctx) };
unsafe { set_fault(ctx, RaisedFault::PARSE_FAILED) };
return unsafe { unit_sentinel(ctx) };
}
let idx = unsafe { int_payload(plan_index_gc) };
match unsafe { crate::parser::run_plan_by_id(ctx, idx, input) } {
Some(result) => result,
None => {
unsafe { set_fault(ctx, RaisedFault::PARSE_FAILED) };
unsafe { unit_sentinel(ctx) }
}
}
})
}
struct ClosureOracle<'s, 'c> {
ctx: *mut RuntimeContext,
scope: &'s NativeScope<'c>,
neighbours: GcRef,
weight: GcRef,
heuristic: GcRef,
goal: GcRef,
}
impl ClosureOracle<'_, '_> {
unsafe fn call(
&mut self,
closure: GcRef,
args: &[GcRef],
) -> Result<GcRef, crate::graph::Aborted> {
if !std::ptr::eq(closure.descriptor(), &crate::closures::CLOSURE) {
return Err(self.abort(crate::context::FaultKind::TypeMismatch));
}
let fn_ptr = unsafe { (*closure.payload::<crate::closures::ClosurePayload>()).fn_ptr };
let result = match args {
[a] => unsafe {
let f: unsafe extern "C" fn(*mut RuntimeContext, GcRef, GcRef) -> GcRef =
std::mem::transmute(fn_ptr);
f(self.ctx, closure, *a)
},
[a, b] => unsafe {
let f: unsafe extern "C" fn(*mut RuntimeContext, GcRef, GcRef, GcRef) -> GcRef =
std::mem::transmute(fn_ptr);
f(self.ctx, closure, *a, *b)
},
_ => return Err(self.abort(crate::context::FaultKind::TypeMismatch)),
};
if unsafe { praxis_check_fault(self.ctx) } != 0 {
return Err(crate::graph::Aborted);
}
Ok(self.scope.root(result).get())
}
unsafe fn int_result(&mut self, value: GcRef) -> Result<i64, crate::graph::Aborted> {
if !std::ptr::eq(value.descriptor(), &scalars::INT) {
return Err(self.abort(crate::context::FaultKind::TypeMismatch));
}
Ok(unsafe { int_payload(value) })
}
}
impl crate::graph::GraphOracle for ClosureOracle<'_, '_> {
fn neighbours(&mut self, state: GcRef) -> Result<Vec<GcRef>, crate::graph::Aborted> {
let result = unsafe { self.call(self.neighbours, &[state])? };
if !std::ptr::eq(result.descriptor(), &crate::collections::VEC) {
return Err(self.abort(crate::context::FaultKind::TypeMismatch));
}
let items = unsafe { (*result.payload::<VecPayload>()).items.to_vec() };
for item in &items {
self.scope.root(*item);
}
Ok(items)
}
fn weight(&mut self, from: GcRef, to: GcRef) -> Result<i64, crate::graph::Aborted> {
let result = unsafe { self.call(self.weight, &[from, to])? };
unsafe { self.int_result(result) }
}
fn heuristic(&mut self, state: GcRef) -> Result<i64, crate::graph::Aborted> {
let result = unsafe { self.call(self.heuristic, &[state])? };
unsafe { self.int_result(result) }
}
fn is_goal(&mut self, state: GcRef) -> Result<bool, crate::graph::Aborted> {
let result = unsafe { self.call(self.goal, &[state])? };
match unsafe { read_scalar(result, scalars::BOOL_PAYLOAD) } {
Some(b) => Ok(b != 0),
None => Err(self.abort(crate::context::FaultKind::TypeMismatch)),
}
}
fn retain(&mut self, state: GcRef) {
self.scope.root(state);
}
fn abort(&mut self, kind: crate::context::FaultKind) -> crate::graph::Aborted {
if let Some(fault) = RaisedFault::new(kind) {
unsafe { set_fault(self.ctx, fault) };
}
crate::graph::Aborted
}
}
#[inline]
fn state_descriptor(start: GcRef) -> *const TypeDescriptor {
start.descriptor() as *const TypeDescriptor
}
unsafe fn states_as_vec(
ctx: *mut RuntimeContext,
element: *const TypeDescriptor,
states: &[GcRef],
) -> GcRef {
let result = unsafe { praxis_vec_new(ctx, element) };
let scope = unsafe { NativeScope::new(ctx) };
let rooted = scope.root(result);
let payload = unsafe { vec_payload_mut(rooted) };
payload.items.extend_from_slice(states);
result
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bfs(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
) -> GcRef {
abi_guard!("praxis_bfs", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight: unit_sentinel(ctx),
heuristic: unit_sentinel(ctx),
goal: unit_sentinel(ctx),
};
match crate::graph::bfs_order(&mut oracle, start) {
Ok(states) => states_as_vec(ctx, state_descriptor(start), &states),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_dfs(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
) -> GcRef {
abi_guard!("praxis_dfs", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight: unit_sentinel(ctx),
heuristic: unit_sentinel(ctx),
goal: unit_sentinel(ctx),
};
match crate::graph::dfs_order(&mut oracle, start) {
Ok(states) => states_as_vec(ctx, state_descriptor(start), &states),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_flood_fill(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
) -> GcRef {
abi_guard!("praxis_flood_fill", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight: unit_sentinel(ctx),
heuristic: unit_sentinel(ctx),
goal: unit_sentinel(ctx),
};
let states = match crate::graph::reachable(&mut oracle, start) {
Ok(states) => states,
Err(_) => return unit_sentinel(ctx),
};
let result = praxis_set_new(ctx, state_descriptor(start));
let rooted = scope.root(result);
let payload = set_payload_mut(rooted);
for state in states {
payload.entries.insert(DynamicKey::new(state));
}
result
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bfs_distance(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
goal: GcRef,
) -> GcRef {
abi_guard!("praxis_bfs_distance", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight: unit_sentinel(ctx),
heuristic: unit_sentinel(ctx),
goal,
};
match crate::graph::bfs_route(&mut oracle, start) {
Ok(route) => alloc_optional_int(ctx, route.map(|r| r.cost)),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_bfs_path(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
goal: GcRef,
) -> GcRef {
abi_guard!("praxis_bfs_path", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight: unit_sentinel(ctx),
heuristic: unit_sentinel(ctx),
goal,
};
match crate::graph::bfs_route(&mut oracle, start) {
Ok(route) => states_as_optional_vec(
ctx,
state_descriptor(start),
route.as_ref().map(|r| r.states.as_slice()),
),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_dfs_distance(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
goal: GcRef,
) -> GcRef {
abi_guard!("praxis_dfs_distance", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight: unit_sentinel(ctx),
heuristic: unit_sentinel(ctx),
goal,
};
match crate::graph::dfs_route(&mut oracle, start) {
Ok(route) => alloc_optional_int(ctx, route.map(|r| r.cost)),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_dfs_path(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
goal: GcRef,
) -> GcRef {
abi_guard!("praxis_dfs_path", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight: unit_sentinel(ctx),
heuristic: unit_sentinel(ctx),
goal,
};
match crate::graph::dfs_route(&mut oracle, start) {
Ok(route) => states_as_optional_vec(
ctx,
state_descriptor(start),
route.as_ref().map(|r| r.states.as_slice()),
),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_dijkstra(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
weight: GcRef,
) -> GcRef {
abi_guard!("praxis_dijkstra", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight,
heuristic: unit_sentinel(ctx),
goal: unit_sentinel(ctx),
};
let costs = match crate::graph::dijkstra_costs(&mut oracle, start) {
Ok(costs) => costs,
Err(_) => return unit_sentinel(ctx),
};
let result = scope.root(praxis_map_new(ctx, state_descriptor(start)));
for (state, cost) in costs {
let boxed = scope.root(int_ref(ctx, cost));
map_payload_mut(result)
.entries
.insert(DynamicKey::new(state), boxed.get());
}
result.get()
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_dijkstra_distance(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
weight: GcRef,
goal: GcRef,
) -> GcRef {
abi_guard!("praxis_dijkstra_distance", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight,
heuristic: unit_sentinel(ctx),
goal,
};
match crate::graph::dijkstra_route(&mut oracle, start) {
Ok(route) => alloc_optional_int(ctx, route.map(|r| r.cost)),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_dijkstra_path(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
weight: GcRef,
goal: GcRef,
) -> GcRef {
abi_guard!("praxis_dijkstra_path", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight,
heuristic: unit_sentinel(ctx),
goal,
};
match crate::graph::dijkstra_route(&mut oracle, start) {
Ok(route) => states_as_optional_vec(
ctx,
state_descriptor(start),
route.as_ref().map(|r| r.states.as_slice()),
),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_a_star_distance(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
weight: GcRef,
heuristic: GcRef,
goal: GcRef,
) -> GcRef {
abi_guard!("praxis_a_star_distance", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight,
heuristic,
goal,
};
match crate::graph::a_star_route(&mut oracle, start) {
Ok(route) => alloc_optional_int(ctx, route.map(|r| r.cost)),
Err(_) => unit_sentinel(ctx),
}
}
})
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn praxis_a_star_path(
ctx: *mut RuntimeContext,
start: GcRef,
neighbours: GcRef,
weight: GcRef,
heuristic: GcRef,
goal: GcRef,
) -> GcRef {
abi_guard!("praxis_a_star_path", ctx, {
unsafe {
let scope = NativeScope::new(ctx);
let mut oracle = ClosureOracle {
ctx,
scope: &scope,
neighbours,
weight,
heuristic,
goal,
};
match crate::graph::a_star_route(&mut oracle, start) {
Ok(route) => states_as_optional_vec(
ctx,
state_descriptor(start),
route.as_ref().map(|r| r.states.as_slice()),
),
Err(_) => unit_sentinel(ctx),
}
}
})
}
unsafe fn alloc_optional_int(ctx: *mut RuntimeContext, value: Option<i64>) -> GcRef {
unsafe {
match value {
Some(n) => {
let boxed = int_ref(ctx, n);
option_some(ctx, boxed)
}
None => option_none(ctx),
}
}
}
unsafe fn states_as_optional_vec(
ctx: *mut RuntimeContext,
element: *const TypeDescriptor,
states: Option<&[GcRef]>,
) -> GcRef {
unsafe {
match states {
Some(states) => {
let scope = NativeScope::new(ctx);
let vec = scope.root(states_as_vec(ctx, element, states));
option_some(ctx, vec.get())
}
None => option_none(ctx),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::context::{Fault, FaultKind, Runtime};
use crate::parse_detail::ParseFail;
use crate::shadow_stack::{SlotCount, push_frame};
pub(super) fn wired_ctx(rt: &mut Runtime) -> *mut RuntimeContext {
let ctx = Box::leak(Box::new(rt.context()));
ctx as *mut RuntimeContext
}
pub(super) unsafe fn drop_ctx(ctx: *mut RuntimeContext) {
let _ = unsafe { Box::from_raw(ctx) };
}
const UNINTERNED: i64 = crate::small_int::SMALL_INT_MAX + 1;
unsafe fn allocate_until_automatic_collection(rt: &Runtime, ctx: *mut RuntimeContext) -> usize {
let mut before = rt.heap().stats().live_count;
for i in 0..10_000_i64 {
let _ = unsafe { praxis_alloc_int(ctx, UNINTERNED + i) };
let after = rt.heap().stats().live_count;
if after < before.saturating_add(1) {
return after;
}
before = after;
}
panic!("automatic collection did not run after 10,000 allocations");
}
#[test]
fn version_is_twenty_for_the_batch_this_build_ships() {
assert_eq!(RUNTIME_ABI_VERSION, 20);
}
#[test]
fn assert_passes_within_a_single_build() {
assert_abi_version();
}
#[test]
fn every_scalar_payload_read_goes_through_the_bounded_reader() {
let source = include_str!("abi.rs");
const SIGNATURE: &str = "unsafe fn read_scalar<T: Copy>(r: GcRef, handle: crate::descriptor::Payload<T>) -> Option<T> {";
let at = source
.find(SIGNATURE)
.expect("`read_scalar`'s definition moved; this gate names it by signature");
let body_start = at + SIGNATURE.len();
let body_len = source[body_start..]
.find("\n}")
.expect("`read_scalar` has no closing brace in the first column");
let body = &source[body_start..body_start + body_len];
assert!(
!body.contains("debug_assert"),
"`read_scalar`'s type check is a `debug_assert`, which is compiled out of a \
release build — and what is left is an unchecked read off a payload that may \
be narrower (REP-56). Make it an ordinary branch.\nbody was:{body}"
);
assert!(
body.contains("std::ptr::eq(r.descriptor(), handle.descriptor())"),
"`read_scalar` no longer proves the value is the handle's type before reading \
it (REP-37, REP-56).\nbody was:{body}"
);
let code: String = source[..source
.find("#[cfg(test)]")
.expect("abi.rs has no test module marker")]
.lines()
.filter(|l| !l.trim_start().starts_with("//"))
.collect::<Vec<_>>()
.join("\n");
for forbidden in [
"r.payload::<i64>()",
"r.payload::<f64>()",
"r.payload::<u32>()",
"r.payload::<bool>()",
] {
assert!(
!code.contains(forbidden),
"a scalar payload is read directly as `{forbidden}` instead of through \
`read_scalar`, so its type is unchecked in release (REP-56). Route it \
through `read_scalar(r, scalars::…_PAYLOAD)` instead."
);
}
assert!(
!code.contains("Payload<bool>") && !code.contains("read_scalar::<bool>"),
"a `bool` is read straight out of a payload; read `scalars::BOOL_PAYLOAD` \
(a `u8`) and compare it instead (REP-56)."
);
}
#[test]
fn the_text_precondition_backstop_is_unconditional_in_every_profile() {
let source = include_str!("abi.rs");
const SIGNATURE: &str = "pub unsafe extern \"C\" fn praxis_alloc_text(";
let at = source
.find(SIGNATURE)
.expect("`praxis_alloc_text`'s definition moved; this gate names it by signature");
let body_len = source[at..]
.find("\n}")
.expect("`praxis_alloc_text` has no closing brace in the first column");
let body = &source[at..at + body_len];
assert!(
body.contains("std::str::from_utf8(slice)"),
"`praxis_alloc_text` no longer validates its buffer. The check is the \
backstop on a raw read, not an optimization the `Allocates` row traded \
away (ADR-111).\nbody was:{body}"
);
assert!(
!body.contains("debug_assert"),
"`praxis_alloc_text`'s UTF-8 check is a `debug_assert`, which is compiled \
out of a release build — leaving a `Box<str>` built from bytes that are \
not UTF-8 (REP-56's shape). Make it an ordinary branch.\nbody was:{body}"
);
assert!(
!body.contains("from_utf8_unchecked"),
"`praxis_alloc_text` skips the check outright. A precondition is not a \
licence to read unvalidated bytes as a `str` — the refusal is \
`text_bytes_are_not_utf8`, which costs a never-taken branch \
(ADR-111).\nbody was:{body}"
);
assert!(
!body.contains("set_fault"),
"`praxis_alloc_text` sets a fault. Its row is `Effect::Allocates`, so no \
`CheckFault` follows the call and nothing would ever observe it \
(ADR-088, ADR-111).\nbody was:{body}"
);
}
#[test]
fn a_scalar_read_refuses_a_value_that_is_not_its_type() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let unit = unsafe { praxis_alloc_unit(ctx) };
assert_eq!(unit.descriptor().size(), 0, "Unit is a zero-width payload");
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| unsafe {
int_payload(unit)
}));
std::panic::set_hook(previous);
unsafe { drop_ctx(ctx) };
let payload = outcome.expect_err("a zero-width payload must not be read as eight bytes");
let message = payload
.downcast_ref::<String>()
.map(String::as_str)
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("");
assert!(
message.contains("int_payload wants a `Int` payload")
&& message.contains("this value is a `Unit`"),
"unexpected panic message: {message:?}"
);
}
#[test]
fn alloc_int_and_load_round_trip() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let r = unsafe { praxis_alloc_int(ctx, 9001) };
assert_eq!(unsafe { praxis_int_load(ctx, r) }, 9001);
unsafe { drop_ctx(ctx) };
}
#[test]
fn small_ints_are_one_object_per_value_and_large_ones_are_not() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, 7);
let b = praxis_alloc_int(ctx, 7);
assert_eq!(a.as_ptr(), b.as_ptr());
assert_eq!(a.as_ptr(), rt.immortals().small_int(7).unwrap().as_ptr());
assert_eq!(praxis_int_load(ctx, a), 7);
for v in [
crate::small_int::SMALL_INT_MIN,
crate::small_int::SMALL_INT_MAX,
] {
assert_eq!(
praxis_alloc_int(ctx, v).as_ptr(),
praxis_alloc_int(ctx, v).as_ptr(),
"{v} is the edge of the range and must be interned"
);
}
for v in [
crate::small_int::SMALL_INT_MIN - 1,
crate::small_int::SMALL_INT_MAX + 1,
] {
let x = praxis_alloc_int(ctx, v);
let y = praxis_alloc_int(ctx, v);
assert_ne!(
x.as_ptr(),
y.as_ptr(),
"{v} is outside the range and must still allocate"
);
assert_eq!(praxis_int_load(ctx, x), v, "and still hold its value");
assert_eq!(praxis_int_load(ctx, y), v);
}
assert_ne!(
praxis_alloc_int(ctx, 7).as_ptr(),
praxis_alloc_int(ctx, 8).as_ptr()
);
assert_eq!(rt.alloc_int(7).as_ptr(), a.as_ptr());
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn alloc_char_answers_one_object_per_ascii_code_point_and_a_large_one_still_allocates() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_char(ctx, i64::from('a' as u32));
let b = praxis_alloc_char(ctx, i64::from('a' as u32));
assert_eq!(a.as_ptr(), b.as_ptr());
assert_eq!(
a.as_ptr(),
rt.immortals().small_char('a' as u32).unwrap().as_ptr()
);
assert_eq!(a.as_char(), 'a');
let max = i64::from(crate::small_char::SMALL_CHAR_MAX);
assert_eq!(
praxis_alloc_char(ctx, max).as_ptr(),
praxis_alloc_char(ctx, max).as_ptr(),
"the last ASCII scalar is the edge of the range and must be interned"
);
assert_eq!(
praxis_alloc_char(ctx, 0).as_ptr(),
praxis_alloc_char(ctx, 0).as_ptr(),
"NUL is the floor and must be interned"
);
for code in [max + 1, i64::from('é' as u32), 0x10_FFFF] {
let x = praxis_alloc_char(ctx, code);
let y = praxis_alloc_char(ctx, code);
assert_ne!(
x.as_ptr(),
y.as_ptr(),
"{code:#x} is outside the range and must still allocate"
);
assert_eq!(
u32::from(x.as_char()),
code as u32,
"and still hold its code point"
);
}
assert_ne!(
praxis_alloc_char(ctx, i64::from('a' as u32)).as_ptr(),
praxis_alloc_char(ctx, i64::from('b' as u32)).as_ptr()
);
for bad in [-1_i64, 0xD800, 0x11_0000, 0x1_0000_0041] {
let _ = praxis_alloc_char(ctx, bad);
assert_eq!(
rt.take_fault(),
Some(FaultKind::InvalidChar),
"{bad:#x} is not a scalar value"
);
}
assert_eq!(rt.alloc_char('a' as u32).as_ptr(), a.as_ptr());
assert_ne!(
rt.alloc_char('é' as u32).as_ptr(),
rt.alloc_char('é' as u32).as_ptr()
);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn text_get_answers_the_interned_char() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let s = "abca";
let text = praxis_alloc_text(ctx, s.as_ptr(), s.len());
let zero = praxis_alloc_int(ctx, 0);
let three = praxis_alloc_int(ctx, 3);
let first = praxis_text_get(ctx, text, zero);
let last = praxis_text_get(ctx, text, three);
assert!(!rt.has_pending_fault());
assert_eq!(first.as_ptr(), last.as_ptr());
assert_eq!(
first.as_ptr(),
praxis_alloc_char(ctx, i64::from('a' as u32)).as_ptr()
);
assert_eq!(
first.as_ptr(),
rt.immortals().small_char('a' as u32).unwrap().as_ptr()
);
assert_eq!(first.as_char(), 'a');
let u = "éé";
let utext = praxis_alloc_text(ctx, u.as_ptr(), u.len());
let one = praxis_alloc_int(ctx, 1);
let x = praxis_text_get(ctx, utext, zero);
let y = praxis_text_get(ctx, utext, one);
assert_ne!(x.as_ptr(), y.as_ptr(), "`é` is outside the interned range");
assert_eq!(x.as_char(), 'é');
assert_eq!(y.as_char(), 'é');
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn int_to_char_answers_the_same_object_as_alloc_char() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let code = praxis_alloc_int(ctx, i64::from('Z' as u32));
let via_to_char = praxis_int_to_char(ctx, code);
let via_alloc = praxis_alloc_char(ctx, i64::from('Z' as u32));
assert!(!rt.has_pending_fault());
assert_eq!(via_to_char.as_ptr(), via_alloc.as_ptr());
assert_eq!(via_to_char.as_char(), 'Z');
let big = praxis_alloc_int(ctx, i64::from('é' as u32));
assert_ne!(
praxis_int_to_char(ctx, big).as_ptr(),
praxis_alloc_char(ctx, i64::from('é' as u32)).as_ptr()
);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn int_to_text_renders_exactly_what_out_renders() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
for v in [0_i64, 1, -1, 1660, i64::MAX, i64::MIN, UNINTERNED] {
let receiver = praxis_alloc_int(ctx, v);
let answer = praxis_int_to_text(ctx, receiver);
assert!(!rt.has_pending_fault(), "{v} faulted");
let mut printed = String::new();
receiver.format(&mut printed);
assert_eq!(answer.as_text(), printed, "to_text and out disagree on {v}");
}
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn char_to_text_renders_exactly_what_out_renders() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
for c in ['#', 'a', 'é', '☃', '\u{10FFFF}'] {
let receiver = praxis_alloc_char(ctx, i64::from(u32::from(c)));
let answer = praxis_char_to_text(ctx, receiver);
assert!(!rt.has_pending_fault(), "{c} faulted");
let mut printed = String::new();
receiver.format(&mut printed);
assert_eq!(answer.as_text(), printed, "to_text and out disagree on {c}");
assert_eq!(answer.as_text(), c.to_string());
}
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn value_to_text_renders_exactly_what_out_renders() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let empty = praxis_alloc_text(ctx, std::ptr::null(), 0);
let hello = "hello";
let text = praxis_alloc_text(ctx, hello.as_ptr(), hello.len());
let vec = praxis_vec_new(ctx, &scalars::INT as *const _);
for n in [1_i64, 2, 3] {
let _ = praxis_vec_push(ctx, vec, praxis_alloc_int(ctx, n));
}
let receivers = [
praxis_alloc_int(ctx, UNINTERNED),
praxis_alloc_int(ctx, 0),
praxis_alloc_bool(ctx, 1),
praxis_alloc_char(ctx, i64::from(u32::from('☃'))),
empty,
text,
vec,
];
for receiver in receivers {
let answer = praxis_value_to_text(ctx, receiver);
assert!(!rt.has_pending_fault(), "value_to_text faulted");
let mut printed = String::new();
receiver.format(&mut printed);
assert_eq!(
answer.as_text(),
printed,
"a hole and `out` must write the same characters"
);
}
assert_eq!(praxis_value_to_text(ctx, text).as_text(), "hello");
assert_eq!(praxis_value_to_text(ctx, empty).as_text(), "");
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_join_puts_the_separator_between_and_nowhere_else() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let cases: [(&[&str], &str, &str); 5] = [
(&[], ", ", ""),
(&["only"], ", ", "only"),
(&["a", "b", "c"], ", ", "a, b, c"),
(&["a", "b", "c"], "", "abc"),
(&["é", "☃"], " — ", "é — ☃"),
];
for (items, sep, want) in cases {
let members: Vec<GcRef> = items.iter().map(|s| rt.alloc_text(s)).collect();
let vec = rt.alloc_vec(&crate::text::TEXT, members);
let separator = rt.alloc_text(sep);
let answer = praxis_vec_join(ctx, vec, separator);
assert!(!rt.has_pending_fault(), "{items:?} faulted");
assert_eq!(answer.as_text(), want);
}
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_join_refuses_a_non_text_element() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let mixed = rt.alloc_vec(&scalars::INT, vec![rt.alloc_text("a"), rt.alloc_int(1)]);
let sep = rt.alloc_text(",");
let answer = praxis_vec_join(ctx, mixed, sep);
assert!(rt.has_pending_fault());
assert!(std::ptr::eq(answer.descriptor(), &scalars::UNIT));
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_to_text_renders_every_char() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
for want in ["", ".", "..|", "héllo", "☃☃"] {
let members: Vec<GcRef> = want
.chars()
.map(|c| praxis_alloc_char(ctx, i64::from(u32::from(c))))
.collect();
let vec = rt.alloc_vec(&scalars::CHAR, members);
let answer = praxis_vec_to_text(ctx, vec);
assert!(!rt.has_pending_fault(), "{want:?} faulted");
assert_eq!(answer.as_text(), want);
}
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_to_text_refuses_a_non_char_element() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let mixed = rt.alloc_vec(&scalars::CHAR, vec![rt.alloc_int(65)]);
let answer = praxis_vec_to_text(ctx, mixed);
assert!(rt.has_pending_fault());
assert!(std::ptr::eq(answer.descriptor(), &scalars::UNIT));
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_reversed_answers_a_new_vec_and_leaves_the_receiver_alone() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let source = rt.alloc_vec(
&scalars::INT,
vec![rt.alloc_int(3), rt.alloc_int(1), rt.alloc_int(2)],
);
let answer = praxis_vec_reversed(ctx, source);
assert!(!rt.has_pending_fault());
let got: Vec<i64> = answer.as_vec().iter().map(|r| r.as_int()).collect();
assert_eq!(got, vec![2, 1, 3]);
let still: Vec<i64> = source.as_vec().iter().map(|r| r.as_int()).collect();
assert_eq!(still, vec![3, 1, 2], "the receiver is not touched");
assert_ne!(answer.as_ptr(), source.as_ptr());
let empty = rt.alloc_vec(&scalars::INT, vec![]);
assert!(praxis_vec_reversed(ctx, empty).as_vec().is_empty());
assert!(!rt.has_pending_fault());
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_reversed_needs_no_callback_where_sorted_needs_compare() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let closures = rt.alloc_vec(
&crate::closures::CLOSURE,
vec![
praxis_alloc_closure(ctx, std::ptr::null(), 0),
praxis_alloc_closure(ctx, std::ptr::null(), 0),
],
);
assert_eq!(praxis_vec_reversed(ctx, closures).as_vec().len(), 2);
assert!(!rt.has_pending_fault(), "reversal asks for no callback");
praxis_vec_sorted(ctx, closures);
assert!(rt.has_pending_fault(), "ordering still asks for `compare`");
}
unsafe { drop_ctx(ctx) };
}
unsafe fn groups_of_int(answer: GcRef) -> Vec<Vec<i64>> {
answer
.as_vec()
.iter()
.map(|inner| inner.as_vec().iter().map(|r| r.as_int()).collect())
.collect()
}
#[test]
fn vec_chunks_partitions_and_keeps_a_short_tail() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let ints: Vec<GcRef> = (1..=5).map(|n| rt.alloc_int(n)).collect();
let source = rt.alloc_vec(&scalars::INT, ints);
let two = rt.alloc_int(2);
let answer = praxis_vec_chunks(ctx, source, two);
assert!(!rt.has_pending_fault());
assert_eq!(groups_of_int(answer), vec![vec![1, 2], vec![3, 4], vec![5]]);
let five = rt.alloc_int(5);
assert_eq!(
groups_of_int(praxis_vec_chunks(ctx, source, five)),
vec![vec![1, 2, 3, 4, 5]],
);
let nine = rt.alloc_int(9);
assert_eq!(
groups_of_int(praxis_vec_chunks(ctx, source, nine)),
vec![vec![1, 2, 3, 4, 5]],
);
let still: Vec<i64> = source.as_vec().iter().map(|r| r.as_int()).collect();
assert_eq!(still, vec![1, 2, 3, 4, 5], "the receiver is not touched");
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_windows_slide_by_one_and_drop_a_run_that_does_not_fit() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let ints: Vec<GcRef> = (1..=4).map(|n| rt.alloc_int(n)).collect();
let source = rt.alloc_vec(&scalars::INT, ints);
let two = rt.alloc_int(2);
assert_eq!(
groups_of_int(praxis_vec_windows(ctx, source, two)),
vec![vec![1, 2], vec![2, 3], vec![3, 4]],
);
assert!(!rt.has_pending_fault());
let four = rt.alloc_int(4);
assert_eq!(
groups_of_int(praxis_vec_windows(ctx, source, four)),
vec![vec![1, 2, 3, 4]],
);
let five = rt.alloc_int(5);
let none = praxis_vec_windows(ctx, source, five);
assert!(
none.as_vec().is_empty(),
"a run of five does not fit in four"
);
assert!(
!rt.has_pending_fault(),
"not fitting is an answer, not a fault"
);
let answer = praxis_vec_windows(ctx, source, two);
let first = answer.as_vec()[0].as_vec()[1].as_ptr();
let second = answer.as_vec()[1].as_vec()[0].as_ptr();
assert_eq!(first, second, "the overlapping element is one object");
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_group_size_of_zero_or_less_is_an_invalid_size_fault() {
for size in [0i64, -1, i64::MIN] {
for (name, wrapper) in [
(
"chunks",
praxis_vec_chunks as unsafe extern "C" fn(_, _, _) -> _,
),
("windows", praxis_vec_windows),
] {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let source = rt.alloc_vec(&scalars::INT, vec![rt.alloc_int(1)]);
let n = rt.alloc_int(size);
let answer = wrapper(ctx, source, n);
assert!(rt.has_pending_fault(), "{name}({size}) must fault");
assert_eq!(rt.fault(), crate::FaultKind::InvalidSize, "{name}({size})");
assert!(
std::ptr::eq(answer.descriptor(), &scalars::UNIT),
"{name}({size}) answers the Unit sentinel"
);
}
unsafe { drop_ctx(ctx) };
}
}
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let empty = rt.alloc_vec(&scalars::INT, vec![]);
let two = rt.alloc_int(2);
assert!(praxis_vec_chunks(ctx, empty, two).as_vec().is_empty());
assert!(praxis_vec_windows(ctx, empty, two).as_vec().is_empty());
assert!(
!rt.has_pending_fault(),
"an empty receiver is an empty answer"
);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_grouping_labels_the_outer_vec_even_when_it_is_empty() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let source = rt.alloc_vec(&scalars::INT, vec![rt.alloc_int(1), rt.alloc_int(2)]);
let two = rt.alloc_int(2);
let five = rt.alloc_int(5);
for answer in [
praxis_vec_chunks(ctx, source, two),
praxis_vec_windows(ctx, source, two),
praxis_vec_windows(ctx, source, five),
praxis_vec_chunks(ctx, rt.alloc_vec(&scalars::INT, vec![]), two),
] {
let p = vec_payload(answer);
assert!(
std::ptr::eq(p.element_descriptor, &crate::collections::VEC),
"the outer Vec holds Vecs whether or not it holds any"
);
for inner in p.items.iter() {
assert!(std::ptr::eq(
vec_payload(*inner).element_descriptor,
&scalars::INT
));
}
}
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_grouping_needs_no_callback_where_sorted_needs_compare() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let closures = rt.alloc_vec(
&crate::closures::CLOSURE,
vec![
praxis_alloc_closure(ctx, std::ptr::null(), 0),
praxis_alloc_closure(ctx, std::ptr::null(), 0),
praxis_alloc_closure(ctx, std::ptr::null(), 0),
],
);
let two = rt.alloc_int(2);
assert_eq!(praxis_vec_chunks(ctx, closures, two).as_vec().len(), 2);
assert_eq!(praxis_vec_windows(ctx, closures, two).as_vec().len(), 2);
assert!(!rt.has_pending_fault(), "grouping asks for no callback");
praxis_vec_sorted(ctx, closures);
assert!(rt.has_pending_fault(), "ordering still asks for `compare`");
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn char_ref_paces_the_collector_even_when_it_answers_from_the_table() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let s = "abcdefgh";
let text = praxis_alloc_text(ctx, s.as_ptr(), s.len());
let index = praxis_alloc_int(ctx, 3);
let mut frame = push_frame(ctx, SlotCount::new(2).unwrap());
frame.set(0, text);
frame.set(1, index);
let mut before = rt.heap().stats().live_count;
let mut paced = false;
for i in 0..100_000_i64 {
let _ = rt.alloc_int(UNINTERNED + i);
let c = praxis_text_get(ctx, text, index);
assert_eq!(c.as_char(), 'd');
let after = rt.heap().stats().live_count;
if after < before.saturating_add(1) {
paced = true;
break;
}
before = after;
}
drop(frame);
assert!(
paced,
"praxis_text_get never gave the collector a turn on the interned path"
);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_grid_of_char_fills_with_the_interned_nul() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let grid = praxis_grid_new(ctx, &crate::scalars::CHAR, 3, 2);
assert!(!rt.has_pending_fault());
let nul = rt.immortals().small_char(0).expect("NUL is interned");
for y in 0..2 {
for x in 0..3 {
let xi = praxis_alloc_int(ctx, x);
let yi = praxis_alloc_int(ctx, y);
let cell = praxis_grid_get(ctx, grid, xi, yi);
assert_eq!(
cell.as_ptr(),
nul.as_ptr(),
"every cell of a fresh Grid[Char] is the one interned NUL"
);
}
}
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn interning_a_char_does_not_change_keyed_collection_behaviour() {
for (a_ch, b_ch, label) in [('a', 'b', "interned"), ('é', 'ü', "allocated")] {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_char(ctx, i64::from(a_ch as u32));
let a_again = praxis_alloc_char(ctx, i64::from(a_ch as u32));
let b = praxis_alloc_char(ctx, i64::from(b_ch as u32));
assert_eq!(
std::ptr::eq(a.as_ptr(), a_again.as_ptr()),
label == "interned",
"the fixture must actually be {label}"
);
let set = praxis_set_new(ctx, &crate::scalars::CHAR);
let _ = praxis_set_insert(ctx, set, a);
assert_eq!(
praxis_bool_load(ctx, praxis_set_contains(ctx, set, a_again)),
1,
"{label}: an equal Char is the same set member"
);
assert_eq!(
praxis_bool_load(ctx, praxis_set_contains(ctx, set, b)),
0,
"{label}: a different Char is not"
);
let _ = praxis_set_insert(ctx, set, a_again);
assert_eq!(praxis_int_load(ctx, praxis_set_len(ctx, set)), 1, "{label}");
let counter = praxis_counter_new(ctx, &crate::scalars::CHAR);
let _ = praxis_counter_inc(ctx, counter, a);
let _ = praxis_counter_inc(ctx, counter, a_again);
assert_eq!(
praxis_int_load(ctx, praxis_counter_get(ctx, counter, a)),
2,
"{label}: two bumps of an equal key are one key"
);
assert_eq!(
praxis_int_load(ctx, praxis_counter_len(ctx, counter)),
1,
"{label}"
);
}
unsafe { drop_ctx(ctx) };
}
}
#[test]
fn an_interned_char_survives_collection_unrooted() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let _ = praxis_alloc_char(ctx, i64::from('q' as u32));
}
assert_eq!(
rt.heap().stats().live_count,
0,
"an interned Char must not enter the live registry"
);
rt.collect_now();
unsafe {
let q = praxis_alloc_char(ctx, i64::from('q' as u32));
assert!(!q.header().is_poisoned(), "an immortal is never swept");
assert_eq!(q.as_char(), 'q');
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn an_interned_int_survives_collection_unrooted() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let _ = praxis_alloc_int(ctx, 5);
}
assert_eq!(
rt.heap().stats().live_count,
0,
"an interned Int must not enter the live registry"
);
rt.collect_now();
unsafe {
let five = praxis_alloc_int(ctx, 5);
assert!(!five.header().is_poisoned(), "an immortal is never swept");
assert_eq!(praxis_int_load(ctx, five), 5);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn interning_does_not_change_keyed_collection_behaviour() {
for (a_val, b_val, label) in [
(5_i64, 6_i64, "interned"),
(UNINTERNED, UNINTERNED + 1, "allocated"),
] {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, a_val);
let a_again = praxis_alloc_int(ctx, a_val);
let b = praxis_alloc_int(ctx, b_val);
let map = praxis_map_new(ctx, &scalars::INT);
let one = praxis_alloc_int(ctx, 1);
let _ = praxis_map_insert(ctx, map, a, one);
assert_eq!(
praxis_int_load(ctx, praxis_map_index(ctx, map, a_again)),
1,
"{label}: an equal key must find the entry"
);
assert_eq!(
rt.fault(),
FaultKind::None,
"{label}: an equal key is a present key"
);
assert_eq!(
praxis_bool_load(ctx, praxis_map_contains(ctx, map, b)),
0,
"{label}: a different key must not"
);
assert_eq!(praxis_int_load(ctx, praxis_map_len(ctx, map)), 1);
let set = praxis_set_new(ctx, &scalars::INT);
let _ = praxis_set_insert(ctx, set, a);
let _ = praxis_set_insert(ctx, set, a_again);
assert_eq!(
praxis_int_load(ctx, praxis_set_len(ctx, set)),
1,
"{label}: re-inserting an equal value must not grow the set"
);
assert_eq!(praxis_bool_load(ctx, praxis_set_contains(ctx, set, b)), 0);
let counter = praxis_counter_new(ctx, &scalars::INT);
let _ = praxis_counter_inc(ctx, counter, a);
let _ = praxis_counter_inc(ctx, counter, a_again);
assert_eq!(
praxis_int_load(ctx, praxis_counter_get(ctx, counter, a)),
2,
"{label}: two bumps of an equal key are two bumps of one key"
);
assert_eq!(praxis_int_load(ctx, praxis_counter_len(ctx, counter)), 1);
}
unsafe { drop_ctx(ctx) };
}
}
#[test]
fn bool_and_unit_abi_allocations_reuse_runtime_singletons() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (true_ref, false_ref, unit_ref) = unsafe {
(
praxis_alloc_bool(ctx, 1),
praxis_alloc_bool(ctx, 0),
praxis_alloc_unit(ctx),
)
};
let expected = (
rt.immortals().true_(),
rt.immortals().false_(),
rt.immortals().unit(),
);
unsafe { drop_ctx(ctx) };
assert_eq!(true_ref.as_ptr(), expected.0.as_ptr());
assert_eq!(false_ref.as_ptr(), expected.1.as_ptr());
assert_eq!(unit_ref.as_ptr(), expected.2.as_ptr());
}
#[test]
fn repeated_bool_allocation_mints_no_new_objects() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let mut seen = std::collections::HashSet::new();
unsafe {
for i in 0..100_i64 {
seen.insert(praxis_alloc_bool(ctx, i % 2).as_ptr());
seen.insert(praxis_alloc_unit(ctx).as_ptr());
}
}
unsafe { drop_ctx(ctx) };
assert_eq!(seen.len(), 3, "true, false and unit — and nothing else");
}
#[test]
fn predicate_wrappers_return_bool_singletons_and_allocate_nothing() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (immortal_true, immortal_false) = (rt.immortals().true_(), rt.immortals().false_());
unsafe {
let one = praxis_alloc_int(ctx, 1);
let two = praxis_alloc_int(ctx, 2);
let empty_vec = praxis_vec_new(ctx, &scalars::INT);
let empty_text = praxis_alloc_text(ctx, std::ptr::null(), 0);
let live_before = rt.heap().stats().live_count;
let answers = [
(praxis_int_eq(ctx, one, two), false),
(praxis_int_ne(ctx, one, two), true),
(praxis_int_lt(ctx, one, two), true),
(praxis_int_gt(ctx, one, two), false),
(praxis_int_le(ctx, one, one), true),
(praxis_int_ge(ctx, one, two), false),
(praxis_vec_is_empty(ctx, empty_vec), true),
(praxis_text_is_empty(ctx, empty_text), true),
];
assert_eq!(
rt.heap().stats().live_count,
live_before,
"a predicate wrapper must not allocate"
);
for (answer, expected) in answers {
let want = if expected {
immortal_true
} else {
immortal_false
};
assert_eq!(
answer.as_ptr(),
want.as_ptr(),
"predicate answered with a fresh Bool instead of the singleton"
);
}
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn every_scalar_boxing_wrapper_paces_the_collector() {
type Call = unsafe extern "C" fn(*mut RuntimeContext, GcRef) -> GcRef;
let cases: [(&str, Call); 7] = [
("praxis_text_len", praxis_text_len),
("praxis_vec_len", praxis_vec_len),
("praxis_grid_width", praxis_grid_width),
("praxis_grid_height", praxis_grid_height),
("praxis_float_to_text", praxis_float_to_text),
("praxis_int_to_text", praxis_int_to_text),
("praxis_char_to_text", praxis_char_to_text),
];
let big = UNINTERNED as usize;
for (name, call) in cases {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let text = "x".repeat(big);
let receiver = match name {
"praxis_text_len" => praxis_alloc_text(ctx, text.as_ptr(), big),
"praxis_vec_len" => {
rt.alloc_vec(&scalars::INT, vec![rt.alloc_int(0); big])
}
"praxis_float_to_text" => praxis_alloc_float(ctx, 1.5_f64.to_bits() as i64),
"praxis_int_to_text" => praxis_alloc_int(ctx, big as i64),
"praxis_char_to_text" => praxis_alloc_char(ctx, i64::from(u32::from('e'))),
"praxis_grid_width" => praxis_grid_new(ctx, &scalars::INT, big as i64, 1),
_ => praxis_grid_new(ctx, &scalars::INT, 1, big as i64),
};
let mut frame = push_frame(ctx, SlotCount::new(1).unwrap());
frame.set(0, receiver);
let mut before = rt.heap().stats().live_count;
let mut paced = false;
for _ in 0..10_000 {
let _ = call(ctx, receiver);
let after = rt.heap().stats().live_count;
if after < before.saturating_add(1) {
paced = true;
break;
}
before = after;
}
drop(frame);
assert!(paced, "{name} never gave the collector a turn");
}
unsafe { drop_ctx(ctx) };
}
}
#[test]
fn checked_add_returns_sum() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, 40);
let b = praxis_alloc_int(ctx, 2);
let s = praxis_int_add(ctx, a, b);
assert_eq!(praxis_int_load(ctx, s), 42);
assert!(!rt.has_pending_fault());
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn float_sign_of_zero_is_zero() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let signed = unsafe {
let zero = praxis_alloc_float(ctx, 0.0_f64.to_bits() as i64);
let result = praxis_float_sign(ctx, zero);
f64::from_bits(praxis_float_load(ctx, result) as u64)
};
unsafe { drop_ctx(ctx) };
assert_eq!(signed, 0.0);
}
#[test]
fn float_sign_of_negative_zero_is_zero() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let signed = unsafe {
let zero = praxis_alloc_float(ctx, (-0.0_f64).to_bits() as i64);
let result = praxis_float_sign(ctx, zero);
f64::from_bits(praxis_float_load(ctx, result) as u64)
};
unsafe { drop_ctx(ctx) };
assert_eq!(signed, 0.0);
}
#[test]
fn float_sign_of_nan_is_nan() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let signed = unsafe {
let nan = praxis_alloc_float(ctx, f64::NAN.to_bits() as i64);
let result = praxis_float_sign(ctx, nan);
f64::from_bits(praxis_float_load(ctx, result) as u64)
};
unsafe { drop_ctx(ctx) };
assert!(signed.is_nan());
}
#[test]
fn the_selecting_helpers_return_an_operand_and_allocate_nothing() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let lo = praxis_alloc_int(ctx, 3);
let hi = praxis_alloc_int(ctx, 7);
assert_eq!(praxis_int_min(ctx, lo, hi).as_ptr(), lo.as_ptr());
assert_eq!(praxis_int_min(ctx, hi, lo).as_ptr(), lo.as_ptr());
assert_eq!(praxis_int_max(ctx, lo, hi).as_ptr(), hi.as_ptr());
assert_eq!(praxis_int_max(ctx, hi, lo).as_ptr(), hi.as_ptr());
let three = praxis_alloc_int(ctx, 3);
assert_eq!(praxis_int_min(ctx, lo, three).as_ptr(), lo.as_ptr());
assert_eq!(praxis_int_max(ctx, lo, three).as_ptr(), lo.as_ptr());
let v = praxis_alloc_int(ctx, 5);
assert_eq!(praxis_int_clamp(ctx, v, lo, hi).as_ptr(), v.as_ptr());
let below = praxis_alloc_int(ctx, 1);
assert_eq!(praxis_int_clamp(ctx, below, lo, hi).as_ptr(), lo.as_ptr());
let above = praxis_alloc_int(ctx, 9);
assert_eq!(praxis_int_clamp(ctx, above, lo, hi).as_ptr(), hi.as_ptr());
assert!(!rt.has_pending_fault());
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn an_inverted_clamp_range_faults_rather_than_guessing() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let v = praxis_alloc_int(ctx, 5);
let lo = praxis_alloc_int(ctx, 10);
let hi = praxis_alloc_int(ctx, 0);
let r = praxis_int_clamp(ctx, v, lo, hi);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::EmptyRange);
assert_eq!(r.as_ptr(), rt.immortals().unit().as_ptr());
}
let _ = rt.take_fault();
unsafe {
let v = praxis_alloc_int(ctx, 5);
let same = praxis_alloc_int(ctx, 4);
let r = praxis_int_clamp(ctx, v, same, same);
assert!(!rt.has_pending_fault());
assert_eq!(r.as_ptr(), same.as_ptr());
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_range_whose_count_has_no_int_faults_rather_than_wrapping_negative() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let lo = praxis_alloc_int(ctx, i64::MIN);
let hi = praxis_alloc_int(ctx, i64::MAX);
let r = praxis_range_new(ctx, lo, hi);
let len = praxis_range_len(ctx, r);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
assert_eq!(len.as_ptr(), rt.immortals().unit().as_ptr());
}
let _ = rt.take_fault();
unsafe {
let lo = praxis_alloc_int(ctx, 0);
let hi = praxis_alloc_int(ctx, i64::MAX);
let r = praxis_range_new(ctx, lo, hi);
let len = praxis_range_len(ctx, r);
assert!(!rt.has_pending_fault());
assert_eq!(praxis_int_load(ctx, len), i64::MAX);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn gcd_and_lcm_are_non_negative_and_refuse_only_what_has_no_int() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let load = |r: GcRef| praxis_int_load(ctx, r);
let min = praxis_alloc_int(ctx, i64::MIN);
let two = praxis_alloc_int(ctx, 2);
assert_eq!(load(praxis_int_gcd(ctx, min, two)), 2);
assert!(!rt.has_pending_fault());
let min2 = praxis_alloc_int(ctx, i64::MIN);
let _ = praxis_int_gcd(ctx, min, min2);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
}
let _ = rt.take_fault();
unsafe {
let load = |r: GcRef| praxis_int_load(ctx, r);
let neg = praxis_alloc_int(ctx, -4);
let six = praxis_alloc_int(ctx, 6);
assert_eq!(load(praxis_int_lcm(ctx, neg, six)), 12);
let neg6 = praxis_alloc_int(ctx, -6);
assert_eq!(load(praxis_int_lcm(ctx, neg, neg6)), 12);
let zero = praxis_alloc_int(ctx, 0);
assert_eq!(load(praxis_int_lcm(ctx, six, zero)), 0);
assert_eq!(load(praxis_int_lcm(ctx, zero, zero)), 0);
assert_eq!(load(praxis_int_gcd(ctx, zero, zero)), 0);
assert!(!rt.has_pending_fault());
let big = praxis_alloc_int(ctx, i64::MAX);
let three = praxis_alloc_int(ctx, 3);
let _ = praxis_int_lcm(ctx, big, three);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn abs_faults_on_the_value_with_no_positive_and_sign_does_not() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let min = praxis_alloc_int(ctx, i64::MIN);
let r = praxis_int_abs(ctx, min);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
assert_eq!(r.as_ptr(), rt.immortals().unit().as_ptr());
}
let _ = rt.take_fault();
unsafe {
let min = praxis_alloc_int(ctx, i64::MIN);
assert_eq!(praxis_int_load(ctx, praxis_int_sign(ctx, min)), -1);
let max = praxis_alloc_int(ctx, i64::MAX);
assert_eq!(praxis_int_load(ctx, praxis_int_abs(ctx, max)), i64::MAX);
assert_eq!(praxis_int_load(ctx, praxis_int_sign(ctx, max)), 1);
assert!(!rt.has_pending_fault());
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn overflow_sets_fault_and_returns_sentinel() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, i64::MAX);
let b = praxis_alloc_int(ctx, 1);
let s = praxis_int_add(ctx, a, b);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
assert_eq!(s.as_ptr(), rt.immortals().unit().as_ptr());
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn division_by_zero_sets_fault() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, 10);
let b = praxis_alloc_int(ctx, 0);
let _ = praxis_int_div(ctx, a, b);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::DivByZero);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn remainder_by_zero_sets_fault() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, 10);
let b = praxis_alloc_int(ctx, 0);
let _ = praxis_int_rem(ctx, a, b);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::DivByZero);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn subtraction_overflow_sets_fault() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, i64::MIN);
let b = praxis_alloc_int(ctx, 1);
let _ = praxis_int_sub(ctx, a, b);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn multiplication_overflow_sets_fault() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, i64::MIN);
let b = praxis_alloc_int(ctx, -1);
let _ = praxis_int_mul(ctx, a, b);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn division_truncates_toward_zero() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, -7);
let b = praxis_alloc_int(ctx, 2);
let q = praxis_int_div(ctx, a, b);
assert!(!rt.has_pending_fault());
assert_eq!(praxis_int_load(ctx, q), -3);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn remainder_truncates_toward_zero() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, -7);
let b = praxis_alloc_int(ctx, 2);
let r = praxis_int_rem(ctx, a, b);
assert!(!rt.has_pending_fault());
assert_eq!(praxis_int_load(ctx, r), -1);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn division_min_div_minus_one_overflows() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, i64::MIN);
let b = praxis_alloc_int(ctx, -1);
let _ = praxis_int_div(ctx, a, b);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn remainder_min_div_minus_one_overflows() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let a = praxis_alloc_int(ctx, i64::MIN);
let b = praxis_alloc_int(ctx, -1);
let _ = praxis_int_rem(ctx, a, b);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn comparisons_yield_bools() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let one = praxis_alloc_int(ctx, 1);
let two = praxis_alloc_int(ctx, 2);
assert_eq!(praxis_bool_load(ctx, praxis_int_lt(ctx, one, two)), 1);
assert_eq!(praxis_bool_load(ctx, praxis_int_gt(ctx, one, two)), 0);
assert_eq!(praxis_bool_load(ctx, praxis_int_eq(ctx, one, one)), 1);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn neg_of_min_overflows() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let min = praxis_alloc_int(ctx, i64::MIN);
let _ = praxis_int_neg(ctx, min);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IntOverflow);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn check_fault_reports_pending() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
assert_eq!(praxis_check_fault(ctx), 0);
let a = praxis_alloc_int(ctx, 1);
let b = praxis_alloc_int(ctx, 0);
let _ = praxis_int_div(ctx, a, b);
assert_eq!(praxis_check_fault(ctx), 1);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn alloc_text_round_trips() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let s = "héllo";
unsafe {
let r = praxis_alloc_text(ctx, s.as_ptr(), s.len());
assert_eq!(r.as_text(), "héllo");
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn alloc_bool_round_trips_value() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let t = praxis_alloc_bool(ctx, 1);
let f = praxis_alloc_bool(ctx, 0);
assert_eq!(praxis_bool_load(ctx, t), 1);
assert_eq!(praxis_bool_load(ctx, f), 0);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn fault_clear_default_is_none() {
let f = Fault::clear();
assert!(!f.is_pending());
assert_eq!(f.kind(), FaultKind::None);
}
#[test]
fn vec_new_is_empty() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let v = praxis_vec_new(ctx, &crate::scalars::INT as *const _);
assert_eq!(praxis_bool_load(ctx, praxis_vec_is_empty(ctx, v)), 1);
assert_eq!(praxis_int_load(ctx, praxis_vec_len(ctx, v)), 0);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_push_grows_and_get_reads_back() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let v = praxis_vec_new(ctx, &crate::scalars::INT as *const _);
let a = praxis_alloc_int(ctx, 10);
let b = praxis_alloc_int(ctx, 20);
let c = praxis_alloc_int(ctx, 30);
let _ = praxis_vec_push(ctx, v, a);
let _ = praxis_vec_push(ctx, v, b);
let _ = praxis_vec_push(ctx, v, c);
assert_eq!(praxis_int_load(ctx, praxis_vec_len(ctx, v)), 3);
let i0 = praxis_alloc_int(ctx, 0);
let i2 = praxis_alloc_int(ctx, 2);
assert_eq!(praxis_int_load(ctx, praxis_vec_get(ctx, v, i0)), 10);
assert_eq!(praxis_int_load(ctx, praxis_vec_get(ctx, v, i2)), 30);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_get_out_of_bounds_faults() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let v = praxis_vec_new(ctx, &crate::scalars::INT as *const _);
let one = praxis_alloc_int(ctx, 1);
let _ = praxis_vec_get(ctx, v, one); assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IndexOutOfBounds);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_sequence_store_replaces_and_never_appends() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let v = praxis_vec_new(ctx, &crate::scalars::INT as *const _);
for n in [10, 20, 30] {
let _ = praxis_vec_push(ctx, v, praxis_alloc_int(ctx, n));
}
let d = praxis_deque_new(ctx, &crate::scalars::INT as *const _);
for n in [10, 20] {
let _ = praxis_deque_push_back(ctx, d, praxis_alloc_int(ctx, n));
}
let one = praxis_alloc_int(ctx, 1);
let ninety_nine = praxis_alloc_int(ctx, 99);
let _ = praxis_vec_set(ctx, v, one, ninety_nine);
let _ = praxis_deque_set(ctx, d, one, ninety_nine);
assert!(!rt.has_pending_fault());
assert_eq!(praxis_int_load(ctx, praxis_vec_len(ctx, v)), 3);
assert_eq!(praxis_int_load(ctx, praxis_deque_len(ctx, d)), 2);
let zero = praxis_alloc_int(ctx, 0);
let two = praxis_alloc_int(ctx, 2);
assert_eq!(praxis_int_load(ctx, praxis_vec_get(ctx, v, zero)), 10);
assert_eq!(praxis_int_load(ctx, praxis_vec_get(ctx, v, one)), 99);
assert_eq!(praxis_int_load(ctx, praxis_vec_get(ctx, v, two)), 30);
assert_eq!(praxis_int_load(ctx, praxis_deque_get(ctx, d, zero)), 10);
assert_eq!(praxis_int_load(ctx, praxis_deque_get(ctx, d, one)), 99);
let three = praxis_alloc_int(ctx, 3);
let neg = praxis_alloc_int(ctx, -1);
type Store = unsafe extern "C" fn(*mut RuntimeContext, GcRef, GcRef, GcRef) -> GcRef;
type Len = unsafe extern "C" fn(*mut RuntimeContext, GcRef) -> GcRef;
for (recv, idx, store, len_of) in [
(v, three, praxis_vec_set as Store, praxis_vec_len as Len),
(v, neg, praxis_vec_set as Store, praxis_vec_len as Len),
(d, two, praxis_deque_set as Store, praxis_deque_len as Len),
(d, neg, praxis_deque_set as Store, praxis_deque_len as Len),
] {
let before = praxis_int_load(ctx, len_of(ctx, recv));
let _ = store(ctx, recv, idx, ninety_nine);
assert!(rt.has_pending_fault(), "an out-of-range store must fault");
assert_eq!(rt.take_fault(), Some(FaultKind::IndexOutOfBounds));
assert_eq!(
praxis_int_load(ctx, len_of(ctx, recv)),
before,
"a faulting store must not have grown the collection"
);
}
let float = praxis_alloc_float(ctx, 1.5_f64.to_bits() as i64);
let _ = praxis_vec_set(ctx, v, zero, float);
assert_eq!(rt.take_fault(), Some(FaultKind::TypeMismatch));
assert_eq!(praxis_int_load(ctx, praxis_vec_get(ctx, v, zero)), 10);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_push_many_survive_collection() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let v = praxis_vec_new(ctx, &crate::scalars::INT as *const _);
let mut frame = push_frame(ctx, SlotCount::new(2).unwrap());
frame.set(0, v);
let mut observed_reclamation = false;
for i in 0..5000_i64 {
let before_alloc = rt.heap().stats().live_count;
let elem = praxis_alloc_int(ctx, UNINTERNED + i);
if rt.heap().stats().live_count < before_alloc.saturating_add(1) {
observed_reclamation = true;
}
frame.set(1, elem);
let before_push = rt.heap().stats().live_count;
let _ = praxis_vec_push(ctx, v, elem);
if rt.heap().stats().live_count < before_push {
observed_reclamation = true;
}
frame.clear(1);
let _ = rt.alloc_int(-UNINTERNED - i - 1);
}
assert!(
observed_reclamation,
"the test must observe an automatic collection, not merely allocation pressure"
);
assert_eq!(praxis_int_load(ctx, praxis_vec_len(ctx, v)), 5000);
let zero = praxis_alloc_int(ctx, 0);
assert_eq!(
praxis_int_load(ctx, praxis_vec_get(ctx, v, zero)),
UNINTERNED
);
let middle = praxis_alloc_int(ctx, 2500);
assert_eq!(
praxis_int_load(ctx, praxis_vec_get(ctx, v, middle)),
UNINTERNED + 2500
);
let last = praxis_alloc_int(ctx, 4999);
assert_eq!(
praxis_int_load(ctx, praxis_vec_get(ctx, v, last)),
UNINTERNED + 4999
);
drop(frame);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_get_negative_index_faults() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let v = praxis_vec_new(ctx, &crate::scalars::INT as *const _);
let a = praxis_alloc_int(ctx, 1);
let _ = praxis_vec_push(ctx, v, a); let neg = praxis_alloc_int(ctx, -1);
let _ = praxis_vec_get(ctx, v, neg);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IndexOutOfBounds);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn text_get_negative_index_faults() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let s = "ab";
let text = praxis_alloc_text(ctx, s.as_ptr(), s.len());
let neg = praxis_alloc_int(ctx, -1);
let _ = praxis_text_get(ctx, text, neg);
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::IndexOutOfBounds);
}
let _ = rt.take_fault();
unsafe { drop_ctx(ctx) };
}
#[test]
fn text_get_answers_a_char_object() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let s = "sddddd";
let text = praxis_alloc_text(ctx, s.as_ptr(), s.len());
let four = praxis_alloc_int(ctx, 4);
let got = praxis_text_get(ctx, text, four);
assert!(!rt.has_pending_fault());
assert!(
std::ptr::eq(got.descriptor(), &crate::scalars::CHAR),
"ADR-086: the read answers a Char, not the char's scalar value"
);
assert_eq!(got.as_char(), 'd');
let u = "héllo";
let utext = praxis_alloc_text(ctx, u.as_ptr(), u.len());
let one = praxis_alloc_int(ctx, 1);
let got = praxis_text_get(ctx, utext, one);
assert!(!rt.has_pending_fault());
assert_eq!(got.as_char(), 'é');
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_text_reads_by_scalar_wherever_the_multi_byte_scalar_sits() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
for src in [
"",
"abc",
"\u{0}\u{7f}",
"éabc",
"abéc",
"abcé",
"a\u{1F600}b",
"\u{20AC}\u{20AC}",
"héllo wörld",
] {
let text = praxis_alloc_text(ctx, src.as_ptr(), src.len());
let expected: Vec<char> = src.chars().collect();
let len = praxis_text_len(ctx, text);
assert!(!rt.has_pending_fault(), "{src:?}");
assert_eq!(len.as_int(), expected.len() as i64, "{src:?}");
let empty = praxis_text_is_empty(ctx, text);
assert_eq!(empty.as_bool(), expected.is_empty(), "{src:?}");
for (i, want) in expected.iter().enumerate() {
let idx = praxis_alloc_int(ctx, i as i64);
let got = praxis_text_get(ctx, text, idx);
assert!(!rt.has_pending_fault(), "{src:?}[{i}]");
assert!(
std::ptr::eq(got.descriptor(), &crate::scalars::CHAR),
"{src:?}[{i}] answers a Char (ADR-086)"
);
assert_eq!(got.as_char(), *want, "{src:?}[{i}]");
}
let past = praxis_alloc_int(ctx, expected.len() as i64);
let _ = praxis_text_get(ctx, text, past);
assert!(rt.has_pending_fault(), "{src:?}[{}]", expected.len());
assert_eq!(rt.fault(), FaultKind::IndexOutOfBounds);
let _ = rt.take_fault();
}
let owner_src = "héllo wörld";
let owner = praxis_alloc_text(ctx, owner_src.as_ptr(), owner_src.len());
let view = rt
.alloc_text_slice(owner, 3, 4)
.expect("[3, 7) is on scalar boundaries");
let len = praxis_text_len(ctx, view);
assert_eq!(len.as_int(), 4);
for (i, want) in "llo ".chars().enumerate() {
let idx = praxis_alloc_int(ctx, i as i64);
let got = praxis_text_get(ctx, view, idx);
assert!(!rt.has_pending_fault());
assert_eq!(got.as_char(), want);
}
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn int_to_char_rejects_what_is_not_a_scalar_value() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
for bad in [-1_i64, 0xD800, 0x11_0000, 0x1_0000_0041] {
let n = praxis_alloc_int(ctx, bad);
let got = praxis_int_to_char(ctx, n);
assert!(rt.has_pending_fault(), "{bad} must not answer a Char");
assert_eq!(rt.fault(), FaultKind::InvalidChar, "{bad}");
assert!(std::ptr::eq(got.descriptor(), &crate::scalars::UNIT));
let _ = rt.take_fault();
}
let n = praxis_alloc_int(ctx, 233);
let got = praxis_int_to_char(ctx, n);
assert!(!rt.has_pending_fault());
assert_eq!(got.as_char(), 'é');
let back = praxis_char_to_int(ctx, got);
assert!(!rt.has_pending_fault());
assert_eq!(back.as_int(), 233);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn alloc_text_empty_string_round_trips() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let r = praxis_alloc_text(ctx, std::ptr::null(), 0);
assert_eq!(r.as_text(), "");
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_new_with_null_descriptor_defaults_to_int() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let v = praxis_vec_new(ctx, std::ptr::null());
assert_eq!(praxis_bool_load(ctx, praxis_vec_is_empty(ctx, v)), 1);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn vec_push_rejects_a_value_with_the_wrong_descriptor() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let length_after;
unsafe {
let ints = praxis_vec_new(ctx, &crate::scalars::INT as *const _);
let float = praxis_alloc_float(ctx, 1.5_f64.to_bits() as i64);
let _ = praxis_vec_push(ctx, ints, float);
length_after = ints.as_vec().len();
}
unsafe { drop_ctx(ctx) };
assert_eq!(
length_after, 0,
"an ABI type mismatch must not silently retag and mutate an explicitly typed Vec[Int]"
);
}
#[test]
fn alloc_char_rejects_values_that_only_become_valid_after_truncation() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let result = unsafe { praxis_alloc_char(ctx, 0x1_0000_0041) };
let unit = rt.immortals().unit();
unsafe { drop_ctx(ctx) };
assert_eq!(
result.as_ptr(),
unit.as_ptr(),
"the ABI must range-check the i64 code point before converting it to u32"
);
assert_eq!(rt.fault(), FaultKind::InvalidChar);
assert!(rt.has_pending_fault());
}
#[test]
fn alloc_char_rejects_a_negative_code_point() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let result = unsafe { praxis_alloc_char(ctx, -1) };
let unit = rt.immortals().unit();
unsafe { drop_ctx(ctx) };
assert_eq!(result.as_ptr(), unit.as_ptr());
assert_eq!(rt.fault(), FaultKind::InvalidChar);
}
#[test]
fn input_that_is_not_utf8_faults_at_the_read() {
fn not_utf8() -> Vec<u8> {
vec![0xF0, 0x28, 0x8C, 0x28]
}
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
crate::input::install_input_reader(not_utf8);
let result = unsafe { praxis_get_input(ctx) };
let unit = rt.immortals().unit();
crate::input::clear_input_reader();
unsafe { drop_ctx(ctx) };
assert_eq!(rt.fault(), FaultKind::InvalidText);
assert!(rt.has_pending_fault());
assert_eq!(
result.as_ptr(),
unit.as_ptr(),
"the fault path answers §10.4's defined dummy, not a half-built Text"
);
}
#[test]
fn input_that_is_utf8_still_becomes_the_buffer() {
fn multibyte() -> Vec<u8> {
"héllo wörld".as_bytes().to_vec()
}
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
crate::input::install_input_reader(multibyte);
let result = unsafe { praxis_get_input(ctx) };
let contents = result.as_text().to_string();
let descriptor = result.descriptor().name;
crate::input::clear_input_reader();
unsafe { drop_ctx(ctx) };
assert!(!rt.has_pending_fault(), "fault: {:?}", rt.fault());
assert_eq!(descriptor, "Text");
assert_eq!(contents, "héllo wörld");
}
#[test]
fn grid_cell_vectors_preserve_the_grid_element_descriptor() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let cell = rt.alloc_text("x");
let grid = rt.alloc_grid(&crate::text::TEXT, vec![cell], 1);
let descriptors;
unsafe {
let zero = praxis_alloc_int(ctx, 0);
let cells = praxis_grid_cells(ctx, grid);
let row = praxis_grid_row(ctx, grid, zero);
let column = praxis_grid_column(ctx, grid, zero);
descriptors = [
(*vec_payload(cells).element_descriptor).id(),
(*vec_payload(row).element_descriptor).id(),
(*vec_payload(column).element_descriptor).id(),
];
}
unsafe { drop_ctx(ctx) };
assert!(
descriptors.iter().all(|id| *id == crate::text::TEXT.id()),
"cells(), row(), and column() must return Vec values tagged with the Grid cell type"
);
}
#[test]
fn constructed_grid_cells_satisfy_the_declared_element_descriptor() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let cell_descriptor;
unsafe {
let grid = praxis_grid_new(ctx, &crate::scalars::INT as *const _, 1, 1);
cell_descriptor = grid_payload(grid).items[0].descriptor().id();
}
unsafe { drop_ctx(ctx) };
assert_eq!(
cell_descriptor,
crate::scalars::INT.id(),
"a live Grid[Int] must never contain a Unit placeholder observable through get/format/hash"
);
}
#[test]
fn grid_position_vectors_use_the_point_tuple_descriptor() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let cell = rt.alloc_int(1);
let grid = rt.alloc_grid(&crate::scalars::INT, vec![cell], 1);
let descriptors;
unsafe {
let point = alloc_point(ctx, 0, 0);
let positions = praxis_grid_positions(ctx, grid);
let neighbors4 = praxis_grid_neighbors4(ctx, grid, point);
let neighbors8 = praxis_grid_neighbors8(ctx, grid, point);
let matches = praxis_grid_find_all(ctx, grid, cell);
descriptors = [
(*vec_payload(positions).element_descriptor).id(),
(*vec_payload(neighbors4).element_descriptor).id(),
(*vec_payload(neighbors8).element_descriptor).id(),
(*vec_payload(matches).element_descriptor).id(),
];
}
unsafe { drop_ctx(ctx) };
assert!(
descriptors
.iter()
.all(|id| *id == crate::tuples::TUPLE.id()),
"position-producing Grid methods must return Vec[Tuple[Int, Int]] at runtime"
);
}
#[test]
fn a_negative_or_absurd_grid_extent_faults_instead_of_allocating() {
let absurd = GridExtent::MAX_CELLS as i64 + 1;
for (width, height) in [
(-1_i64, 4_i64),
(4, -1),
(-1, -1),
(i64::MIN, 1),
(i64::MAX, 2),
(1 << 40, 1 << 40),
(absurd, 1),
(1, absurd),
] {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let live_before = rt.heap().stats().live_count;
let result =
unsafe { praxis_grid_new(ctx, &crate::scalars::INT as *const _, width, height) };
let live_after = rt.heap().stats().live_count;
let unit = rt.immortals().unit();
unsafe { drop_ctx(ctx) };
assert_eq!(
rt.fault(),
FaultKind::InvalidSize,
"Grid[Int]({width}, {height}) must fault"
);
assert_eq!(
result.as_ptr(),
unit.as_ptr(),
"a faulted Grid[Int]({width}, {height}) returns the Unit sentinel"
);
assert_eq!(
live_after, live_before,
"a rejected Grid[Int]({width}, {height}) allocates nothing"
);
}
}
#[test]
fn an_in_range_grid_extent_still_builds_its_cells() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let shapes: Vec<(i64, i64, usize, usize)> = vec![(0, 0, 0, 0), (0, 5, 0, 0), (3, 2, 6, 3)];
let mut observed = Vec::new();
for (width, height, _, _) in &shapes {
let grid =
unsafe { praxis_grid_new(ctx, &crate::scalars::INT as *const _, *width, *height) };
let p = unsafe { grid_payload(grid) };
observed.push((p.items.len(), p.width));
}
unsafe { drop_ctx(ctx) };
assert_eq!(rt.fault(), FaultKind::None, "no in-range extent faults");
for ((w, h, cells, width), (got_cells, got_width)) in shapes.iter().zip(observed) {
assert_eq!(
(got_cells, got_width),
(*cells, *width),
"Grid[Int]({w}, {h}) shape"
);
}
}
#[test]
fn vec_filled_builds_n_copies_of_one_value() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let observed: Vec<(usize, bool)> = [0_i64, 1, 7]
.into_iter()
.map(|n| unsafe {
let count = praxis_alloc_int(ctx, n);
let fill = praxis_alloc_int(ctx, 42);
let v = praxis_vec_filled(ctx, &crate::scalars::INT as *const _, count, fill);
let p = vec_payload(v);
let all_same = p.items.iter().all(|item| item.as_ptr() == fill.as_ptr());
(p.items.len(), all_same)
})
.collect();
unsafe { drop_ctx(ctx) };
assert_eq!(rt.fault(), FaultKind::None, "no in-range count faults");
assert_eq!(observed, vec![(0, true), (1, true), (7, true)]);
}
#[test]
fn vec_filled_refuses_a_negative_or_absurd_count() {
let absurd = crate::collections::VecExtent::MAX_ITEMS as i64 + 1;
for n in [-1_i64, i64::MIN, absurd, i64::MAX] {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (result, live_before, live_after, unit) = unsafe {
let count = praxis_alloc_int(ctx, n);
let fill = praxis_alloc_int(ctx, 0);
let before = rt.heap().stats().live_count;
let r = praxis_vec_filled(ctx, &crate::scalars::INT as *const _, count, fill);
(
r,
before,
rt.heap().stats().live_count,
rt.immortals().unit(),
)
};
unsafe { drop_ctx(ctx) };
assert_eq!(rt.fault(), FaultKind::InvalidSize, "Vec({n}, 0) must fault");
assert_eq!(
result.as_ptr(),
unit.as_ptr(),
"a faulted Vec({n}, 0) returns the Unit sentinel"
);
assert_eq!(
live_after, live_before,
"a rejected Vec({n}, 0) allocates nothing"
);
}
}
#[test]
fn vec_filled_reconciles_its_element_descriptor() {
let mut rejecting = Runtime::new();
let ctx = wired_ctx(&mut rejecting);
unsafe {
let count = praxis_alloc_int(ctx, 3);
let text = praxis_alloc_text(ctx, b"x".as_ptr(), 1);
praxis_vec_filled(ctx, &crate::scalars::INT as *const _, count, text);
drop_ctx(ctx);
}
assert_eq!(
rejecting.fault(),
FaultKind::TypeMismatch,
"a `Vec[Int]` filled with a `Text` is a mislabelled element descriptor"
);
let mut adopting = Runtime::new();
let ctx = wired_ctx(&mut adopting);
let adopted = unsafe {
let count = praxis_alloc_int(ctx, 3);
let text = praxis_alloc_text(ctx, b"x".as_ptr(), 1);
let v = praxis_vec_filled(ctx, std::ptr::null(), count, text);
let matches = std::ptr::eq(vec_payload(v).element_descriptor, text.descriptor());
drop_ctx(ctx);
matches
};
assert_eq!(adopting.fault(), FaultKind::None);
assert!(
adopted,
"a null static descriptor adopts the fill's, as `praxis_vec_new` already does"
);
}
#[test]
fn grid_filled_accepts_a_composite_fill_where_grid_new_cannot() {
let mut inventing = Runtime::new();
let ctx = wired_ctx(&mut inventing);
unsafe {
praxis_grid_new(ctx, &crate::collections::VEC as *const _, 2, 2);
drop_ctx(ctx);
}
assert_eq!(
inventing.fault(),
FaultKind::TypeMismatch,
"`praxis_grid_new` still has no zero value for a `Vec` cell"
);
let mut supplied = Runtime::new();
let ctx = wired_ctx(&mut supplied);
let (cells, all_same) = unsafe {
let inner = praxis_vec_new(ctx, &crate::scalars::INT as *const _);
let (w, h) = (praxis_alloc_int(ctx, 2), praxis_alloc_int(ctx, 2));
let g = praxis_grid_filled(ctx, &crate::collections::VEC as *const _, w, h, inner);
let p = grid_payload(g);
let same = p.items.iter().all(|c| c.as_ptr() == inner.as_ptr());
let len = p.items.len();
drop_ctx(ctx);
(len, same)
};
assert_eq!(supplied.fault(), FaultKind::None);
assert_eq!(cells, 4, "an explicit fill builds all four cells");
assert!(
all_same,
"the four cells are one `Vec`, not four (ADR-146 decision 4)"
);
}
#[test]
fn grid_filled_refuses_the_extents_grid_new_refuses() {
let absurd = GridExtent::MAX_CELLS as i64 + 1;
for (width, height) in [(-1_i64, 4_i64), (4, -1), (i64::MAX, 2), (absurd, 1)] {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (result, live_before, live_after, unit) = unsafe {
let (w, h) = (praxis_alloc_int(ctx, width), praxis_alloc_int(ctx, height));
let fill = praxis_alloc_int(ctx, 0);
let before = rt.heap().stats().live_count;
let r = praxis_grid_filled(ctx, &crate::scalars::INT as *const _, w, h, fill);
(
r,
before,
rt.heap().stats().live_count,
rt.immortals().unit(),
)
};
unsafe { drop_ctx(ctx) };
assert_eq!(
rt.fault(),
FaultKind::InvalidSize,
"Grid({width}, {height}, 0) must fault"
);
assert_eq!(
result.as_ptr(),
unit.as_ptr(),
"a faulted Grid({width}, {height}, 0) returns the Unit sentinel"
);
assert_eq!(
live_after, live_before,
"a rejected Grid({width}, {height}, 0) allocates nothing"
);
}
}
#[test]
fn a_bitset_member_outside_the_representable_range_faults() {
for member in [-1_i64, i64::MIN, i64::MAX, BitIndex::MAX + 1] {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let words;
unsafe {
let bs = praxis_bitset_new(ctx);
let value = praxis_alloc_int(ctx, member);
let _ = praxis_bitset_insert(ctx, bs, value);
words = bitset_payload(bs).words.len();
}
unsafe { drop_ctx(ctx) };
assert_eq!(
rt.fault(),
FaultKind::InvalidSize,
"BitSet.insert({member}) must fault"
);
assert_eq!(words, 0, "BitSet.insert({member}) must allocate no words");
}
}
#[cfg(not(feature = "std-vec-payload"))]
#[test]
fn the_inline_bitset_site_addresses_a_live_bitsets_words() {
use crate::bitset::INLINE_BITSET_SITE;
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (words, len) = unsafe {
let bs = praxis_bitset_new(ctx);
assert!(
std::ptr::eq(INLINE_BITSET_SITE.type_id().descriptor(), bs.descriptor()),
"the site names the descriptor the inline proof compares against"
);
for member in [0_i64, 63, 64, 200] {
let value = praxis_alloc_int(ctx, member);
let _ = praxis_bitset_insert(ctx, bs, value);
}
let base = bs.as_ptr().cast::<u8>().cast_const();
(
base.add(INLINE_BITSET_SITE.elements_offset())
.cast::<*const u64>()
.read(),
base.add(INLINE_BITSET_SITE.len_offset())
.cast::<usize>()
.read(),
)
};
assert_eq!(len, 4, "bit 200 lives in the fourth word");
assert_eq!(
INLINE_BITSET_SITE.element_shift(),
3,
"a word is eight bytes"
);
for member in [0_u64, 63, 64, 200] {
let w = unsafe { *words.add((member >> 6) as usize) };
assert!(
(w >> (member & 63)) & 1 == 1,
"bit {member} read back through the site's displacements"
);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn bitset_queries_outside_the_range_are_absent_rather_than_faults() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (present, words) = unsafe {
let bs = praxis_bitset_new(ctx);
let huge = praxis_alloc_int(ctx, i64::MAX);
let _ = praxis_bitset_remove(ctx, bs, huge);
let answer = praxis_bitset_contains(ctx, bs, huge);
(answer != 0, bitset_payload(bs).words.len())
};
unsafe { drop_ctx(ctx) };
assert!(!present, "an unrepresentable member is absent");
assert_eq!(words, 0, "a query allocates no words");
assert_eq!(rt.fault(), FaultKind::None, "a query does not fault");
}
#[test]
fn neighbors_of_an_extreme_point_are_empty_rather_than_a_panic() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let cell = rt.alloc_int(1);
let grid = rt.alloc_grid(&crate::scalars::INT, vec![cell], 1);
let counts = unsafe {
let mut counts = Vec::new();
for (x, y) in [
(i64::MAX, i64::MAX),
(i64::MIN, i64::MIN),
(i64::MAX, 0),
(0, i64::MIN),
] {
let point = alloc_point(ctx, x, y);
counts.push((
vec_payload(praxis_grid_neighbors4(ctx, grid, point))
.items
.len(),
vec_payload(praxis_grid_neighbors8(ctx, grid, point))
.items
.len(),
));
}
counts
};
unsafe { drop_ctx(ctx) };
assert!(
counts.iter().all(|(n4, n8)| *n4 == 0 && *n8 == 0),
"an out-of-range point has no in-grid neighbours: {counts:?}"
);
assert_eq!(rt.fault(), FaultKind::None);
}
fn nine_grid(rt: &mut Runtime) -> GcRef {
let cells: Vec<GcRef> = (1..=9).map(|n| rt.alloc_int(n)).collect();
rt.alloc_grid(&crate::scalars::INT, cells, 3)
}
unsafe fn around_fields(record: GcRef) -> Vec<(&'static str, Option<(i64, i64)>)> {
unsafe {
let rp = &*(record.payload::<u8>() as *const crate::records::RecordPayload);
let schema = &*rp.schema;
schema
.fields
.iter()
.zip(&rp.items)
.map(|(field, value)| {
let ep = &*(value.payload::<u8>() as *const crate::enums::EnumPayload);
let point = (i64::from(ep.tag) == crate::enums::OPTION_SOME_TAG)
.then(|| point_xy(ep.items[0]));
(field.name, point)
})
.collect()
}
}
#[test]
fn around4_answers_every_direction_and_the_missing_ones_are_none() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let grid = nine_grid(&mut rt);
let (middle, corner, far_corner) = unsafe {
let m = around_fields(praxis_grid_around4(ctx, grid, alloc_point(ctx, 1, 1)));
let c = around_fields(praxis_grid_around4(ctx, grid, alloc_point(ctx, 0, 0)));
let f = around_fields(praxis_grid_around4(ctx, grid, alloc_point(ctx, 2, 2)));
(m, c, f)
};
unsafe { drop_ctx(ctx) };
assert_eq!(
middle,
vec![
("up", Some((1, 0))),
("left", Some((0, 1))),
("right", Some((2, 1))),
("down", Some((1, 2))),
],
"the plus in reading order, centre skipped"
);
assert_eq!(
corner,
vec![
("up", None),
("left", None),
("right", Some((1, 0))),
("down", Some((0, 1))),
]
);
assert_eq!(
far_corner,
vec![
("up", Some((2, 1))),
("left", Some((1, 2))),
("right", None),
("down", None),
]
);
assert_eq!(rt.fault(), FaultKind::None);
}
#[test]
fn around8_is_a_3x3_block_in_reading_order() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let grid = nine_grid(&mut rt);
let (middle, edge) = unsafe {
let m = around_fields(praxis_grid_around8(ctx, grid, alloc_point(ctx, 1, 1)));
let e = around_fields(praxis_grid_around8(ctx, grid, alloc_point(ctx, 1, 0)));
(m, e)
};
unsafe { drop_ctx(ctx) };
assert_eq!(
middle,
vec![
("up_left", Some((0, 0))),
("up", Some((1, 0))),
("up_right", Some((2, 0))),
("left", Some((0, 1))),
("right", Some((2, 1))),
("down_left", Some((0, 2))),
("down", Some((1, 2))),
("down_right", Some((2, 2))),
]
);
assert_eq!(
edge,
vec![
("up_left", None),
("up", None),
("up_right", None),
("left", Some((0, 0))),
("right", Some((2, 0))),
("down_left", Some((0, 1))),
("down", Some((1, 1))),
("down_right", Some((2, 1))),
]
);
assert_eq!(rt.fault(), FaultKind::None);
}
#[test]
fn a_neighbourhood_of_an_extreme_point_is_all_absent_rather_than_a_panic() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let grid = nine_grid(&mut rt);
let one = rt.alloc_int(1);
let observed = unsafe {
let mut observed = Vec::new();
for (x, y) in [
(i64::MAX, i64::MAX),
(i64::MIN, i64::MIN),
(i64::MAX, 0),
(0, i64::MIN),
] {
let point = alloc_point(ctx, x, y);
let four = around_fields(praxis_grid_around4(ctx, grid, point));
let eight = around_fields(praxis_grid_around8(ctx, grid, point));
observed.push((
four.iter().filter(|(_, p)| p.is_some()).count(),
eight.len(),
eight.iter().filter(|(_, p)| p.is_some()).count(),
int_payload(praxis_grid_count4(ctx, grid, point, one)),
int_payload(praxis_grid_count8(ctx, grid, point, one)),
));
}
observed
};
unsafe { drop_ctx(ctx) };
for row in &observed {
assert_eq!(
*row,
(0, 8, 0, 0, 0),
"an out-of-range point has no in-grid neighbours, and its \
record still has all eight fields: {observed:?}"
);
}
assert_eq!(rt.fault(), FaultKind::None);
}
#[test]
fn a_neighbourhood_count_counts_only_the_cells_that_are_there() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let grid = nine_grid(&mut rt);
let counts = unsafe {
let centre = alloc_point(ctx, 1, 1);
let corner = alloc_point(ctx, 0, 0);
let two = praxis_alloc_int(ctx, 2);
let five = praxis_alloc_int(ctx, 5);
let nine = praxis_alloc_int(ctx, 9);
[
int_payload(praxis_grid_count4(ctx, grid, centre, two)),
int_payload(praxis_grid_count4(ctx, grid, centre, nine)),
int_payload(praxis_grid_count8(ctx, grid, centre, nine)),
int_payload(praxis_grid_count8(ctx, grid, centre, five)),
int_payload(praxis_grid_count4(ctx, grid, corner, five)),
int_payload(praxis_grid_count8(ctx, grid, corner, five)),
]
};
unsafe { drop_ctx(ctx) };
assert_eq!(counts, [1, 0, 1, 0, 0, 1]);
assert_eq!(rt.fault(), FaultKind::None);
}
#[test]
fn a_map_index_faults_where_get_answers_and_a_counter_set_replaces() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (present, absent_get) = unsafe {
let map = praxis_map_new(ctx, &crate::scalars::INT as *const _);
let key = praxis_alloc_int(ctx, 1);
let val = praxis_alloc_int(ctx, 42);
praxis_map_insert(ctx, map, key, val);
let present = int_payload(praxis_map_index(ctx, map, key));
assert_eq!(rt.fault(), FaultKind::None, "a present key does not fault");
let other = praxis_alloc_int(ctx, 2);
let absent_get = praxis_map_get(ctx, map, other);
assert_eq!(rt.fault(), FaultKind::None, "`.get` does not fault");
praxis_map_index(ctx, map, other);
(present, absent_get)
};
assert_eq!(present, 42);
assert_eq!(
absent_get.descriptor().id(),
crate::enums::ENUM.id(),
"`.get` answers with absence, and absence is an `Option` value"
);
assert_eq!(
rt.fault(),
FaultKind::IndexOutOfBounds,
"§4.7: indexing a missing key faults"
);
unsafe { drop_ctx(ctx) };
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (after_inc, after_set, len) = unsafe {
let c = praxis_counter_new(ctx, &crate::scalars::INT as *const _);
let key = praxis_alloc_int(ctx, 7);
praxis_counter_inc(ctx, c, key);
let after_inc = int_payload(praxis_counter_get(ctx, c, key));
let five = praxis_alloc_int(ctx, 5);
praxis_counter_set(ctx, c, key, five);
let after_set = int_payload(praxis_counter_get(ctx, c, key));
let len = int_payload(praxis_counter_len(ctx, c));
(after_inc, after_set, len)
};
unsafe { drop_ctx(ctx) };
assert_eq!(after_inc, 1);
assert_eq!(after_set, 5, "a set replaces rather than adds");
assert_eq!(len, 1, "and does not add a second entry for the same key");
assert_eq!(rt.fault(), FaultKind::None);
}
#[test]
fn absent_map_get_does_not_return_an_untyped_unit_sentinel() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (missing, found);
unsafe {
let map = praxis_map_new(ctx, &crate::scalars::INT as *const _);
let key = praxis_alloc_int(ctx, 1);
missing = praxis_map_get(ctx, map, key);
let value = praxis_alloc_int(ctx, 42);
praxis_map_insert(ctx, map, key, value);
found = praxis_map_get(ctx, map, key);
}
assert_ne!(
missing.descriptor().id(),
crate::scalars::UNIT.id(),
"Map.get is statically value-typed; absence needs Option or a checked fault, not Unit"
);
assert_eq!(missing.descriptor().id(), crate::enums::ENUM.id());
assert_eq!(
enum_tag_of(missing),
crate::enums::OPTION_NONE_TAG as u32,
"absence is `None`"
);
assert_eq!(enum_tag_of(found), crate::enums::OPTION_SOME_TAG as u32);
let payload = unsafe { praxis_enum_payload(ctx, found, 0) };
assert_eq!(unsafe { praxis_int_load(ctx, payload) }, 42);
unsafe { drop_ctx(ctx) };
}
unsafe extern "C" fn always_false(
ctx: *mut RuntimeContext,
_closure: GcRef,
_state: GcRef,
) -> GcRef {
unsafe { bool_ref(ctx, false) }
}
unsafe extern "C" fn no_neighbours(
ctx: *mut RuntimeContext,
_closure: GcRef,
_state: GcRef,
) -> GcRef {
unsafe { praxis_vec_new(ctx, &crate::scalars::INT as *const _) }
}
#[repr(C)]
struct DirtyPaddedBool {
header: crate::gc::GcHeader,
payload: [u8; 8],
}
fn dirty_padded_false() -> GcRef {
thread_local! {
static CELL: std::cell::Cell<*mut crate::gc::GcHeader> =
const { std::cell::Cell::new(std::ptr::null_mut()) };
}
CELL.with(|cell| {
if cell.get().is_null() {
let object = Box::leak(Box::new(DirtyPaddedBool {
header: crate::gc::GcHeader::new(
&scalars::BOOL,
crate::gc::GcHeader::payload_offset_for(scalars::BOOL.align()) as u16,
crate::gc::HeapId::mint(),
),
payload: [0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF],
}));
cell.set(&mut object.header as *mut crate::gc::GcHeader);
}
unsafe { GcRef::from_raw(cell.get()) }
})
}
unsafe extern "C" fn always_dirty_false(
_ctx: *mut RuntimeContext,
_closure: GcRef,
_state: GcRef,
) -> GcRef {
dirty_padded_false()
}
#[test]
fn a_graph_goal_predicate_reads_a_bool_at_a_bool_s_width() {
let fixture = dirty_padded_false();
assert!(std::ptr::eq(fixture.descriptor(), &scalars::BOOL));
assert_eq!(
unsafe { read_scalar(fixture, scalars::BOOL_PAYLOAD) },
Some(0u8),
"the fixture is `false` at a Bool's width"
);
assert_ne!(
unsafe { *fixture.payload::<i64>() },
0,
"…and non-zero at an Int's, which is what the wrong read consumed"
);
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let answer = unsafe {
let goal = praxis_alloc_closure(ctx, always_dirty_false as *const u8, 0);
let neighbours = praxis_alloc_closure(ctx, no_neighbours as *const u8, 0);
let start = praxis_alloc_int(ctx, 0);
praxis_bfs_distance(ctx, start, neighbours, goal)
};
assert!(!rt.has_pending_fault(), "fault: {:?}", rt.fault());
assert_eq!(answer.descriptor().id(), crate::enums::ENUM.id());
assert_eq!(
enum_tag_of(answer),
crate::enums::OPTION_NONE_TAG as u32,
"the goal answered `false` at every state, so no distance was found"
);
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_graph_goal_predicate_that_is_false_everywhere_finds_nothing() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let answer = unsafe {
let goal = praxis_alloc_closure(ctx, always_false as *const u8, 0);
let neighbours = praxis_alloc_closure(ctx, no_neighbours as *const u8, 0);
let start = praxis_alloc_int(ctx, 0);
praxis_bfs_distance(ctx, start, neighbours, goal)
};
assert!(!rt.has_pending_fault(), "fault: {:?}", rt.fault());
assert_eq!(answer.descriptor().id(), crate::enums::ENUM.id());
assert_eq!(enum_tag_of(answer), crate::enums::OPTION_NONE_TAG as u32);
unsafe { drop_ctx(ctx) };
}
#[test]
fn read_scalar_answers_none_for_a_foreign_descriptor() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let t = bool_ref(ctx, true);
let f = bool_ref(ctx, false);
assert_eq!(read_scalar(t, crate::scalars::BOOL_PAYLOAD), Some(1u8));
assert_eq!(read_scalar(f, crate::scalars::BOOL_PAYLOAD), Some(0u8));
let n = praxis_alloc_int(ctx, 1);
assert_eq!(read_scalar(n, crate::scalars::BOOL_PAYLOAD), None);
assert_eq!(read_scalar(n, crate::scalars::INT_PAYLOAD), Some(1i64));
drop_ctx(ctx);
}
}
fn enum_tag_of(value: GcRef) -> u32 {
unsafe { (*(value.payload::<u8>() as *const crate::enums::EnumPayload)).tag }
}
#[test]
fn absent_grid_find_does_not_return_an_untyped_unit_sentinel() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let (missing, found);
unsafe {
let cell = praxis_alloc_int(ctx, 1);
let sought = praxis_alloc_int(ctx, 2);
let grid = rt.alloc_grid(&crate::scalars::INT, vec![cell], 1);
missing = praxis_grid_find(ctx, grid, sought);
let present = praxis_alloc_int(ctx, 1);
found = praxis_grid_find(ctx, grid, present);
}
assert_ne!(
missing.descriptor().id(),
crate::scalars::UNIT.id(),
"Grid.find is statically point-typed; absence needs Option or a checked fault, not Unit"
);
assert_eq!(missing.descriptor().id(), crate::enums::ENUM.id());
assert_eq!(enum_tag_of(missing), crate::enums::OPTION_NONE_TAG as u32);
assert_eq!(enum_tag_of(found), crate::enums::OPTION_SOME_TAG as u32);
let point = unsafe { praxis_enum_payload(ctx, found, 0) };
assert_eq!(point.descriptor().id(), crate::tuples::TUPLE.id());
unsafe { drop_ctx(ctx) };
}
#[test]
fn maybe_collect_skips_below_threshold() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let _ = praxis_alloc_int(ctx, 1);
let roots = crate::roots::RuntimeRoots::from_context(ctx);
let ran = rt.heap().maybe_collect(&roots);
assert!(
!ran,
"a single small Int must not trip the 64 KiB threshold"
);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn maybe_collect_runs_under_pressure() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let _ = allocate_until_automatic_collection(&rt, ctx);
let roots = crate::roots::RuntimeRoots::from_context(ctx);
assert!(
!rt.heap().maybe_collect(&roots),
"counter must reset after a collection"
);
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn checked_int_add_is_an_automatic_gc_safepoint() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let collected;
unsafe {
let lhs = praxis_alloc_int(ctx, UNINTERNED);
let rhs = praxis_alloc_int(ctx, 22);
let mut frame = push_frame(ctx, SlotCount::new(2).unwrap());
frame.set(0, lhs);
frame.set(1, rhs);
let mut before = rt.heap().stats().live_count;
let mut observed = false;
for _ in 0..10_000 {
let _ = praxis_int_add(ctx, lhs, rhs);
let after = rt.heap().stats().live_count;
if after < before.saturating_add(1) {
observed = true;
break;
}
before = after;
}
collected = observed;
drop(frame);
}
unsafe { drop_ctx(ctx) };
assert!(
collected,
"every allocating ABI wrapper must participate in automatic GC pacing"
);
}
#[test]
fn automatic_gc_roots_the_ambient_input_buffer() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let live_after_collection;
unsafe {
(*ctx).input_source = rt.alloc_text("input that main has not read yet");
let frame = push_frame(ctx, SlotCount::new(0).unwrap());
live_after_collection = allocate_until_automatic_collection(&rt, ctx);
drop(frame);
}
unsafe { drop_ctx(ctx) };
assert!(
live_after_collection >= 2,
"the ambient input Text and the allocation returned after collection must both remain live"
);
}
#[test]
fn automatic_gc_roots_parse_failure_partial_values() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let partial = rt.alloc_int(UNINTERNED);
rt.parse_detail_mut()
.consider(ParseFail::here(0, "test").with_partial(Some(partial)), b"");
let live_after_collection;
unsafe {
let frame = push_frame(ctx, SlotCount::new(0).unwrap());
live_after_collection = allocate_until_automatic_collection(&rt, ctx);
drop(frame);
}
unsafe { drop_ctx(ctx) };
assert!(
live_after_collection >= 2,
"ParseDetail.partial is runtime-owned and must be included in every automatic root set"
);
}
#[test]
fn automatic_gc_roots_runtime_owned_crash_snapshots() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let captured = rt.alloc_int(UNINTERNED);
let local_name = b"value";
let meta = crate::debug::DebugLocalMeta {
callee_name: std::ptr::null(),
callee_name_len: 0,
source_name: local_name.as_ptr(),
name_len: local_name.len() as u32,
symbol_id: 1,
descriptor: &crate::scalars::INT as *const _,
type_id: 0,
kind: crate::debug::LOCAL_KIND_USER,
span_start: 0,
span_end: 0,
slot_kind: crate::debug::DebugSlotKind::Reference,
};
let metas = [meta];
let func_name = b"main";
let func_meta = crate::debug::FunctionDebugMeta {
func_name: func_name.as_ptr(),
func_name_len: func_name.len() as u32,
local_count: 1,
locals: metas.as_ptr(),
span_start: 0,
span_end: 0,
};
let live_after_collection;
unsafe {
let mut debug_frame = crate::debug::push_frame(ctx, &func_meta);
debug_frame.set(0, captured);
crate::crash_snapshot::praxis_snapshot_debug_chain(ctx);
drop(debug_frame);
assert!(rt.crash_snapshot().is_some());
let shadow_frame = push_frame(ctx, SlotCount::new(0).unwrap());
live_after_collection = allocate_until_automatic_collection(&rt, ctx);
drop(shadow_frame);
}
unsafe { drop_ctx(ctx) };
assert!(
live_after_collection >= 2,
"a runtime-owned CrashSnapshot must root its copied local values during automatic GC"
);
}
#[test]
fn nested_allocating_helpers_root_intermediate_results() {
const W: usize = 40;
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
let mut coords: Vec<(i64, i64)> = Vec::new();
let collections_inside_the_helper;
unsafe {
let cells: Vec<GcRef> = (0..(W * W) as i64).map(|i| rt.alloc_int(i)).collect();
let grid = rt.alloc_grid(&scalars::INT, cells, W);
let mut frame = push_frame(ctx, SlotCount::new(1).unwrap());
frame.set(0, grid);
let before = rt.heap().stats().live_count;
let positions = praxis_grid_positions(ctx, grid);
collections_inside_the_helper = rt.heap().stats().live_count > before;
let items = &(*positions.payload::<VecPayload>()).items;
assert_eq!(items.len(), W * W, "one position per cell");
for point in items {
let tuple = &*point.payload::<crate::tuples::TuplePayload>();
coords.push((int_payload(tuple.items[0]), int_payload(tuple.items[1])));
}
drop(frame);
}
unsafe { drop_ctx(ctx) };
assert!(collections_inside_the_helper);
let expected: Vec<(i64, i64)> = (0..W * W)
.map(|i| ((i % W) as i64, (i / W) as i64))
.collect();
assert_eq!(
coords, expected,
"every point and coordinate the helper allocated must survive the \
collections the helper itself triggers"
);
}
#[test]
fn check_fault_on_null_context_is_zero() {
assert_eq!(unsafe { praxis_check_fault(std::ptr::null_mut()) }, 0);
}
#[test]
fn a_collection_with_no_static_element_type_does_not_claim_int() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let null = std::ptr::null::<TypeDescriptor>();
let counter = praxis_counter_new(ctx, null);
let set = praxis_set_new(ctx, null);
let map = praxis_map_new(ctx, null);
let min_heap = praxis_min_heap_new(ctx, null);
let max_heap = praxis_max_heap_new(ctx, null);
assert!(
counter_payload(counter).key().is_none(),
"a Counter with no static key type must not claim one"
);
assert!(set_payload(set).element().is_none());
assert!(map_payload(map).key().is_none());
assert!(min_heap_payload(min_heap).element().is_none());
assert!(max_heap_payload(max_heap).element().is_none());
let key = praxis_alloc_text(ctx, "ab".as_ptr(), 2);
praxis_counter_inc(ctx, counter, key);
let keys = praxis_counter_keys(ctx, counter);
let mut rendered = String::new();
keys.format(&mut rendered);
assert_eq!(rendered, "[ab]", "a Counter's keys are its keys");
let half = praxis_alloc_float(ctx, 1.5f64.to_bits() as i64);
praxis_min_heap_push(ctx, min_heap, half);
let mut rendered = String::new();
min_heap.format(&mut rendered);
assert_eq!(rendered, "[1.5]", "a MinHeap prints the elements it holds");
let member = praxis_alloc_text(ctx, "zz".as_ptr(), 2);
praxis_set_insert(ctx, set, member);
let items = praxis_set_items(ctx, set);
let mut rendered = String::new();
items.format(&mut rendered);
assert_eq!(rendered, "[zz]");
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_map_does_not_claim_its_values_are_ints() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let empty = praxis_map_new(ctx, &crate::text::TEXT);
assert!(
map_payload(empty).value().is_none(),
"an empty Map has been told nothing about its values"
);
let none_yet = praxis_map_values(ctx, empty);
assert!(vec_payload(none_yet).element().is_none());
let k = praxis_alloc_text(ctx, "k".as_ptr(), 1);
let v = praxis_alloc_text(ctx, "vv".as_ptr(), 2);
praxis_map_insert(ctx, empty, k, v);
assert!(
std::ptr::eq(map_payload(empty).value().unwrap(), &crate::text::TEXT),
"a Map learns its value type from the first value inserted"
);
let mut rendered = String::new();
praxis_map_values(ctx, empty).format(&mut rendered);
assert_eq!(rendered, "[vv]");
let ints = praxis_map_new(ctx, &crate::text::TEXT);
let ik = praxis_alloc_text(ctx, "n".as_ptr(), 1);
praxis_map_insert(ctx, ints, ik, praxis_alloc_int(ctx, 7));
assert!(std::ptr::eq(
map_payload(ints).value().unwrap(),
&scalars::INT
));
}
unsafe { drop_ctx(ctx) };
}
#[test]
fn an_unlearned_element_label_does_not_make_two_empty_collections_unequal() {
use crate::collections::same_element;
let int: *const crate::descriptor::TypeDescriptor = &scalars::INT;
let text: *const crate::descriptor::TypeDescriptor = &crate::text::TEXT;
let unlearned: *const crate::descriptor::TypeDescriptor = std::ptr::null();
assert!(same_element(unlearned, int), "no label agrees with `Int`");
assert!(same_element(int, unlearned), "and in the other order");
assert!(same_element(unlearned, unlearned));
assert!(same_element(int, int));
assert!(!same_element(int, text));
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let never_inserted = praxis_map_new(ctx, &crate::text::TEXT);
let unlabelled = praxis_map_values(ctx, never_inserted);
assert!(vec_payload(unlabelled).element().is_none());
let labelled_ints = praxis_vec_new(ctx, &scalars::INT as *const _);
assert!(
praxis_struct_eq(ctx, unlabelled, labelled_ints) != 0,
"an empty Map's values are an empty Vec[Int]"
);
assert!(
praxis_struct_eq(ctx, labelled_ints, unlabelled) != 0,
"and equality is symmetric"
);
praxis_vec_push(ctx, labelled_ints, praxis_alloc_int(ctx, 1));
assert!(praxis_struct_eq(ctx, unlabelled, labelled_ints) == 0);
}
unsafe { drop_ctx(ctx) };
}
fn functions_in_this_file() -> Vec<(String, String)> {
functions_in(include_str!("abi.rs"))
}
fn code_only(line: &str) -> &str {
let bytes = line.as_bytes();
let (mut in_str, mut in_char, mut escaped) = (false, false, false);
let mut i = 0;
while i < bytes.len() {
let c = bytes[i];
if escaped {
escaped = false;
} else if c == b'\\' && (in_str || in_char) {
escaped = true;
} else if in_str {
in_str = c != b'"';
} else if in_char {
in_char = c != b'\'';
} else if c == b'"' {
in_str = true;
} else if c == b'\'' {
in_char = bytes[i + 1..].iter().take(4).any(|b| *b == b'\'');
} else if c == b'/' && bytes.get(i + 1) == Some(&b'/') {
return &line[..i];
}
i += 1;
}
line
}
fn functions_in(src: &str) -> Vec<(String, String)> {
const PREFIXES: [&str; 6] = [
"fn ",
"pub fn ",
"pub(crate) fn ",
"unsafe fn ",
"pub unsafe fn ",
"pub unsafe extern \"C\" fn ",
];
let mut out: Vec<(String, String)> = Vec::new();
let mut open: Option<(String, String, i32, bool)> = None;
for raw in src.lines() {
let line = code_only(raw);
let depth_change = |l: &str| -> i32 {
l.chars().filter(|c| *c == '{').count() as i32
- l.chars().filter(|c| *c == '}').count() as i32
};
if let Some((name, body, depth, opened)) = open.as_mut() {
body.push_str(line);
body.push('\n');
*depth += depth_change(line);
*opened |= line.contains('{');
if *opened && *depth <= 0 {
out.push((std::mem::take(name), std::mem::take(body)));
open = None;
}
continue;
}
let trimmed = line.trim_start();
let Some(rest) = PREFIXES
.iter()
.find_map(|p| trimmed.strip_prefix(p).filter(|_| trimmed.starts_with(p)))
else {
continue;
};
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if name.is_empty() {
continue;
}
let depth = depth_change(line);
let opened = line.contains('{');
if opened && depth <= 0 {
out.push((name, line.to_string()));
} else {
open = Some((name, format!("{line}\n"), depth, opened));
}
}
out
}
fn faulting_functions(defs: &[(String, String)]) -> std::collections::HashSet<String> {
let mut faulting: std::collections::HashSet<String> =
["set_fault".to_string()].into_iter().collect();
loop {
let mut grew = false;
for (name, body) in defs {
if faulting.contains(name) {
continue;
}
if faulting.iter().any(|f| body.contains(&format!("{f}("))) {
faulting.insert(name.clone());
grew = true;
}
}
if !grew {
break;
}
}
faulting
}
#[test]
fn a_wrapper_that_can_raise_a_fault_declares_that_it_faults() {
let defs = functions_in_this_file();
let faulting = faulting_functions(&defs);
let mut checked = 0usize;
for (name, _) in &defs {
let Some(sym) = praxis_stdlib::abi::RuntimeSymbol::from_name(name) else {
continue;
};
if !faulting.contains(name) {
continue;
}
assert!(
sym.faults(),
"{name} can reach `set_fault`, but its manifest row says it \
cannot fault — so no `CheckFault` follows the call and the \
fault is observed somewhere else entirely"
);
checked += 1;
}
assert!(
checked >= 20,
"expected the fault-raising wrappers to be found; saw {checked}"
);
for name in [
"praxis_vec_push",
"praxis_deque_push_front",
"praxis_deque_push_back",
] {
assert!(
faulting.contains(name),
"{name} reaches `set_fault` through `adopt_or_reject`; a scan \
that cannot see that cannot hold the invariant"
);
}
assert!(
faulting.contains("praxis_get_input"),
"`praxis_get_input` validates the host's input and raises \
`InvalidText` itself (ADR-111); a scan that cannot see that cannot \
tell a relocated fault from a deleted one"
);
assert!(
!faulting.contains("praxis_alloc_text"),
"`praxis_alloc_text` reaches `set_fault` again. Its row is \
`Effect::Allocates`, so nothing observes the fault — a violated \
UTF-8 precondition aborts through `abi_guard!` instead (ADR-111)"
);
}
#[test]
fn the_manifest_sweep_reads_code_and_not_comments() {
let src = r#"
pub unsafe extern "C" fn praxis_pretend_pure(ctx: *mut RuntimeContext) -> GcRef {
// It used to call set_fault(ctx, RaisedFault::TYPE_MISMATCH) here, and a
// later edit removed the only path that could. Prose, not code. }
let sep = "//";
let slash = '/';
let _ = (sep, slash);
unit_sentinel(ctx)
}
pub unsafe extern "C" fn praxis_pretend_faulting(ctx: *mut RuntimeContext) -> GcRef {
set_fault(ctx, RaisedFault::TYPE_MISMATCH);
unit_sentinel(ctx)
}
"#;
let defs = functions_in(src);
let names: Vec<&str> = defs.iter().map(|(n, _)| n.as_str()).collect();
assert_eq!(names, ["praxis_pretend_pure", "praxis_pretend_faulting"]);
assert!(
defs[0].1.contains("unit_sentinel(ctx)"),
"a brace inside a comment ended the body early: {:?}",
defs[0].1
);
let faulting = faulting_functions(&defs);
assert!(
!faulting.contains("praxis_pretend_pure"),
"a comment naming `set_fault` is not a call to it"
);
assert!(
faulting.contains("praxis_pretend_faulting"),
"and a real call still is — stripping comments must not blind the sweep"
);
}
#[test]
fn code_only_keeps_a_slash_inside_a_literal() {
assert_eq!(code_only("let x = 1; // two"), "let x = 1; ");
assert_eq!(code_only(r#"let s = "a//b";"#), r#"let s = "a//b";"#);
assert_eq!(code_only(r"let c = '/'; // gone"), r"let c = '/'; ");
assert_eq!(
code_only(r#"let e = "\"//"; // gone"#),
r#"let e = "\"//"; "#
);
assert_eq!(code_only(" /// a doc comment"), " ");
assert_eq!(code_only("no comment here"), "no comment here");
assert_eq!(
code_only("fn f<'a>(x: &'a str) {} // gone"),
"fn f<'a>(x: &'a str) {} "
);
}
#[test]
fn every_no_mangle_wrapper_is_behind_the_panic_guard() {
fn rust_sources(dir: &std::path::Path, out: &mut Vec<(String, String)>) {
let entries =
std::fs::read_dir(dir).unwrap_or_else(|e| panic!("read {}: {e}", dir.display()));
let mut entries: Vec<_> = entries.map(|e| e.expect("dir entry").path()).collect();
entries.sort();
for path in entries {
let name = path.file_name().unwrap_or_default().to_string_lossy();
if name == "target" || name.starts_with('.') {
continue;
}
if path.is_dir() {
rust_sources(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
let text = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
out.push((path.display().to_string(), text));
}
}
}
let mut crates_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
crates_dir.pop();
let mut sources: Vec<(String, String)> = Vec::new();
rust_sources(&crates_dir, &mut sources);
assert!(
sources.len() > 50,
"the walk of {} found only {} Rust files, so it is not reading the workspace",
crates_dir.display(),
sources.len()
);
let mut wrappers = 0usize;
let mut unguarded: Vec<String> = Vec::new();
for (file, source) in &sources {
let lines: Vec<&str> = source.lines().collect();
for (n, line) in lines.iter().enumerate() {
if !matches!(line.trim(), "#[unsafe(no_mangle)]" | "#[no_mangle]") {
continue;
}
wrappers += 1;
let mut k = n + 1;
while k < lines.len() && !lines[k].trim_end().ends_with('{') {
k += 1;
}
let name = lines[n..=k.min(lines.len() - 1)]
.iter()
.find_map(|l| l.split("fn ").nth(1))
.and_then(|l| l.split('(').next())
.unwrap_or("<unnamed>")
.trim()
.to_string();
let opens_guard = lines
.get(k + 1)
.map(|l| l.trim_start().starts_with("abi_guard!("))
.unwrap_or(false);
if !opens_guard {
unguarded.push(format!("{file}:{} {name}", n + 1));
}
}
}
assert!(
wrappers > 100,
"the scan found only {wrappers} wrappers, so it is not reading the ABI surface"
);
assert!(
unguarded.is_empty(),
"these `extern \"C\"` entry points can let a panic unwind into generated frames: {unguarded:#?}"
);
}
#[test]
fn a_panic_inside_a_wrapper_becomes_a_fault_and_a_defined_dummy() {
let value = {
abi_guard!(
"praxis_test_panics",
std::ptr::null_mut::<RuntimeContext>(),
{
#[allow(unreachable_code)]
{
if std::hint::black_box(false) {
panic!("this is the guard under test");
}
7i64
}
}
)
};
assert_eq!(value, 7, "the guard is transparent when nothing panics");
let mut runtime = crate::Runtime::new();
let mut ctx = runtime.context();
let previous = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let dummy: GcRef = abi_guard!("praxis_run_parser", &mut ctx as *mut RuntimeContext, {
panic!("a wrapper that forgot to be total");
});
std::panic::set_hook(previous);
assert_eq!(
runtime.fault(),
crate::FaultKind::Panic,
"an escaped panic is a fault, not an unwind into generated code"
);
assert!(
runtime
.fault_message()
.is_some_and(|m| m.contains("praxis_run_parser")),
"the fault names the wrapper it escaped, which a bare kind could not"
);
assert_eq!(
dummy.descriptor().id(),
crate::scalars::UNIT.id(),
"the dummy is the Unit sentinel §10.4 already specifies"
);
}
#[test]
fn a_panic_dummy_is_only_returned_where_a_fault_check_can_follow() {
use praxis_stdlib::abi::RuntimeSymbol;
let mut pure = 0usize;
let mut faulting = 0usize;
for symbol in RuntimeSymbol::ALL.iter().copied() {
let observable = panic_fault_is_observable(symbol.name());
assert_eq!(
observable,
symbol.faults(),
"`{}` is declared {:?}; the panic dummy must be returned iff a \
fault check can follow it",
symbol.name(),
symbol.sig().effect
);
if symbol.faults() {
faulting += 1;
} else {
pure += 1;
}
}
assert!(
pure > 0 && faulting > 0,
"the manifest must contain both classes for this rule to mean anything \
({pure} non-faulting, {faulting} faulting)"
);
assert!(
!panic_fault_is_observable("praxis_not_a_wrapper_at_all"),
"an unknown name is never treated as observable"
);
}
fn no_bytes() -> Vec<u8> {
Vec::new()
}
unsafe fn text_bytes_of(r: GcRef) -> &'static [u8] {
unsafe { crate::text::text_bytes(r.payload::<crate::text::TextPayload>() as *const _) }
}
#[test]
fn a_reader_that_answers_zero_bytes_installs_an_empty_text() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
crate::input::install_input_reader(no_bytes);
let source = unsafe { praxis_get_input(ctx) };
assert_eq!(
source.descriptor().id(),
crate::text::TEXT.id(),
"a zero-byte answer is still an input buffer"
);
assert!(
unsafe { text_bytes_of(source) }.is_empty(),
"and the buffer holds exactly what the reader answered"
);
assert_eq!(
unsafe { (*ctx).input_source }.as_ptr(),
source.as_ptr(),
"the buffer is installed, not merely returned — §7.10's later \
`read`s reuse it"
);
unsafe { drop_ctx(ctx) };
}
#[test]
fn a_host_that_installs_no_reader_keeps_the_unit_source() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
crate::input::clear_input_reader();
let before = unsafe { (*ctx).input_source };
let source = unsafe { praxis_get_input(ctx) };
assert_eq!(
source.as_ptr(),
before.as_ptr(),
"with no reader installed there is nothing to call and nothing to \
install; `input_source` is answered untouched"
);
assert_ne!(
source.descriptor().id(),
crate::text::TEXT.id(),
"and it is still the Unit the §6.3 guard is the net under"
);
unsafe { drop_ctx(ctx) };
}
#[test]
fn the_non_text_guard_does_not_report_a_previous_parses_failure() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
rt.parse_detail_mut()
.consider(ParseFail::here(7, "int"), b"0123456789");
assert!(rt.parse_detail().is_set(), "the seed is in place");
unsafe {
let plan = praxis_alloc_int(ctx, 1);
let unit = (*ctx).unit_ref;
let result = praxis_run_parser(ctx, plan, unit);
assert_eq!(
result.descriptor().id(),
crate::scalars::UNIT.id(),
"the guard answers the sentinel"
);
}
assert!(rt.has_pending_fault());
assert_eq!(rt.fault(), FaultKind::ParseFailed);
assert!(
!rt.parse_detail().is_set(),
"the §6.3 guard runs no parse, so it has no detail to report — and \
it must not report the previous parse's"
);
unsafe { drop_ctx(ctx) };
}
}
#[cfg(test)]
mod growth_charging_tests {
use super::tests::{drop_ctx, wired_ctx};
use super::*;
use crate::Runtime;
fn charged_by(rt: &Runtime, body: impl FnOnce()) -> usize {
let before = rt.heap().bytes_since_collect();
body();
rt.heap().bytes_since_collect().saturating_sub(before)
}
const PUSHES: i64 = 256;
macro_rules! charges_its_spine {
($name:ident, $make:expr_2021, $push:expr_2021) => {
#[test]
fn $name() {
let mut rt = Runtime::new();
let ctx = wired_ctx(&mut rt);
unsafe {
let subject = $make(ctx);
let charged = charged_by(&rt, || {
for i in 0..PUSHES {
$push(ctx, subject, i);
}
});
assert!(
charged > 0,
"growing this collection charged the pacer nothing, so a \
program whose memory is this buffer would never collect \
(ADR-121); every value pushed is an interned immortal, so \
the spine is the only thing that could have charged"
);
drop_ctx(ctx);
}
}
};
}
charges_its_spine!(
vec_push_charges_its_spine,
|c| praxis_vec_new(c, &crate::scalars::INT),
|c, s, i| { praxis_vec_push(c, s, praxis_alloc_int(c, i)) }
);
charges_its_spine!(
deque_push_back_charges_its_spine,
|c| praxis_deque_new(c, &crate::scalars::INT),
|c, s, i| praxis_deque_push_back(c, s, praxis_alloc_int(c, i))
);
charges_its_spine!(
deque_push_front_charges_its_spine,
|c| praxis_deque_new(c, &crate::scalars::INT),
|c, s, i| praxis_deque_push_front(c, s, praxis_alloc_int(c, i))
);
charges_its_spine!(
map_insert_charges_its_spine,
|c| praxis_map_new(c, &crate::scalars::INT),
|c, s, i| praxis_map_insert(c, s, praxis_alloc_int(c, i), praxis_alloc_int(c, i))
);
charges_its_spine!(
set_insert_charges_its_spine,
|c| praxis_set_new(c, &crate::scalars::INT),
|c, s, i| praxis_set_insert(c, s, praxis_alloc_int(c, i))
);
charges_its_spine!(
counter_set_charges_its_spine,
|c| praxis_counter_new(c, &crate::scalars::INT),
|c, s, i| praxis_counter_set(c, s, praxis_alloc_int(c, i), praxis_alloc_int(c, i))
);
charges_its_spine!(
bitset_insert_charges_its_spine,
|c| praxis_bitset_new(c),
|c, s, i| praxis_bitset_insert(c, s, praxis_alloc_int(c, i))
);
charges_its_spine!(
max_heap_push_charges_its_spine,
|c| praxis_max_heap_new(c, &crate::scalars::INT),
|c, s, i| praxis_max_heap_push(c, s, praxis_alloc_int(c, i))
);
charges_its_spine!(
min_heap_push_charges_its_spine,
|c| praxis_min_heap_new(c, &crate::scalars::INT),
|c, s, i| praxis_min_heap_push(c, s, praxis_alloc_int(c, i))
);
}