1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
//! What a virtual address range is made of.
//!
//! [`VirtualBuffer`](crate::VirtualBuffer) owns the interesting part: growth,
//! leasing, granule rounding, and the promise that the base address never
//! moves. None of that is platform-specific. What is platform-specific is three
//! operations — reserve address space, commit a block into part of it, release
//! that block — plus the granularity everything must be a multiple of.
//!
//! Splitting those out is what stops the device implementation from being a
//! second copy of the growth and leasing logic.
//!
//! # Host and device are the same shape
//!
//! | | host | CUDA |
//! |---|---|---|
//! | reserve | `VirtualAlloc2` placeholder / `mmap(PROT_NONE)` | `cuMemAddressReserve` |
//! | commit | `MapViewOfFile3` / `mmap(MAP_FIXED)` | `cuMemCreate` + `cuMemMap` + `cuMemSetAccess` |
//! | release | `UnmapViewOfFile2` / `mmap(PROT_NONE)` | `cuMemUnmap`; pool or `cuMemRelease` |
//! | granularity | 64 KiB / page size | `cuMemGetAllocationGranularity` |
//!
//! Measured on the hardware this was developed against: **64 KiB** on Windows,
//! **2 MiB** for CUDA on an RTX 4060 — where 2 MiB is roughly a thousand tokens
//! of one KV tensor at Llama-3-8B geometry.
//!
//! # Why there is an associated `Reservation`
//!
//! A backing cannot be stateless. Windows requires a placeholder to be *split*
//! before a block is mapped into part of it, and whether a split is needed
//! depends on the block's already-mapped neighbours — so committing needs to
//! know what else is mapped in the same reservation. CUDA needs the same shape
//! because mappings still belong to a reservation even when their physical
//! handles outlive them in a shared pool.
//!
//! Putting that state in an associated type rather than in the backing keeps
//! one backing able to serve many reservations, and keeps the state next to the
//! thing it describes.
//!
//! The cost is that `VirtualBacking` is not `dyn`-safe. That is deliberate and
//! it costs nothing: what callers hold is a
//! [`VirtualBuffer`](crate::VirtualBuffer), and *that* can be boxed behind an
//! object-safe trait if it ever needs to be. Nobody needs to hold a backing.
//!
//! # Why addresses are `usize`
//!
//! A device address is not a host pointer and must never be dereferenced on the
//! CPU. Typing both as `*mut u8` would invite exactly that.
use crateVirtualMemoryError;
use MemoryAuthorityId;
/// Who charges physical memory committed by a [`VirtualBacking`].
/// The platform operations a [`VirtualBuffer`](crate::VirtualBuffer) is built
/// from.
///
/// # Safety
///
/// This trait is `unsafe` to implement because [`VirtualBuffer`] hands out the
/// base address and lets callers write to the committed prefix. An
/// implementation that reported a range it had not reserved, or a granularity
/// it did not honour, would turn those writes into memory corruption rather
/// than an error. Specifically:
///
/// * [`VirtualBacking::granularity`] is constant for the backing's life and a
/// power of two.
/// * [`VirtualBacking::reserve`] takes address space only. It must not commit
/// memory: the whole design rests on reserving generously being free.
/// * [`VirtualBacking::base`] returns the address the reservation actually
/// starts at, and that address does not change for the reservation's life.
/// * After [`VirtualBacking::commit`] returns `Ok`, every byte of
/// `base + offset .. base + offset + len` is writable through that address.
/// * Dropping a `Reservation` releases both its address space and any blocks
/// still committed in it.
///
/// [`VirtualBuffer`]: crate::VirtualBuffer
pub unsafe
/// The process's own address space.
///
/// The default backing. Uses placeholder reservations on Windows and `mmap` on
/// unix, both of which let a block be committed into part of a larger
/// reservation and taken back out without disturbing its neighbours.
;
// SAFETY: every address comes from `VirtualRange::reserve`; the granularity is
// the platform's own and constant for the process; `VirtualRange` tracks its
// own mapped blocks and releases everything on drop.
unsafe