use std::cell::RefCell;
pub fn new_heap_id() -> u64 { NEXT_HEAP_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed) }
pub fn new_heaps() -> *mut std::ffi::c_void { queue_signals(); let h = HEAPS.load(std::sync::atomic::Ordering::Relaxed); HEAPS.store(std::ptr::null_mut(), std::sync::atomic::Ordering::Relaxed); FHEAP.store(std::ptr::null_mut(), std::sync::atomic::Ordering::Relaxed);
unqueue_signals(); h
}
impl Default for heap_arena {
fn default() -> Self {
Self::new()
}
}
impl heap_arena {
pub fn new() -> Self {
heap_arena {
generations: vec![Generation {
strings: Vec::new(),
buffers: Vec::new(),
}],
}
}
pub fn push(&mut self) {
self.generations.push(Generation {
strings: Vec::new(),
buffers: Vec::new(),
});
}
pub fn pop(&mut self) {
if self.generations.len() > 1 {
self.generations.pop();
}
}
pub fn free_current(&mut self) {
if let Some(gen) = self.generations.last_mut() {
gen.strings.clear();
gen.buffers.clear();
}
}
pub fn alloc_string(&mut self, s: String) -> &str {
if let Some(gen) = self.generations.last_mut() {
gen.strings.push(s);
gen.strings.last().map(|s| s.as_str()).unwrap()
} else {
panic!("No generation available")
}
}
pub fn alloc_bytes(&mut self, bytes: Vec<u8>) -> &[u8] {
if let Some(gen) = self.generations.last_mut() {
gen.buffers.push(bytes);
gen.buffers.last().map(|b| b.as_slice()).unwrap()
} else {
panic!("No generation available")
}
}
pub fn depth(&self) -> usize {
self.generations.len()
}
}
thread_local! {
static HEAP: RefCell<heap_arena> = RefCell::new(heap_arena::new());
}
pub fn old_heaps(old: *mut std::ffi::c_void) { queue_signals(); HEAPS.store(old, std::sync::atomic::Ordering::Relaxed); unqueue_signals(); }
pub fn switch_heaps(new: *mut std::ffi::c_void) -> *mut std::ffi::c_void { queue_signals(); let h = HEAPS.load(std::sync::atomic::Ordering::Relaxed); HEAPS.store(new, std::sync::atomic::Ordering::Relaxed); FHEAP.store(std::ptr::null_mut(), std::sync::atomic::Ordering::Relaxed);
unqueue_signals(); h
}
pub fn pushheap() { HEAP.with(|h| h.borrow_mut().push());
}
pub fn freeheap() { HEAP.with(|h| h.borrow_mut().free_current());
}
pub fn popheap() { HEAP.with(|h| h.borrow_mut().pop());
}
pub fn mmap_heap_alloc(n: &mut usize) -> *mut std::ffi::c_void { let pgsz = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as usize; let pgsz = if pgsz == 0 { 4096 } else { pgsz };
*n = (*n + pgsz - 1) & !(pgsz - 1);
unsafe {
libc::mmap(
std::ptr::null_mut(), *n,
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_ANON | libc::MAP_PRIVATE, -1, 0,
) }
}
pub fn zheapptr<T>(p: &T) -> bool { true
}
#[allow(unused_variables)]
pub fn zhalloc(size: usize) -> usize { 0 }
pub fn memory_validate(heap_id: u64) -> i32 { const HEAPID_PERMANENT: u64 = 0;
if heap_id == HEAPID_PERMANENT { return 0;
}
0
}
pub fn hrealloc(old: Vec<u8>, new_size: usize) -> Vec<u8> { let mut v = old;
v.resize(new_size, 0);
v
}
#[allow(unused_variables)]
pub fn hcalloc(size: usize) -> usize { 0 }
#[allow(unused_variables)]
pub fn malloc(size: usize) -> usize { 0 }
#[allow(unused_variables)]
pub fn free(p: *mut std::ffi::c_void) { }
pub fn zalloc<T: Default>() -> Box<T> { Box::default()
}
pub fn zshcalloc<T: Default>() -> Box<T> { Box::default()
}
pub fn zrealloc<T>(v: &mut Vec<T>, new_size: usize) where
T: Default + Clone,
{
v.resize(new_size, T::default());
}
#[allow(clippy::boxed_local)]
pub fn zfree<T>(p: Box<T>) { drop(p); }
pub fn zsfree(p: String) { }
pub fn realloc(_size: usize) -> usize { 0 }
#[allow(unused_variables)]
pub fn calloc(n: usize, size: usize) -> usize { 0 }
pub fn bin_mem( _name: &str,
_argv: &[String],
ops: &crate::ported::zsh_h::options,
_func: i32,
) -> i32 {
let m_l: i64 = 0; let m_high: i64 = 0; let m_s: i32 = 0; let m_b: i32 = 0; crate::ported::signals::queue_signals(); if crate::ported::zsh_h::OPT_ISSET(ops, b'v') { println!("The lower and the upper addresses of the heap. Diff gives");
println!("the difference between them, i.e. the size of the heap.\n");
}
println!("low mem {}\t high mem {}\t diff {}", m_l, m_high, m_high - m_l); if crate::ported::zsh_h::OPT_ISSET(ops, b'v') { println!("\nThe number of bytes that were allocated using sbrk() and");
println!("the number of bytes that were given back to the system");
println!("via brk().");
}
println!("\nsbrk {}\tbrk {}", m_s, m_b); if crate::ported::zsh_h::OPT_ISSET(ops, b'v') { println!("\nInformation about the sizes that were allocated or freed.");
println!("For each size that were used the number of mallocs and");
println!("frees is shown. Diff gives the difference between these");
println!("values, i.e. the number of blocks of that size that is");
println!("currently allocated. Total is the product of size and diff,");
println!("i.e. the number of bytes that are allocated for blocks of");
println!("this size. The last field gives the accumulated number of");
println!("bytes for all sizes.");
}
println!("\nsize\tmalloc\tfree\tdiff\ttotal\tcum"); if crate::ported::zsh_h::OPT_ISSET(ops, b'v') { println!("\nThe list of memory blocks. For each block the following");
println!("information is shown:\n");
println!("num\tthe number of this block");
println!("tnum\tlike num but counted separately for used and free");
println!("\tblocks");
println!("addr\tthe address of this block");
println!("len\tthe length of the block");
println!("state\tthe state of this block, this can be:");
println!("\t used\tthis block is used for one big block");
println!("\t free\tthis block is free");
println!("\t small\tthis block is used for an array of small blocks");
println!("cum\tthe accumulated sizes of the blocks, counted");
println!("\tseparately for used and free blocks");
println!("\nFor blocks holding small blocks the number of free");
println!("blocks, the number of used blocks and the size of the");
println!("blocks is shown. For otherwise used blocks the first few");
println!("bytes are shown as an ASCII dump.");
}
println!("\nblock list:\nnum\ttnum\taddr\t\tlen\tstate\tcum"); if crate::ported::zsh_h::OPT_ISSET(ops, b'v') { println!("\nHere is some information about the small blocks used.");
println!("For each size the arrays with the number of free and the");
println!("number of used blocks are shown.");
}
println!("\nsmall blocks:\nsize\tblocks (free/used)"); if crate::ported::zsh_h::OPT_ISSET(ops, b'v') { println!("\n\nBelow is some information about the allocation");
println!("behaviour of the zsh heaps. First the number of times");
println!("pushheap(), popheap(), and freeheap() were called.");
}
println!("\nzsh heaps:\n"); let h_push: i32 = 0; let h_pop: i32 = 0;
let h_free: i32 = 0;
println!("push {}\tpop {}\tfree {}\n", h_push, h_pop, h_free); if crate::ported::zsh_h::OPT_ISSET(ops, b'v') { println!("\nThe next list shows for several sizes the number of times");
println!("memory of this size were taken from heaps.\n");
}
println!("size\tmalloc\ttotal"); crate::ported::signals::unqueue_signals(); 0 }
#[allow(non_camel_case_types)]
pub struct heap_arena {
generations: Vec<Generation>,
}
struct Generation {
strings: Vec<String>,
buffers: Vec<Vec<u8>>,
}
pub fn dupstring(s: &str) -> String { s.to_string()
}
pub fn dupstring_wlen(s: &str, len: usize) -> String { let bytes = s.as_bytes();
let n = len.min(bytes.len());
String::from_utf8_lossy(&bytes[..n]).into_owned()
}
pub fn zarrdup(s: &[String]) -> Vec<String> { s.to_vec()
}
pub fn arrdup_max(arr: &[String], max: usize) -> Vec<String> {
arr.iter().take(max).cloned().collect()
}
pub fn arrlen<T>(s: &[T]) -> usize { s.len()
}
pub fn arrlen_lt<T>(s: &[T], upper_bound: usize) -> bool { s.len() < upper_bound
}
pub fn arrlen_le<T>(s: &[T], upper_bound: usize) -> bool { s.len() <= upper_bound
}
pub fn arrlen_gt<T>(s: &[T], lower_bound: usize) -> bool { s.len() > lower_bound
}
pub fn sepjoin(arr: &[String], sep: Option<&str>) -> String { arr.join(sep.unwrap_or(" "))
}
pub use crate::ported::signals_h::{queue_signals, unqueue_signals};
pub fn sepsplit(s: &str, sep: &str, allow_empty: bool) -> Vec<String> { if allow_empty {
s.split(sep).map(|s| s.to_string()).collect()
} else {
s.split(sep)
.filter(|s| !s.is_empty())
.map(|s| s.to_string())
.collect()
}
}
pub static NEXT_HEAP_ID: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(1);
pub static LAST_HEAP_ID: std::sync::atomic::AtomicU64 =
std::sync::atomic::AtomicU64::new(0);
pub fn ztrdup(s: &str) -> String { s.to_string()
}
pub fn ztrduppfx(s: &str, len: usize) -> String { let bytes = s.as_bytes();
let n = len.min(bytes.len());
String::from_utf8_lossy(&bytes[..n]).into_owned()
}
pub fn bicat(s1: &str, s2: &str) -> String { format!("{}{}", s1, s2)
}
pub static HEAPS: std::sync::atomic::AtomicPtr<std::ffi::c_void> =
std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
pub static FHEAP: std::sync::atomic::AtomicPtr<std::ffi::c_void> =
std::sync::atomic::AtomicPtr::new(std::ptr::null_mut());
pub fn tricat(s1: &str, s2: &str, s3: &str) -> String { format!("{}{}{}", s1, s2, s3)
}
pub fn dyncat(s1: &str, s2: &str) -> String { format!("{}{}", s1, s2)
}
pub fn strend(str: &str) -> Option<char> { str.chars().last()
}
pub fn appstr(base: &mut String, append: &str) { base.push_str(append);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_heap_push_pop() {
let mut arena = heap_arena::new();
assert_eq!(arena.depth(), 1);
arena.push();
assert_eq!(arena.depth(), 2);
arena.alloc_string("test".to_string());
arena.pop();
assert_eq!(arena.depth(), 1);
}
#[test]
fn test_heap_free_current() {
let mut arena = heap_arena::new();
arena.alloc_string("test1".to_string());
arena.alloc_bytes(vec![1, 2, 3]);
arena.free_current();
assert_eq!(arena.depth(), 1);
}
#[test]
fn test_nested_generations() {
let mut arena = heap_arena::new();
arena.alloc_string("level1".to_string());
arena.push();
arena.alloc_string("level2".to_string());
arena.push();
arena.alloc_string("level3".to_string());
assert_eq!(arena.depth(), 3);
arena.pop();
assert_eq!(arena.depth(), 2);
arena.pop();
assert_eq!(arena.depth(), 1);
}
#[test]
fn test_dupstring() {
let s = dupstring("hello");
assert_eq!(s, "hello");
}
#[test]
fn test_dupstring_wlen() {
let s = dupstring_wlen("hello world", 5);
assert_eq!(s, "hello");
}
#[test]
fn test_global_heap() {
pushheap();
pushheap();
popheap();
popheap();
}
#[test]
fn sepjoin_with_explicit_separator() {
assert_eq!(sepjoin(&["a".into(), "b".into(), "c".into()], Some("-")), "a-b-c");
assert_eq!(sepjoin(&["a".into(), "b".into(), "c".into()], Some("")), "abc");
}
#[test]
fn sepjoin_none_defaults_to_space() {
assert_eq!(sepjoin(&["a".into(), "b".into()], None), "a b");
}
#[test]
fn sepjoin_empty_array_returns_empty() {
assert_eq!(sepjoin(&[], Some("-")), "");
assert_eq!(sepjoin(&[], None), "");
}
#[test]
fn sepsplit_canonical_path_split() {
let r = sepsplit("a/b/c", "/", false);
assert_eq!(r, vec!["a".to_string(), "b".to_string(), "c".to_string()]);
}
#[test]
fn sepsplit_filters_empty_segments_when_disallowed() {
let r = sepsplit("a//b", "/", false);
assert_eq!(r, vec!["a".to_string(), "b".to_string()]);
}
#[test]
fn sepsplit_preserves_empty_segments_when_allowed() {
let r = sepsplit("a//b", "/", true);
assert_eq!(r.len(), 3, "consecutive sep yields empty middle entry");
assert_eq!(r[1], "");
}
#[test]
fn new_heap_id_is_monotonically_increasing() {
let a = new_heap_id();
let b = new_heap_id();
let c = new_heap_id();
assert!(a < b && b < c, "heap-id sequence must be strictly increasing: {a} < {b} < {c}");
}
#[test]
fn pushheap_popheap_balance_two_levels() {
pushheap();
pushheap();
popheap();
popheap();
}
#[test]
fn freeheap_does_not_pop_levels() {
pushheap();
freeheap();
freeheap();
freeheap();
popheap();
}
#[test]
fn popheap_without_push_does_not_panic() {
popheap();
}
#[test]
fn dupstring_wlen_is_byte_counted_not_char_counted() {
assert_eq!(dupstring_wlen("café", 5), "café",
"c:55 — memcpy is byte-counted; 5 bytes of 'café' is the whole string");
assert_eq!(dupstring_wlen("café", 100), "café",
"c:55 — len > strlen clamps (Rust safety) instead of UB");
let r = dupstring_wlen("café", 4);
assert!(r.starts_with("caf"),
"c:55 — byte cut at codepoint boundary uses from_utf8_lossy");
assert_eq!(dupstring_wlen("", 0), "");
assert_eq!(dupstring_wlen("hello", 0), "",
"c:55 — len 0 → empty result");
}
#[test]
fn ztrduppfx_is_byte_counted_not_char_counted() {
assert_eq!(ztrduppfx("café", 5), "café",
"c:175 — 5 bytes copies whole 'café' (é=2 bytes)");
assert_eq!(ztrduppfx("hello", 3), "hel");
assert_eq!(ztrduppfx("字", 3), "字");
assert_eq!(ztrduppfx("", 5), "");
assert_eq!(ztrduppfx("ab", 100), "ab",
"c:175 — len > strlen clamps");
}
#[test]
fn dupstring_yields_independent_copy() {
let mut src = String::from("original");
let dup = dupstring(&src);
src.clear();
assert_eq!(dup, "original",
"c:38-40 — dup must survive source-side mutation");
}
#[test]
fn zarrdup_clones_array_independent() {
let src = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let dup = zarrdup(&src);
assert_eq!(dup, src);
let mut dup_mut = dup;
dup_mut.push("d".to_string());
assert_eq!(src.len(), 3);
assert_eq!(dup_mut.len(), 4);
}
#[test]
fn zarrdup_empty_returns_empty() {
let src: Vec<String> = Vec::new();
let dup = zarrdup(&src);
assert!(dup.is_empty());
}
#[test]
fn arrdup_max_truncates_at_bound() {
let src = vec!["a".to_string(), "b".to_string(), "c".to_string(), "d".to_string()];
let r = arrdup_max(&src, 2);
assert_eq!(r, vec!["a".to_string(), "b".to_string()]);
let r = arrdup_max(&src, 4);
assert_eq!(r, src);
let r = arrdup_max(&src, 99);
assert_eq!(r.len(), 4);
let r = arrdup_max(&src, 0);
assert!(r.is_empty());
}
#[test]
fn arrlen_matches_slice_len() {
let arr: Vec<String> = vec!["one".into(), "two".into(), "three".into()];
assert_eq!(arrlen(&arr), 3);
let empty: Vec<String> = vec![];
assert_eq!(arrlen(&empty), 0);
}
#[test]
fn arrlen_lt_boundary_semantics() {
let arr: Vec<String> = vec!["a".into(), "b".into(), "c".into()];
assert!(arrlen_lt(&arr, 4), "3 < 4");
assert!(!arrlen_lt(&arr, 3), "3 < 3 is false (strict)");
assert!(!arrlen_lt(&arr, 2), "3 < 2 is false");
assert!(!arrlen_lt(&arr, 0), "3 < 0 is false");
}
#[test]
fn arrlen_le_boundary_semantics() {
let arr: Vec<String> = vec!["a".into(), "b".into(), "c".into()];
assert!(arrlen_le(&arr, 4), "3 <= 4");
assert!(arrlen_le(&arr, 3), "3 <= 3 is TRUE (non-strict)");
assert!(!arrlen_le(&arr, 2), "3 <= 2 is false");
assert!(!arrlen_le(&arr, 0), "3 <= 0 is false");
}
#[test]
fn dupstring_and_dupstring_wlen_agree_on_ascii() {
let a = dupstring("hello");
let b = dupstring_wlen("hello", 5);
assert_eq!(a, b);
assert_eq!(a, "hello");
}
}