Skip to main content

zsh/
mem.rs

1//! Memory management for zshrs
2//!
3//! Port from zsh/Src/mem.c
4//!
5//! In Rust, we don't need the complex heap management that zsh uses in C.
6//! Instead, we provide a simpler arena-style allocator abstraction that
7//! can be used for temporary allocations that all get freed at once.
8
9use std::cell::RefCell;
10
11/// A memory arena for temporary allocations
12///
13/// This provides push/pop semantics similar to zsh's heap management,
14/// but uses Rust's standard memory management under the hood.
15pub struct HeapArena {
16    /// Stack of arena generations
17    generations: Vec<Generation>,
18}
19
20struct Generation {
21    /// Strings allocated in this generation
22    strings: Vec<String>,
23    /// Byte buffers allocated in this generation
24    buffers: Vec<Vec<u8>>,
25}
26
27impl Default for HeapArena {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl HeapArena {
34    pub fn new() -> Self {
35        HeapArena {
36            generations: vec![Generation {
37                strings: Vec::new(),
38                buffers: Vec::new(),
39            }],
40        }
41    }
42
43    /// Push a new heap state (like zsh's pushheap)
44    pub fn push(&mut self) {
45        self.generations.push(Generation {
46            strings: Vec::new(),
47            buffers: Vec::new(),
48        });
49    }
50
51    /// Pop and free all allocations since the last push (like zsh's popheap)
52    pub fn pop(&mut self) {
53        if self.generations.len() > 1 {
54            self.generations.pop();
55        }
56    }
57
58    /// Free allocations in current generation but keep generation marker (like zsh's freeheap)
59    pub fn free_current(&mut self) {
60        if let Some(gen) = self.generations.last_mut() {
61            gen.strings.clear();
62            gen.buffers.clear();
63        }
64    }
65
66    /// Allocate a string in the current generation
67    pub fn alloc_string(&mut self, s: String) -> &str {
68        if let Some(gen) = self.generations.last_mut() {
69            gen.strings.push(s);
70            gen.strings.last().map(|s| s.as_str()).unwrap()
71        } else {
72            panic!("No generation available")
73        }
74    }
75
76    /// Allocate bytes in the current generation
77    pub fn alloc_bytes(&mut self, bytes: Vec<u8>) -> &[u8] {
78        if let Some(gen) = self.generations.last_mut() {
79            gen.buffers.push(bytes);
80            gen.buffers.last().map(|b| b.as_slice()).unwrap()
81        } else {
82            panic!("No generation available")
83        }
84    }
85
86    /// Get current stack depth
87    pub fn depth(&self) -> usize {
88        self.generations.len()
89    }
90}
91
92thread_local! {
93    static HEAP: RefCell<HeapArena> = RefCell::new(HeapArena::new());
94}
95
96/// Push heap state
97pub fn pushheap() {
98    HEAP.with(|h| h.borrow_mut().push());
99}
100
101/// Pop heap state and free allocations
102pub fn popheap() {
103    HEAP.with(|h| h.borrow_mut().pop());
104}
105
106/// Free current heap allocations but keep state
107pub fn freeheap() {
108    HEAP.with(|h| h.borrow_mut().free_current());
109}
110
111/// Allocate memory (in Rust, this just uses the normal allocator)
112pub fn zalloc<T: Default>() -> Box<T> {
113    Box::default()
114}
115
116/// Allocate zeroed memory
117pub fn zshcalloc<T: Default>() -> Box<T> {
118    Box::default()
119}
120
121/// Reallocate memory (Rust handles this automatically with Vec)
122pub fn zrealloc<T>(v: &mut Vec<T>, new_size: usize)
123where
124    T: Default + Clone,
125{
126    v.resize(new_size, T::default());
127}
128
129/// Free memory (no-op in Rust, drop handles it)
130pub fn zfree<T>(_ptr: Box<T>) {
131    // Drop happens automatically
132}
133
134/// Free a string (no-op in Rust)
135pub fn zsfree(_s: String) {
136    // Drop happens automatically
137}
138
139/// Duplicate a string
140pub fn dupstring(s: &str) -> String {
141    s.to_string()
142}
143
144/// Duplicate a string with length
145pub fn dupstring_wlen(s: &str, len: usize) -> String {
146    s.chars().take(len).collect()
147}
148
149/// Create a heap-allocated string (in Rust, just creates a String)
150pub fn zhalloc_string(s: &str) -> String {
151    s.to_string()
152}
153
154/// Check if a pointer is within a memory pool
155/// In Rust, we don't need this - just returns true for any valid reference
156pub fn zheapptr<T>(_ptr: &T) -> bool {
157    true
158}
159
160/// Reallocate heap memory (from mem.c hrealloc)
161pub fn hrealloc(old: Vec<u8>, new_size: usize) -> Vec<u8> {
162    let mut v = old;
163    v.resize(new_size, 0);
164    v
165}
166
167/// Duplicate array of strings (from mem.c zarrdup)
168pub fn zarrdup(arr: &[String]) -> Vec<String> {
169    arr.to_vec()
170}
171
172/// Duplicate array with maximum length (from mem.c arrdup_max)
173pub fn arrdup_max(arr: &[String], max: usize) -> Vec<String> {
174    arr.iter().take(max).cloned().collect()
175}
176
177/// Get array length (from mem.c arrlen)
178pub fn arrlen<T>(arr: &[T]) -> usize {
179    arr.len()
180}
181
182/// Check if array length is less than n (from mem.c arrlen_lt)
183pub fn arrlen_lt<T>(arr: &[T], n: usize) -> bool {
184    arr.len() < n
185}
186
187/// Check if array length is less than or equal to n (from mem.c arrlen_le)
188pub fn arrlen_le<T>(arr: &[T], n: usize) -> bool {
189    arr.len() <= n
190}
191
192/// Check if array length equals n (from mem.c arrlen_eq)
193pub fn arrlen_eq<T>(arr: &[T], n: usize) -> bool {
194    arr.len() == n
195}
196
197/// Check if array length is greater than n (from mem.c arrlen_gt)
198pub fn arrlen_gt<T>(arr: &[T], n: usize) -> bool {
199    arr.len() > n
200}
201
202/// Concatenate strings with separator (from mem.c sepjoin)
203pub fn sepjoin(arr: &[String], sep: Option<&str>) -> String {
204    arr.join(sep.unwrap_or(" "))
205}
206
207/// Split string by separator (from mem.c sepsplit)
208pub fn sepsplit(s: &str, sep: &str, allow_empty: bool) -> Vec<String> {
209    if allow_empty {
210        s.split(sep).map(|s| s.to_string()).collect()
211    } else {
212        s.split(sep)
213            .filter(|s| !s.is_empty())
214            .map(|s| s.to_string())
215            .collect()
216    }
217}
218
219/// Allocate zeroed buffer (from mem.c zshcalloc)
220pub fn zshcalloc_buf(size: usize) -> Vec<u8> {
221    vec![0u8; size]
222}
223
224/// Allocate buffer with size (from mem.c zalloc)
225pub fn zalloc_buf(size: usize) -> Vec<u8> {
226    Vec::with_capacity(size)
227}
228
229/// Duplicate string to permanent storage (from mem.c ztrdup)
230pub fn ztrdup(s: &str) -> String {
231    s.to_string()
232}
233
234/// Duplicate n characters (from mem.c ztrncpy / ztrduppfx)
235pub fn ztrduppfx(s: &str, len: usize) -> String {
236    s.chars().take(len).collect()
237}
238
239/// Concatenate two strings (from mem.c bicat)
240pub fn bicat(s1: &str, s2: &str) -> String {
241    format!("{}{}", s1, s2)
242}
243
244/// Concatenate three strings (from mem.c tricat)
245pub fn tricat(s1: &str, s2: &str, s3: &str) -> String {
246    format!("{}{}{}", s1, s2, s3)
247}
248
249/// Dynamic concatenate on heap (from mem.c dyncat)
250pub fn dyncat(s1: &str, s2: &str) -> String {
251    format!("{}{}", s1, s2)
252}
253
254/// Get last character of string (from mem.c strend)
255pub fn strend(s: &str) -> Option<char> {
256    s.chars().last()
257}
258
259/// Append string (from mem.c appstr)
260pub fn appstr(base: &mut String, append: &str) {
261    base.push_str(append);
262}
263
264/// Memory statistics structure
265#[derive(Default, Debug, Clone)]
266pub struct MemStats {
267    pub heap_count: usize,
268    pub heap_total: usize,
269    pub heap_used: usize,
270    pub alloc_count: usize,
271    pub free_count: usize,
272}
273
274impl MemStats {
275    pub fn new() -> Self {
276        Self::default()
277    }
278}
279
280/// Get memory statistics (stub - Rust manages memory automatically)
281pub fn get_mem_stats() -> MemStats {
282    MemStats::new()
283}
284
285/// Context save/restore for memory (from mem.c zcontext_save/restore)
286pub struct MemContext {
287    heap_depth: usize,
288}
289
290impl MemContext {
291    pub fn save() -> Self {
292        let depth = HEAP.with(|h| h.borrow().depth());
293        MemContext { heap_depth: depth }
294    }
295
296    pub fn restore(self) {
297        HEAP.with(|h| {
298            let mut heap = h.borrow_mut();
299            while heap.depth() > self.heap_depth {
300                heap.pop();
301            }
302        });
303    }
304}
305
306/// Save memory context
307pub fn zcontext_save() -> MemContext {
308    MemContext::save()
309}
310
311/// Restore memory context
312pub fn zcontext_restore(ctx: MemContext) {
313    ctx.restore();
314}
315
316/// Queue signals during memory operations (stub in Rust - not needed)
317pub fn queue_signals() {}
318
319/// Unqueue signals after memory operations (stub in Rust - not needed)
320pub fn unqueue_signals() {}
321
322#[cfg(test)]
323mod tests {
324    use super::*;
325
326    #[test]
327    fn test_heap_push_pop() {
328        let mut arena = HeapArena::new();
329        assert_eq!(arena.depth(), 1);
330
331        arena.push();
332        assert_eq!(arena.depth(), 2);
333
334        arena.alloc_string("test".to_string());
335
336        arena.pop();
337        assert_eq!(arena.depth(), 1);
338    }
339
340    #[test]
341    fn test_heap_free_current() {
342        let mut arena = HeapArena::new();
343
344        arena.alloc_string("test1".to_string());
345        arena.alloc_bytes(vec![1, 2, 3]);
346
347        arena.free_current();
348        // Arena still at depth 1
349        assert_eq!(arena.depth(), 1);
350    }
351
352    #[test]
353    fn test_nested_generations() {
354        let mut arena = HeapArena::new();
355
356        arena.alloc_string("level1".to_string());
357
358        arena.push();
359        arena.alloc_string("level2".to_string());
360
361        arena.push();
362        arena.alloc_string("level3".to_string());
363
364        assert_eq!(arena.depth(), 3);
365
366        arena.pop();
367        assert_eq!(arena.depth(), 2);
368
369        arena.pop();
370        assert_eq!(arena.depth(), 1);
371    }
372
373    #[test]
374    fn test_dupstring() {
375        let s = dupstring("hello");
376        assert_eq!(s, "hello");
377    }
378
379    #[test]
380    fn test_dupstring_wlen() {
381        let s = dupstring_wlen("hello world", 5);
382        assert_eq!(s, "hello");
383    }
384
385    #[test]
386    fn test_global_heap() {
387        pushheap();
388        pushheap();
389        popheap();
390        popheap();
391        // Should not panic
392    }
393}