Skip to main content

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//! Arena allocator.
8//! We map anonymous memory in arena-sized chunks and fill allocation
9//! requests from the current chunk. This reduces syscalls to one per chunk,
10//! often one per whole program. We never munmap old sections, memory stays
11//! alive for duration of run.
12
13#![allow(static_mut_refs)]
14
15use core::alloc::Layout;
16use core::ffi::c_void;
17use core::ptr;
18
19use crate::syscall;
20
21#[cfg(feature = "panic-on-realloc")]
22static mut IS_FIRST_REALLOC: bool = true;
23
24#[cfg(feature = "print-allocations")]
25use crate::common::utils::to_ascii;
26
27/// All allocated memory locations will have this alignment, max_align_t
28const ALIGN: usize = 16;
29
30/// Allocate a minimum of one MiB at a time.
31const ARENA_SIZE: usize = 1024 * 1024;
32const PAGE_SIZE: usize = 4096;
33
34struct Arena {
35    base: *mut u8,
36    size: usize,
37    offset: usize,
38}
39
40static mut ARENA: Arena = Arena {
41    base: ptr::null_mut(),
42    size: 0,
43    offset: 0,
44};
45
46pub struct ArenaAlloc;
47
48#[inline]
49unsafe fn map_chunk(min_size: usize) -> Arena {
50    let size = min_size.max(ARENA_SIZE).next_multiple_of(PAGE_SIZE);
51    let base = syscall::mmap(
52        ptr::null_mut(),
53        size,
54        syscall::PROT_READ | syscall::PROT_WRITE,
55        syscall::MAP_PRIVATE | syscall::MAP_ANONYMOUS,
56        -1,
57        0,
58    ) as *mut u8;
59
60    if base.is_null() {
61        let msg = c"mmap failed in common/alloc.rs\n";
62        syscall::write(2, msg.as_ptr() as *const c_void, msg.count_bytes());
63        syscall::exit(1);
64    }
65
66    Arena {
67        base,
68        size,
69        offset: 0,
70    }
71}
72
73// In case you were wondering, yes all three methods get used. Rust does
74// a bnuch of alloc_zeroed and realloc.
75//
76// Build with feature "print-allocations" to see memory being allocated:
77// cargo build --features="print-allocations"
78// cargo build --release --features="print-allocations" -Zbuild-std="core,alloc"
79//
80// There's a Python script at the end of this file to summarize the output.
81//
82// Normal / prompt usage seems to peak under 64 Kib of active memory.
83//
84// `ort list` peaks around 180 Kib because it has one large (~128 KiB)
85// allocation which is a string holding the names of all models, so we can sort them.
86unsafe impl core::alloc::GlobalAlloc for ArenaAlloc {
87    unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
88        #[cfg(feature = "print-allocations")]
89        {
90            let mut buf = [0u8; 16];
91            buf[0] = b'+';
92            let len = to_ascii(layout.size(), &mut buf[1..]);
93            crate::syscall::write(2, buf.as_ptr().cast(), len);
94
95            // Also print alignment
96            //let mut buf = [0u8; 16];
97            //buf[0] = b' ';
98            //let len = to_ascii(layout.align(), &mut buf[1..]);
99            //unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
100        }
101
102        let alloc_size = layout.size().next_multiple_of(ALIGN);
103
104        unsafe {
105            if ARENA.base.is_null() || ARENA.offset + alloc_size > ARENA.size {
106                ARENA = map_chunk(alloc_size);
107            }
108
109            let ptr = ARENA.base.add(ARENA.offset);
110            ARENA.offset += alloc_size;
111            ptr
112        }
113    }
114
115    unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
116        #[cfg(feature = "print-allocations")]
117        {
118            let mut buf = [0u8; 16];
119            buf[0] = b'+';
120            let len = to_ascii(layout.size(), &mut buf[1..]);
121            crate::syscall::write(2, buf.as_ptr().cast(), len);
122
123            // Also print alignment
124            //let mut buf = [0u8; 16];
125            //buf[0] = b' ';
126            //let len = to_ascii(layout.align(), &mut buf[1..]);
127            //unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
128        }
129
130        // Anonymous mmap memory is zeroed by the kernel
131        unsafe { self.alloc(layout) }
132    }
133
134    #[allow(unused_variables)]
135    unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
136        #[cfg(feature = "print-allocations")]
137        {
138            let mut buf = [0u8; 16];
139            buf[0] = b'-';
140            let len = to_ascii(layout.size(), &mut buf[1..]);
141            crate::syscall::write(2, buf.as_ptr().cast(), len);
142        }
143
144        // we never free, program runtime is short
145    }
146
147    // ort is tuned to avoid reallocations, so we don't override it.
148    // Parent implements realloc as alloc-and-copy, which is fine because it should not happen.
149    //
150    // Keep here to panic-on-realloc when code changes.
151    /*
152    #[allow(unused_variables)]
153    unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
154        #[cfg(feature = "print-allocations")]
155        {
156            let mut buf = [0u8; 16];
157            buf[0] = b'\\';
158            let len = to_ascii(layout.size(), &mut buf[1..]);
159            crate::syscall::write(2, buf.as_ptr().cast(), len);
160
161            buf[0] = b'/';
162            let len = to_ascii(new_size, &mut buf[1..]);
163            crate::syscall::write(2, buf.as_ptr().cast(), len);
164
165            // Also print alignment
166            //let mut buf = [0u8; 16];
167            //buf[0] = b' ';
168            //let len = to_ascii(layout.align(), &mut buf[1..]);
169            //unsafe { crate::libc::write(2, buf.as_ptr().cast(), len) };
170        }
171
172        #[cfg(feature = "panic-on-realloc")]
173        unsafe {
174            // The panic machinery uses realloc, so we must immediately disable this or we would
175            // panic during the panic, and get no useful stack trace.
176            if IS_FIRST_REALLOC {
177                IS_FIRST_REALLOC = false;
178                panic!("realloc {} -> {}", layout.size(), new_size);
179            }
180        }
181    }
182    */
183}
184
185/*
186"""Print running totals from allocs.txt and report the maximum cumulative value."""
187
188from pathlib import Path
189
190
191def main() -> None:
192    path = Path("allocs.txt")
193    if not path.is_file():
194        raise SystemExit("allocs.txt not found in the current directory")
195
196    total = 0
197    max_total = None  # Highest cumulative total
198    max_plus = None   # Largest individual + value
199    max_minus = None  # Largest (most negative) individual - value
200
201    with path.open() as fh:
202        for raw_line in fh:
203            line = raw_line.strip()
204            if not line:
205                continue  # Skip blank lines silently
206
207            try:
208                delta = int(line)
209            except ValueError as exc:
210                raise SystemExit(f"Invalid line in allocs.txt: {line!r}") from exc
211            # realloc indicators
212            line = line.replace('/', '+').replace('\\', '-')
213
214            if delta > 0:
215                max_plus = delta if max_plus is None else max(max_plus, delta)
216            elif delta < 0:
217                max_minus = delta if max_minus is None else min(max_minus, delta)
218
219            total += delta
220            max_total = total if max_total is None else max(max_total, total)
221            print(f"{line} {total}")
222
223    print()  # Blank line before the summary, matching the example
224    print(f"Max: {max_total if max_total is not None else 0}")
225    print(f"Largest +: {max_plus if max_plus is not None else 0}")
226    print(f"Largest -: {max_minus if max_minus is not None else 0}")
227
228
229if __name__ == "__main__":
230    main()
231*/