ort_openrouter_cli/common/alloc.rs
1//! ort: Open Router CLI
2//! https://github.com/grahamking/ort
3//!
4//! MIT License
5//! Copyright (c) 2025 Graham King
6
7#![allow(static_mut_refs)]
8
9#[cfg(not(feature = "arena-alloc"))]
10use crate::libc;
11use core::alloc::Layout;
12#[cfg(not(feature = "arena-alloc"))]
13use core::ffi::c_void;
14
15#[cfg(feature = "panic-on-realloc")]
16static mut IS_FIRST_REALLOC: bool = true;
17
18#[cfg(feature = "arena-alloc")]
19use core::sync::atomic::{AtomicUsize, Ordering};
20
21#[cfg(feature = "print-allocations")]
22use crate::common::utils::to_ascii;
23
24/// All allocated memory locations will have this alignment, max_align_t
25#[cfg(feature = "arena-alloc")]
26const ALIGN: usize = 16;
27
28// How much memory to allocate total. Don't exceed this!
29#[cfg(feature = "arena-alloc")]
30const MEM_SIZE: usize = 2 * 1024 * 1024;
31
32#[cfg(feature = "arena-alloc")]
33#[repr(align(16))]
34struct Heap(pub [u8; MEM_SIZE]);
35
36#[cfg(feature = "arena-alloc")]
37static mut HEAP: Heap = Heap([0u8; MEM_SIZE]);
38#[cfg(feature = "arena-alloc")]
39static mut OFFSET: AtomicUsize = AtomicUsize::new(0);
40
41pub struct LibcAlloc;
42
43// In case you were wondering, yes all three methods get used. Rust does
44// a bnuch of alloc_zeroed and realloc.
45//
46// Build with feature "print-allocations" to see memory being allocated:
47// cargo build --features="print-allocations"
48// cargo build --release --features="print-allocations" -Zbuild-std="core,alloc"
49//
50// There's a Python script at the end of this file to summarize the output.
51//
52// Normal / prompt usage seems to peak under 64 Kib of active memory.
53//
54// `ort list` peaks around 180 Kib because it has one large (~128 KiB)
55// allocation which is a string holding the names of all models, so we can sort them.
56unsafe impl core::alloc::GlobalAlloc for LibcAlloc {
57 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
58 #[cfg(feature = "print-allocations")]
59 {
60 let mut buf = [0u8; 16];
61 buf[0] = b'+';
62 let len = to_ascii(layout.size(), &mut buf[1..]);
63 unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
64
65 // Also print alignment
66 //let mut buf = [0u8; 16];
67 //buf[0] = b' ';
68 //let len = to_ascii(layout.align(), &mut buf[1..]);
69 //unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
70 }
71
72 #[cfg(not(feature = "arena-alloc"))]
73 unsafe {
74 libc::malloc(layout.size().max(layout.align())) as *mut u8
75 }
76
77 #[cfg(feature = "arena-alloc")]
78 {
79 let next_offset = layout.size().next_multiple_of(ALIGN);
80 unsafe {
81 HEAP.0
82 .as_mut_ptr()
83 .add(OFFSET.fetch_add(next_offset, Ordering::Relaxed))
84 }
85 }
86 }
87
88 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
89 #[cfg(feature = "print-allocations")]
90 {
91 let mut buf = [0u8; 16];
92 buf[0] = b'+';
93 let len = to_ascii(layout.size(), &mut buf[1..]);
94 unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
95
96 // Also print alignment
97 //let mut buf = [0u8; 16];
98 //buf[0] = b' ';
99 //let len = to_ascii(layout.align(), &mut buf[1..]);
100 //unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
101 }
102
103 #[cfg(not(feature = "arena-alloc"))]
104 unsafe {
105 libc::calloc(1, layout.size().max(layout.align())) as *mut u8
106 }
107
108 #[cfg(feature = "arena-alloc")]
109 {
110 let next_offset = layout.size().next_multiple_of(ALIGN);
111 unsafe {
112 HEAP.0
113 .as_mut_ptr()
114 .add(OFFSET.fetch_add(next_offset, Ordering::Relaxed))
115 }
116 }
117 }
118
119 #[allow(unused_variables)]
120 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
121 #[cfg(feature = "print-allocations")]
122 {
123 let mut buf = [0u8; 16];
124 buf[0] = b'-';
125 let len = to_ascii(layout.size(), &mut buf[1..]);
126 unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
127 }
128
129 #[cfg(not(feature = "arena-alloc"))]
130 unsafe {
131 libc::free(ptr as *mut c_void)
132 }
133 }
134
135 #[cfg(not(feature = "arena-alloc"))]
136 #[allow(unused_variables)]
137 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
138 #[cfg(feature = "print-allocations")]
139 {
140 let mut buf = [0u8; 16];
141 buf[0] = b'\\';
142 let len = to_ascii(layout.size(), &mut buf[1..]);
143 unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
144
145 buf[0] = b'/';
146 let len = to_ascii(new_size, &mut buf[1..]);
147 unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
148
149 // Also print alignment
150 //let mut buf = [0u8; 16];
151 //buf[0] = b' ';
152 //let len = to_ascii(layout.align(), &mut buf[1..]);
153 //unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
154 }
155
156 #[cfg(feature = "panic-on-realloc")]
157 unsafe {
158 // The panic machinery uses realloc, so we must immediately disable this or we would
159 // panic during the panic, and get no useful stack trace.
160 if IS_FIRST_REALLOC {
161 IS_FIRST_REALLOC = false;
162 panic!("realloc {} -> {}", layout.size(), new_size);
163 }
164 }
165
166 unsafe { libc::realloc(ptr as *mut c_void, new_size) as *mut u8 }
167 }
168}
169
170/*
171"""Print running totals from allocs.txt and report the maximum cumulative value."""
172
173from pathlib import Path
174
175
176def main() -> None:
177 path = Path("allocs.txt")
178 if not path.is_file():
179 raise SystemExit("allocs.txt not found in the current directory")
180
181 total = 0
182 max_total = None # Highest cumulative total
183 max_plus = None # Largest individual + value
184 max_minus = None # Largest (most negative) individual - value
185
186 with path.open() as fh:
187 for raw_line in fh:
188 line = raw_line.strip()
189 if not line:
190 continue # Skip blank lines silently
191
192 try:
193 delta = int(line)
194 except ValueError as exc:
195 raise SystemExit(f"Invalid line in allocs.txt: {line!r}") from exc
196 # realloc indicators
197 line = line.replace('/', '+').replace('\\', '-')
198
199 if delta > 0:
200 max_plus = delta if max_plus is None else max(max_plus, delta)
201 elif delta < 0:
202 max_minus = delta if max_minus is None else min(max_minus, delta)
203
204 total += delta
205 max_total = total if max_total is None else max(max_total, total)
206 print(f"{line} {total}")
207
208 print() # Blank line before the summary, matching the example
209 print(f"Max: {max_total if max_total is not None else 0}")
210 print(f"Largest +: {max_plus if max_plus is not None else 0}")
211 print(f"Largest -: {max_minus if max_minus is not None else 0}")
212
213
214if __name__ == "__main__":
215 main()
216*/