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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
use core::ptr::NonNull;
#[cfg(feature = "bench-internals")]
use core::sync::atomic::Ordering;
#[cfg(feature = "lazy-commit")]
use crate::api::{commit_range, try_commit_range};
use crate::api::{decommit, decommit_lazy, dispatch_try_decommit, recommit, try_recommit};
#[cfg(feature = "bench-internals")]
use crate::bench_internals::HUGE_DECOMMIT_ATTEMPTS;
use crate::decommit_outcome::DecommitOutcome;
use crate::error::VmemError;
use crate::os::release_reservation;
use crate::page::PAGE;
use crate::page_size::{page_size_or_poison, PAGE_SIZE_QUERY_FAILED};
use crate::reservation_full_parts::ReservationFullParts;
use crate::reservation_parts::ReservationParts;
/// An owning handle to one aligned span of anonymous virtual memory.
///
/// `as_ptr()` is non-null, aligned to the `align` requested at reservation, and
/// valid for `len()` bytes for the lifetime of this handle **with the following
/// exceptions**:
///
/// - **Decommitted ranges**: Ranges that the caller has decommitted (via the
/// free functions or the safe methods) and not yet recommitted have
/// platform-specific behavior:
/// - **Windows**: pages are unmapped until `recommit`; access before `recommit`
/// crashes with `STATUS_ACCESS_VIOLATION`.
/// - **Linux (eager `decommit`)**: pages are zeroed on next access via `MADV_DONTNEED`.
/// - **Linux (lazy `decommit_lazy`)**: pages keep old contents until kernel
/// reclaims them under pressure; writes before reclamation cancel the free.
/// - **Darwin/BSD**: pages keep old contents; `MADV_DONTNEED` is advisory-only
/// and does not reliably zero.
/// - **Huge reservations, `decommit_lazy` (both layers), and `decommit`/
/// `try_decommit` on Windows or on a non-huge-page-aligned range on
/// Linux/Android**: old contents remain. The safe methods
/// [`Reservation::decommit`]/[`Reservation::decommit_lazy`] skip the
/// backend call outright in this case (they can consult `is_huge()` and,
/// for `decommit`, the requested range); the free functions cannot
/// consult `is_huge()`, so they still issue the syscall — which the OS
/// then refuses or ignores. Same observable outcome, different mechanism;
/// do not read "no-op" as "no syscall" for the free functions.
/// - **Huge reservations, eager `decommit`/`try_decommit`, Linux/Android
/// kernel >= 5.18, range aligned to the huge page size (2 MiB) at both
/// endpoints (task #1140)**: this is the ONE huge-page case where decommit
/// actually works — pages ARE zeroed on next access via `MADV_DONTNEED`,
/// same as the ordinary eager-Linux case above. Both the safe method and
/// the free function issue the real syscall here; they agree. See
/// [`Reservation::decommit`]'s own doc for the exact eligibility rule.
///
/// - **Lazy reservations on Windows (feature `lazy-commit`)**: When created via
/// `reserve_aligned_lazy`, only the `initial_commit` prefix is committed at
/// reservation time. The tail `[initial_commit, len())` must be committed via
/// `commit_range` before it becomes writable. Writing to the uncommitted tail
/// results in an access violation.
///
/// The span is **not** initialised. Dropping the handle returns the whole
/// underlying OS reservation to the OS exactly once.
///
/// For a self-hosted allocator that records `(reservation, reservation_len)` in
/// its own metadata rather than keeping a `Vec<Reservation>`, use
/// [`into_parts`](Self::into_parts) to take the raw reservation (suppressing the
/// `Drop`) and release it later with [`release`](crate::api::release).
///
/// `Reservation` is `Send` (the span is owned exclusively) but not `Sync`
/// (writes through the raw pointer are unsynchronised — that is the caller's
/// concern).
pub struct Reservation {
pub(crate) base: NonNull<u8>,
pub(crate) len: usize,
pub(crate) reservation: NonNull<u8>,
pub(crate) reservation_len: usize,
/// The alignment requested at reservation time. Carried so the `Drop` /
/// [`release`](crate::api::release) path can reconstruct the exact `Layout` under miri (the
/// native `munmap` / `VirtualFree` paths ignore it). See [`into_parts`].
pub(crate) align: usize,
/// Whether OS large/huge pages were actually granted for this reservation.
/// True if `reserve_aligned_huge` succeeded in obtaining large pages on
/// Linux (`MAP_HUGETLB`) or Windows (`MEM_LARGE_PAGES` when the OS grants
/// the request). False if the request fell back to ordinary pages.
///
/// This flag is the "best-effort" observable: a caller can detect whether
/// the huge-page feature actually engaged, rather than receiving only an
/// indistinguishable `Ok(Reservation)` on every fallback path.
///
/// **Windows limitation (task #848 single-call fast path):** on Windows,
/// this flag is `true` only when ALL of the following hold:
/// 1. The fast-path condition `align <= GetLargePageMinimum()` is satisfied
/// (typically `align <= 2 MiB` on x86_64)
/// 2. `size` is a multiple of the system's large-page minimum
/// 3. The calling process has `SeLockMemoryPrivilege` granted AND has
/// **enabled** it via `AdjustTokenPrivileges` (the crate does not do
/// this for you — a process with the privilege granted but not
/// enabled fails exactly like an unprivileged one and silently falls
/// back to ordinary pages)
///
/// NOTE: The widened fast-path condition (II-3, 2026-08-16 audit finding) expanded
/// the single-call ATTEMPT window from `align <= 64 KiB` to `align <= GetLargePageMinimum()`,
/// but on an unprivileged host the actual paths that SUCCEED (pass the post-call alignment
/// check) are typically still limited. When large pages are NOT granted (unprivileged),
/// `VirtualAlloc`'s alignment guarantee is only 64 KiB; in practice it typically does NOT
/// happen to land on the requested alignment, so the post-call check fails and the fast
/// path falls through to the two-call path. Practically, this means `is_huge == true` only
/// for shapes where large pages are actually granted, which requires all three conditions
/// above to hold.
///
/// If any of these conditions fail, the function falls back to ordinary
/// pages and this flag is `false`. On Windows, large pages (`MEM_LARGE_PAGES`)
/// are only ever requested and possibly granted via the single-call fast path;
/// the two-call path never requests large pages, so
/// `granted_huge` is always `false` for a reservation that takes it.
pub(crate) granted_huge: bool,
}
impl core::fmt::Debug for Reservation {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Reservation")
.field("base", &self.base.as_ptr())
.field("len", &self.len)
.field("reservation", &self.reservation.as_ptr())
.field("reservation_len", &self.reservation_len)
.field("align", &self.align)
.field("granted_huge", &self.granted_huge)
.finish()
}
}
impl Reservation {
/// The aligned usable base of this span. Non-null, aligned to the `align`
/// requested at reservation.
///
/// **Validity scope:** Valid for [`len()`](Self::len) bytes, with the
/// following exceptions:
///
/// - **Decommitted ranges:** Ranges decommitted via the free functions or
/// safe methods and not yet recommitted have platform-specific behavior:
/// - **Windows**: pages are unmapped until `recommit`; access before
/// `recommit` crashes with `STATUS_ACCESS_VIOLATION`.
/// - **Linux (eager `decommit`)**: pages are zeroed on next access via
/// `MADV_DONTNEED`.
/// - **Linux (lazy `decommit_lazy`)**: pages keep old contents until kernel
/// reclaims them under pressure; writes before reclamation cancel the free.
/// - **Darwin/BSD**: pages keep old contents; `MADV_DONTNEED` is
/// advisory-only and does not reliably zero.
/// - **Huge reservations, `decommit_lazy` (both layers), and `decommit`/
/// `try_decommit` on Windows or on a non-huge-page-aligned range on
/// Linux/Android**: old contents remain. The safe
/// methods [`Self::decommit`]/[`Self::decommit_lazy`] skip the backend
/// call outright in this case (they can consult [`Self::is_huge`] and,
/// for `decommit`, the requested range); the free
/// functions cannot consult [`Self::is_huge`], so they still issue the
/// syscall — which the OS then refuses or ignores. Same observable
/// outcome, different mechanism; do not read "no-op" as "no syscall"
/// for the free functions.
/// - **Huge reservations, eager `decommit`/`try_decommit`, Linux/Android
/// kernel >= 5.18, range aligned to the huge page size (2 MiB) at both
/// endpoints (task #1140):** the one huge-page case where decommit
/// actually works — pages ARE zeroed on next access via
/// `MADV_DONTNEED`. Both layers issue the real syscall here and agree.
/// See [`Self::decommit`]'s own doc for the exact eligibility rule.
///
/// - **Lazy reservations on Windows (feature `lazy-commit`):** When created
/// via `reserve_aligned_lazy`, only the `initial_commit` prefix is
/// committed at reservation time. The tail `[initial_commit, len())` must
/// be committed via `commit_range` before it becomes writable. Writing
/// to the uncommitted tail results in an access violation.
///
/// Returns `*mut u8` (rather than the std convention of `*const T` from
/// `&self`) because a raw pointer carries no borrow obligation in this
/// crate's model, and the span is exclusively owned by this `Reservation`
/// handle. The mutability reflects ownership, not mutability of the
/// borrow itself.
#[must_use]
#[inline]
pub fn as_ptr(&self) -> *mut u8 {
self.base.as_ptr()
}
/// The number of usable bytes at [`as_ptr`](Self::as_ptr).
#[must_use]
#[inline]
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> usize {
self.len
}
/// The start of the underlying OS reservation (may sit below
/// [`as_ptr`](Self::as_ptr) because the reservation is over-reserved
/// to achieve alignment and the full mapping is kept).
#[must_use]
#[inline]
pub fn reservation_ptr(&self) -> *mut u8 {
self.reservation.as_ptr()
}
/// The **requested/logical** span length of this reservation.
///
/// **This value is NOT necessarily the actual OS reservation size** — at least
/// three paths under-report the true VA span the OS mapped:
///
/// - **Windows single-call fast path** (`align <= 64 KiB`): this returns
/// `commit_len` (which equals `size`), not the rounded-up VA reservation
/// size. Windows rounds VA reservations up to the 64 KiB allocation
/// granularity internally, so `reserve_aligned(4096, 4096)` reports
/// `reservation_len() == 4096` while actually consuming 64 KiB of address
/// space.
/// - **Windows two-call path's fast-reserve sub-path** (`align <= 64 KiB`
/// via `reserve_aligned_lazy`): when the candidate `VirtualAlloc(NULL,
/// size, MEM_RESERVE)` happens to be aligned, this returns `size` directly,
/// not the rounded-up 64 KiB granularity. The underlying reservation still
/// consumes a 64 KiB-granular region.
/// - **Any page-rounding `mmap` where the OS page size exceeds the requested
/// granularity** — e.g. Apple-Silicon macOS's 16 KiB pages, or 64 KiB on
/// some Linux configurations (see [`MIN_PAGE`](crate::min_page::MIN_PAGE)'s doc above): `mmap` rounds
/// `length` up to the page size, so `reserve_aligned(PAGE, PAGE)` on a 16
/// KiB-page host actually maps a full 16 KiB page while this returns
/// `4096`.
///
/// Both cases are harmless for correctness (`VirtualFree(base, 0,
/// MEM_RELEASE)` ignores the length argument; `munmap` rounds its length
/// argument up to the page size the same way `mmap` did, so `release`
/// still unmaps the whole underlying mapping) — but the return value is
/// not a portable measure of the true reservation size.
#[must_use]
#[inline]
pub const fn reservation_len(&self) -> usize {
self.reservation_len
}
// Historical note (task #848, #921): the Windows single-call fast path
// (align <= WIN_ALLOCATION_GRANULARITY, typically 64 KiB; widens to
// GetLargePageMinimum() when requesting large pages) and the two-call
// path's fast-reserve sub-path (align <= WIN_ALLOCATION_GRANULARITY
// via reserve_aligned_lazy) are the primary under-report cases for
// this method; the page-rounding mmap case is the third. These are
// documented in the method's rustdoc above without task-number references.
/// The alignment requested at reservation time.
#[must_use]
#[inline]
pub const fn align(&self) -> usize {
self.align
}
/// Whether OS large/huge pages were actually granted for this reservation.
///
/// Returns `true` if the reservation successfully obtained large/huge pages
/// from the OS (Linux `MAP_HUGETLB` or Windows `MEM_LARGE_PAGES`), and `false`
/// if it fell back to ordinary pages or was not a huge-page request.
///
/// This is the "best-effort" observable: a caller using `reserve_aligned_huge`
/// can now detect whether the huge-page feature actually engaged, rather than
/// receiving only an indistinguishable `Ok(Reservation)` on every fallback.
///
/// **Windows limitation (task #848 single-call fast path):** on Windows,
/// this returns `true` only when ALL of the following hold:
/// 1. The fast-path condition `align <= GetLargePageMinimum()` is satisfied
/// (typically `align <= 2 MiB` on x86_64)
/// 2. `size` is a multiple of the system's large-page minimum
/// 3. The calling process has `SeLockMemoryPrivilege` granted AND has
/// **enabled** it via `AdjustTokenPrivileges` (the crate does not do
/// this for you — a process with the privilege granted but not
/// enabled fails exactly like an unprivileged one and silently falls
/// back to ordinary pages)
///
/// NOTE: The widened fast-path condition (II-3, 2026-08-16 audit finding) expanded
/// the single-call ATTEMPT window from `align <= 64 KiB` to `align <= GetLargePageMinimum()`,
/// but on an unprivileged host the actual paths that SUCCEED (pass the post-call alignment
/// check) are typically still limited. When large pages are NOT granted (unprivileged),
/// `VirtualAlloc`'s alignment guarantee is only 64 KiB; in practice it typically does NOT
/// happen to land on the requested alignment, so the post-call check fails and the fast
/// path falls through to the two-call path. Practically, this means `is_huge() == true` only
/// for shapes where large pages are actually granted, which requires all three conditions
/// above to hold.
///
/// If any of these conditions fail, the function falls back to ordinary pages
/// and this flag is `false`. On Windows, large pages (`MEM_LARGE_PAGES`)
/// are only ever requested and possibly granted via the single-call fast path;
/// the two-call path never requests large pages, so
/// `is_huge()` is always `false` for a reservation that takes it. See
/// [`reserve_aligned_huge`](crate::api::reserve_aligned_huge)'s rustdoc for details.
///
/// **Note:** reservations adopted via [`from_raw_parts`](Self::from_raw_parts)
/// report whatever `granted_huge` value the caller passed to that constructor,
/// which the caller is responsible for getting right (see that constructor's
/// `# Safety` section).
///
/// **This method has no `huge-pages` feature gate — [`Self::decommit`]'s
/// eligible-forward behavior does (task #1156, finding F10).** `is_huge()`
/// reports `true`/`false` identically regardless of which features are
/// enabled; whether a `true` result also gets you a real Linux/Android
/// kernel >= 5.18 decommit forward instead of a guaranteed skip depends
/// on the `huge-pages` feature being enabled too. See [`Self::decommit`]'s
/// doc for the full explanation — this asymmetry matters most for
/// [`from_raw_parts`](Self::from_raw_parts) callers, since that
/// constructor is also unconditionally compiled.
#[must_use]
#[inline]
pub const fn is_huge(&self) -> bool {
self.granted_huge
}
/// Returns `true` if the current platform's **ordinary native backend** guarantees
/// that eager [`Self::decommit`] returns physical backing to the OS and zero-fills
/// on next access, `false` otherwise.
///
/// **Scope:** this is a platform-level query about the ordinary native backend's
/// contract. It does NOT apply to:
/// - **huge-page reservations** (those with [`Self::is_huge`] == `true`) — eligibility
/// there depends on the platform, the requested range, and (on Linux/Android) the
/// running kernel, not on a single platform-wide answer: on Windows decommit is a
/// guaranteed no-op; on Linux/Android with kernel >= 5.18, a huge-page-size-aligned
/// range CAN actually decommit (see the free [`decommit`] function's "Huge-page
/// granularity" rustdoc section for the exact split, and
/// [`Self::can_decommit_reclaim_and_zero`]'s own huge-page bullet for the
/// conservative instance-level answer this platform-level query cannot give).
/// - **miri** — under miri, the backend is a no-op that doesn't model RSS or reclaim.
/// - **the `aligned_vmem_mock` cfg** (`RUSTFLAGS="--cfg aligned_vmem_mock"`) — the
/// recording mock backend's decommit logs the call WITHOUT touching the OS, so it
/// reclaims nothing and zeroes nothing (task #1066). Excluded for the same reason
/// the sibling capability query `lazy_commit_is_honored()` (feature `lazy-commit`)
/// excludes it: this family answers for the backend actually linked into the
/// compilation, and the miri bullet above is already that same substituted-backend
/// category rather than a platform property.
///
/// For an instance-level query that accounts for huge pages, use
/// [`Self::can_decommit_reclaim_and_zero`].
///
/// Platform behavior (ordinary native backend only, eager decommit path):
/// - **Linux (all targets)**: returns `true`. `MADV_DONTNEED` unmaps physical pages
/// and re-faults fresh zero pages on next access.
/// - **Windows**: returns `true`. `MEM_DECOMMIT` unmaps physical pages and
/// re-faults fresh zero pages on next access.
/// - **Darwin family (macOS/iOS/tvOS/watchOS)**: returns `false`. `MADV_DONTNEED`
/// is advisory-only for anonymous memory and does not reliably unmap/zero pages.
/// A decommit+recommit roundtrip can observe old data still resident.
/// - **BSD family (FreeBSD/DragonFly/NetBSD/OpenBSD)**: returns `false`. Same
/// advisory-only caveat as Darwin for eager decommit. (Note: lazy decommit
/// via [`decommit_lazy`] DOES reclaim on BSD via `MADV_FREE`, even though
/// eager decommit does not.)
///
/// This is a compile-time constant per platform: the return value is the same
/// for all calls within a single compilation unit, determined by the target
/// OS triple, whether miri is active, and whether the `aligned_vmem_mock` recording
/// backend is compiled in. It provides programmatic access to the
/// platform-specific guarantee that [`Self::decommit`]'s rustdoc describes in prose.
#[must_use]
#[inline]
pub const fn decommit_reclaims_and_zeroes() -> bool {
cfg!(not(any(
target_os = "macos",
target_os = "ios",
target_os = "tvos",
target_os = "watchos",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "netbsd",
target_os = "openbsd",
miri,
aligned_vmem_mock
)))
}
/// Returns `true` if eager [`Self::decommit`] on **this specific reservation**
/// guarantees reclaim+zero-fill semantics, `false` otherwise.
///
/// This is an **advisory** capability query. It is computed from
/// compile-time platform capability and the reservation's huge-page status only,
/// and does **not** issue any runtime syscall or observe whether a prior `decommit`
/// call actually succeeded. Specifically:
///
/// - On Linux/Windows (native — not miri, not the `aligned_vmem_mock` cfg), `true` means the platform guarantees that
/// `decommit` will return physical backing and zero-fill on next access via
/// `MADV_DONTNEED` / `MEM_DECOMMIT`. Backend syscall failures (e.g. rare kernel
/// failures) are silently discarded and not reflected in this query's return value.
/// - On Darwin/BSDs, under miri, or under the `aligned_vmem_mock` cfg, `false` means
/// decommit is advisory-only (Darwin/BSDs) or a recorded no-op (miri, mock) with no
/// reclaim or zero-fill guarantee.
/// - On huge-page reservations, `false` — **this bool is CONSERVATIVE and is NOT
/// range-aware** (task #1140): on Windows it is unconditionally correct (large-page
/// decommit never works there). On Linux/Android with kernel >= 5.18, it
/// UNDER-reports: [`Self::decommit`]/[`Self::try_decommit`] DO issue a real
/// `MADV_DONTNEED` for a `[start, end)` range that is itself huge-page-size-aligned
/// (2 MiB) at both endpoints — see those methods' own doc comments — but this
/// instance-level query has no `start`/`end` parameters to judge that per-call, so it
/// answers `false` for EVERY range on a huge reservation, including the ranges that
/// actually do work. Call [`Self::try_decommit`] directly and judge by its
/// [`DecommitOutcome`] return value (task #1180: `Skipped` vs `Advised` vs
/// `Refused` — `Self::decommit` itself stays `()`/infallible and carries no
/// such signal) / the `bench-internals`
/// `huge_decommit_attempts` counter (not an intra-doc link: `bench-internals` is excluded from the published docs.rs feature set)
/// if you need to distinguish "this exact range worked" from "this bool said no."
///
/// A `true` return is therefore a statement about the **platform and reservation type**,
/// not a guarantee that a specific `decommit` call actually released memory or zeroed
/// pages — OS errors in that path are unobservable through this API by design
/// (the same contract as the infallible `decommit` method itself). A `false` return is
/// similarly not a guarantee that no range on this reservation can ever be decommitted
/// (see the huge-page bullet above).
///
/// This query combines:
/// - the platform-level guarantee (see [`Self::decommit_reclaims_and_zeroes`]), and
/// - the reservation's huge-page status (via [`Self::is_huge`]).
///
/// Returns `false` if EITHER condition fails:
/// - the platform doesn't guarantee reclaim+zero-fill (Darwin/BSDs, miri, or the
/// `aligned_vmem_mock` cfg), or
/// - this reservation uses huge pages — **conservatively**: on Windows this is
/// always correct (huge-page decommit is a genuine no-op there), but on
/// Linux/Android >= 5.18 a huge-page-size-aligned range CAN actually
/// decommit (see [`Self::decommit`]'s doc and the bullet on this fact
/// above); this bool has no range to judge, so it answers `false`
/// unconditionally for a huge reservation regardless of platform.
///
/// Use this when you have an actual `Reservation` and need to know whether decommit
/// will work on it **for an ordinary (non-huge) reservation, or to conservatively rule
/// out a huge one**. Use the associated function [`Self::decommit_reclaims_and_zeroes`]
/// when you only care about platform capability without a reservation instance. For a
/// huge reservation on Linux/Android, this bool cannot tell you whether a SPECIFIC
/// `[start, end)` will work — call [`Self::try_decommit`] and judge by its
/// [`DecommitOutcome`] instead (see the huge-page bullet above).
///
/// # Example
///
/// Ordinary reservation: decommit works on Linux/Windows (except miri):
/// ```text
/// let ordinary = reserve_aligned(1024 * 1024, 4096).expect("reserve");
/// // On Linux/Windows (native): ordinary.can_decommit_reclaim_and_zero() == true
/// // On Darwin/BSD, under miri, or under `aligned_vmem_mock`:
/// // ordinary.can_decommit_reclaim_and_zero() == false
/// ```
///
/// Huge-page reservation: this bool is always false, but on Linux/Android
/// >= 5.18 that does NOT mean `decommit` itself is a no-op for every range:
/// ```text
/// let huge = reserve_aligned_huge(2 * 1024 * 1024, 2 * 1024 * 1024);
/// if let Some(ref reservation) = huge {
/// if reservation.is_huge() {
/// // The bool is always false, regardless of platform — conservative,
/// // not "decommit never works" (see the doc above this example).
/// assert!(!reservation.can_decommit_reclaim_and_zero());
/// }
/// }
/// ```
/// NOTE: On Linux/Android with the `huge-pages` feature enabled, the
/// arguments must be multiples of the huge page size (2 MiB); the example
/// above uses 2 MiB for both size and align to avoid rejection. On other
/// platforms, the function is a best-effort no-op and any size/align will
/// succeed (falling back to ordinary pages).
///
/// See the tests in `tests/decommit_capability.rs` for runnable coverage of both cases.
#[must_use]
#[inline]
pub fn can_decommit_reclaim_and_zero(&self) -> bool {
Self::decommit_reclaims_and_zeroes() && !self.is_huge()
}
/// Consume the handle WITHOUT releasing the OS reservation, returning the
/// `(reservation_ptr, reservation_len, align)` the caller must later hand to
/// [`release`](crate::api::release) exactly once. Use this when your allocator records the
/// reservation in its own self-hosted metadata instead of relying on
/// `Drop`.
///
/// `align` is the alignment originally requested; the native release paths
/// ignore it, but it is required for the miri fallback to reconstruct the
/// exact `Layout`. A self-hosting allocator that always uses one alignment
/// can pass that constant to [`release`](crate::api::release) instead of storing this value.
///
/// **Warning:** This method returns a raw tuple. Consider using
/// [`into_reservation_parts`](Self::into_reservation_parts) instead, which
/// returns a named struct that prevents accidentally swapping `len` and `align`.
#[must_use]
pub fn into_parts(self) -> (*mut u8, usize, usize) {
let parts = (self.reservation.as_ptr(), self.reservation_len, self.align);
core::mem::forget(self);
parts
}
/// Consume the handle WITHOUT releasing the OS reservation, returning the
/// [`ReservationParts`] struct the caller must later hand to [`release_parts`](crate::api::release_parts)
/// exactly once. Use this when your allocator records the reservation in its
/// own self-hosted metadata instead of relying on `Drop`.
///
/// This method is the typed, named alternative to [`into_parts`](Self::into_parts);
/// it prevents the footgun of accidentally swapping `len` and `align`, which
/// would be undefined behavior on the native backend and cause leaks or crashes
/// on the Unix backend.
///
/// **WARNING:** This method discards `base`, `len`, and `granted_huge`. To
/// reconstruct a full `Reservation` via [`from_raw_parts`](Self::from_raw_parts),
/// you MUST preserve these three fields separately alongside the returned
/// `ReservationParts`. If you omit `granted_huge`, the reconstructed reservation
/// will incorrectly report `is_huge() == false` even if the original used huge
/// pages, which can lead to incorrect decommit-availability decisions.
///
/// For backwards compatibility with code that already uses the tuple form,
/// you can call [`ReservationParts::as_tuple`] to get a raw tuple.
///
/// No message-less `#[must_use]` on this function itself (task
/// #1213/L3): the return type [`ReservationParts`] now carries its own
/// `#[must_use]` with a leak-specific message (dropping it leaks the
/// reservation), which already fires for every caller of every function
/// returning it, this one included — clippy's `double_must_use` lint
/// flags a redundant message-less attribute stacked on top of that.
pub fn into_reservation_parts(self) -> ReservationParts {
let parts = ReservationParts {
ptr: self.reservation.as_ptr(),
len: self.reservation_len,
align: self.align,
};
// Same suppression as `into_parts` -- without this, `self` would run
// its normal `Drop` (which now also releases the OS reservation) at
// the end of this function, and the returned `ReservationParts`
// would describe already-freed memory: a guaranteed double-free the
// moment the caller follows this method's own contract and passes
// it to `release_parts`.
core::mem::forget(self);
parts
}
/// Consume the handle WITHOUT releasing the OS reservation, returning a
/// full [`ReservationFullParts`] struct containing all six fields needed to
/// reconstruct the original `Reservation` via [`from_raw_parts`](Self::from_raw_parts).
///
/// This is the lossless round-trip alternative to [`into_reservation_parts`](Self::into_reservation_parts):
/// it preserves `base`, `len`, and `granted_huge` in addition to the underlying
/// reservation metadata, eliminating the risk of silent huge-page status loss
/// or usable-span information loss.
///
/// Use this when you need to temporarily extract all reservation state for
/// later reconstruction, such as in a custom allocator that hands off
/// reservations between components within the same process.
///
/// **IMPORTANT:** `ReservationFullParts` is a plain struct with no `Drop`
/// implementation — dropping or forgetting it does NOT release the underlying
/// OS reservation. The reservation will leak until you reconstruct it via
/// `into_reservation()` and drop the resulting `Reservation`, or release it
/// manually via [`release`](crate::api::release) (using the `reservation`, `reservation_len`, and
/// `align` fields from `ReservationFullParts`). If you only need manual
/// release and don't require preserving `base`, `len`, and `granted_huge`,
/// prefer [`into_reservation_parts`](Self::into_reservation_parts) instead,
/// which provides the `release_parts` function.
///
/// No message-less `#[must_use]` on this function itself (task
/// #1213/L3): the return type [`ReservationFullParts`] now carries its
/// own `#[must_use]` with a leak-specific message, for the same reason
/// as [`into_reservation_parts`](Self::into_reservation_parts) above.
pub fn into_full_parts(self) -> ReservationFullParts {
let parts = ReservationFullParts {
base: self.base.as_ptr(),
len: self.len,
reservation: self.reservation.as_ptr(),
reservation_len: self.reservation_len,
align: self.align,
granted_huge: self.granted_huge,
};
core::mem::forget(self);
parts
}
/// Decommit pages `[start, end)` within this reservation.
///
/// This is the safe, bounds-checked alternative to the free [`decommit`]
/// function for callers already holding a `Reservation`. It delegates to
/// the underlying implementation with `self.as_ptr()` as base and
/// automatically ensures `[start, end)` is within the reservation's usable span.
///
/// Takes `&mut self` (task #1113): OS-state mutation requires exclusive
/// access, so a shared `&Reservation` can reach none of the seven state
/// mutators. This is what structurally seals the `LazyReservation`
/// watermark (finding H1, task #1104): a leaked `&Reservation` is now
/// read-only by construction, not by policing.
///
/// **Programmatically check platform guarantees:** use
/// [`Self::decommit_reclaims_and_zeroes`] to query whether the current
/// platform guarantees reclaim+zero-fill semantics.
///
/// Hint the OS to return the physical backing of `[start, end)` while keeping the
/// address-space reservation alive. On Linux and Windows this is guaranteed to
/// return physical backing and zero-fill on next access (Linux `MADV_DONTNEED`,
/// Windows `MEM_DECOMMIT`). On the Darwin family (macOS/iOS/tvOS/watchOS) and the
/// four BSDs (FreeBSD/DragonFly/NetBSD/OpenBSD), this is a best-effort hint with no
/// zero-fill or reclaim guarantee — the physical pages may remain resident and
/// old data may be observed after a decommit+recommit roundtrip.
///
/// `start` and `end` must be multiples of the runtime page size ([`page_size()`](crate::page_size::page_size)).
/// A no-op if the range is out of bounds (`end > self.len()`); an empty
/// range is a no-op only when page-ALIGNED — an empty MISALIGNED range
/// (`start == end`, endpoints not page multiples, e.g. `decommit(1, 1)`)
/// is a contract violation like any other, with the SAME profile split
/// as every other violated range: a silent no-op in a RELEASE build
/// (the forwarded free function returns at `start >= end` once the
/// `debug_assert!` is compiled out) and a tripwire panic in a DEBUG
/// build — EXCEPT on a huge-page reservation, where a NON-huge-aligned
/// (or inverted) range never reaches the forward at all, so it is a
/// silent no-op on EVERY profile there and the debug tripwire never
/// fires (see `# Panics`; task #1084/M2 wrote the split into `# Panics`,
/// task #1097/L4 qualified this summary line to match, task #1108 added
/// the huge exception that the paragraph below and `# Panics` both
/// already stated but this sentence did not; task #1140 narrowed the
/// huge exception to "non-huge-aligned or inverted" — see below).
///
/// **Contract violations, by build profile (task #1051, narrowed task
/// #1140):** this method forwards to the free [`decommit`] function
/// UNFILTERED whenever it forwards at all, so a violated range
/// (`start > end`, or an endpoint not a multiple of
/// [`page_size()`](crate::page_size::page_size)) follows that function's
/// documented profile split exactly on a NON-huge reservation — a silent
/// no-op in a RELEASE build (no OS call, nothing recorded), a tripwire
/// panic in a DEBUG build. On a HUGE-page reservation
/// ([`Self::is_huge`] == `true`), whether this method forwards at all
/// now depends on the range (task #1140, Linux/Android kernel >= 5.18
/// only): a WELL-FORMED range that is ALSO aligned to the huge page size
/// (2 MiB) at both endpoints forwards to the real backend exactly like a
/// non-huge reservation would (and can therefore reach that same debug
/// tripwire, only for a range that manages to be simultaneously
/// huge-aligned AND page-size-misaligned — impossible in practice since
/// 2 MiB is already page-size-aligned on every supported page size, so
/// this case cannot actually occur); every OTHER range on a huge
/// reservation (not huge-aligned, or `start > end`) never reaches the
/// forward and is a silent no-op on every profile (see `# Panics`).
/// [`Self::try_decommit`] is the fallible form: it reports a violated
/// range as `Err` on every profile — including huge reservations (task
/// #1084/M3) — and never trips the tripwire.
///
/// See [`decommit`] for platform divergence notes (Windows crashes on write
/// before recommit, Linux and Android do not), huge-page incompatibility,
/// and Darwin zero-fill caveats. Under the `bench-internals` feature, the
/// `huge_decommit_attempts` counter (not an intra-doc link: `bench-internals` is excluded from the published docs.rs feature set) is incremented when decommit
/// is called on a huge-page reservation with a range that is NOT eligible
/// for the Linux/Android >= 5.18 huge-aligned real-call path (i.e. the
/// counter tracks calls that hit the silent-no-op path, not every call on
/// a huge reservation — task #1140 narrowed this from "every huge-reservation
/// call" to "every huge-reservation call that is actually skipped").
///
/// **The Linux/Android kernel >= 5.18 eligible-forward path itself
/// requires the `huge-pages` feature (task #1156, finding F10) —
/// [`Self::is_huge`] does NOT.** The eligibility check this method
/// consults (`linux_huge_range_is_madvise_eligible`) is compiled only
/// under `#[cfg(all(not(miri), not(aligned_vmem_mock),
/// any(target_os = "linux", target_os = "android"),
/// feature = "huge-pages"))]`; without that
/// feature enabled, EVERY range on a huge-flagged reservation takes the
/// silent-no-op early-exit above, unconditionally, on every platform —
/// there is no huge-aligned-range exception without the feature.
/// **[`Self::from_raw_parts`] no longer creates a mismatch here (task
/// #1172/M1-hybrid, closing finding M2):** that constructor now
/// `assert!`s that `granted_huge: true` requires the `huge-pages` feature
/// to be enabled in THIS crate, so a reservation with `is_huge() == true`
/// cannot exist without `huge-pages` — the scenario this paragraph used
/// to describe (an adopted `MAP_HUGETLB` reservation reporting
/// `is_huge() == true` while `decommit`/`try_decommit` silently and
/// permanently skip the backend regardless of range or kernel version,
/// because the CONSUMER's Cargo feature set diverged from what the flag
/// promised) can no longer arise: without `huge-pages`, adopting such a
/// reservation panics at construction instead of silently under-serving
/// it later. **[`decommit_lazy`] is NOT a
/// workaround** (task #1172, correcting the advice this paragraph used
/// to give): [`Self::decommit_lazy`] skips its backend call
/// UNCONDITIONALLY for every huge-flagged reservation, on every
/// platform, regardless of feature flags or kernel version — it has no
/// Linux >= 5.18 huge-aligned carve-out at all (see its own doc). Routing
/// around a `huge-pages`-gated no-op into a permanent no-op is not a
/// substitute. If you cannot enable `huge-pages`, either accept the
/// no-op (RSS will not drop for this reservation) or track the huge-page
/// state yourself and avoid relying on either decommit path for it.
///
/// # Panics
///
/// DEBUG builds only, and only for a contract-violating range (`start >
/// end`, or an endpoint not a multiple of the runtime
/// [`page_size()`](crate::page_size::page_size)) that actually reaches the
/// forwarded free [`decommit`]: unconditionally true on a NON-huge
/// reservation, or — since task #1140 — on a huge reservation whenever the
/// range happens to be huge-page-size-aligned at both endpoints (in
/// practice this can only be a WELL-FORMED range, since a huge-page-size
/// multiple is always also a `page_size()` multiple, so the tripwire is
/// not actually reachable through the huge-aligned path — this bullet
/// exists to be precise about the forwarding rule, not because a real
/// input triggers it). That includes an EMPTY MISALIGNED range such as
/// `decommit(1, 1)` — emptiness is NOT a pre-check (task #1084, finding
/// M2, rewrote this section, which previously claimed "empty and
/// out-of-bounds ranges are checked by this method first and never
/// panic"; only the out-of-bounds half of that sentence was true). The
/// two classes that never panic on any profile: out-of-bounds
/// (`end > self.len()`), the one range class this method itself
/// pre-checks, and an empty PAGE-ALIGNED range (`start == end`, both
/// endpoints multiples of `page_size()`), which forwards as
/// well-formed. On a huge-page reservation ([`Self::is_huge`] == `true`)
/// a range that is NOT huge-page-size-aligned at both endpoints (or is
/// inverted, `start > end`) never reaches the tripwire: it is a silent
/// no-op there on every profile, same as before task #1140. RELEASE
/// builds silently skip a violated range regardless of huge-page status.
/// This is the free function's own documented panic surface reached
/// through the safe method, not a new one (task #1079 added this
/// `# Panics` section to a doc that previously promised "the same
/// silent-skip behavior as the free `decommit` function" with no profile
/// qualifier; task #1084 corrected its empty-range claim; task #1140
/// narrowed the huge-page exception). **A poisoned page-size query is
/// NOT a panic source, on any profile** (task #1173/L1) — see the free
/// [`decommit`]'s own "Contract violations, by build profile" section
/// for why that state is a silent no-op unconditionally, unlike the
/// range-contract tripwire this section describes.
pub fn decommit(&mut self, start: usize, end: usize) {
// Bounds check: the range must be within the reservation's usable span.
if end > self.len() {
return;
}
// Huge-page reservations (finding R6-7, revised task #1140): on Windows,
// decommit NEVER works — `VirtualFree(MEM_DECOMMIT)` unconditionally fails
// on a large-page region, full stop, so the backend call is skipped
// unconditionally there. On Linux/Android, that used to be believed true
// unconditionally too, but it is not: Linux 5.18+ added `MADV_DONTNEED`
// support for HugeTLB mappings, gated on the address/length both being
// aligned to the mapping's huge page size (`man 2 madvise`). This
// reservation's `base` is always huge-page-aligned by construction
// whenever `is_huge()` (see `linux_huge_range_is_madvise_eligible`'s own
// doc), so only `[start, end)` needs checking. See `try_decommit`'s doc
// for why an eligible-but-malformed range is still handled by validation,
// not by this eligibility check.
//
// The `if` itself is UNCONDITIONAL and only the diagnostic increment is
// feature-gated. Putting the whole block (and therefore the `return`)
// behind `#[cfg(feature = "bench-internals")]` would confine the
// optimisation to diagnostic builds and leave the useless syscall in
// every production build — the exact inverse of the point — while also
// making an observable behaviour (syscall issued or not) depend on a
// feature flag. Caught at review of task #1040's delegated diff, which
// had exactly that shape.
if self.is_huge() {
#[cfg(all(
not(miri),
not(aligned_vmem_mock),
any(target_os = "linux", target_os = "android"),
feature = "huge-pages"
))]
if crate::os::linux_huge_range_is_madvise_eligible(start, end) {
// SAFETY: `self.as_ptr()` is a valid reservation base, and
// we've just verified `[start, end)` is within `self.len()`;
// the free function's own contract is validated inside it.
// The huge-decommit-eligibility check above is this method's
// own addition on top of that contract.
unsafe { decommit(self.as_ptr(), start, end) };
return;
}
// Counts calls that hit this early-exit path; the increment is a
// single relaxed fetch_add and compiles out when the feature is off.
#[cfg(feature = "bench-internals")]
HUGE_DECOMMIT_ATTEMPTS.fetch_add(1, Ordering::Relaxed);
return;
}
// SAFETY: `self.as_ptr()` is a valid reservation base, and we've just
// verified `[start, end)` is within `self.len()`. The free function's
// own contract (multiples of page_size(), etc.) is validated inside it.
unsafe { decommit(self.as_ptr(), start, end) };
}
/// Fallible [`Self::decommit`]: `Ok(DecommitOutcome)` on a well-formed
/// range, `Err(VmemError::invalid_argument())` if the offsets violated
/// the contract (misaligned, `start > end`, or `end > self.len()`) — on
/// EVERY reservation kind, huge included (task #1084/M3: the huge-page
/// skip used to sit ahead of validation and answer `Ok(())` for a
/// malformed range on a huge reservation, disagreeing with both this
/// promise and the free [`try_decommit`](crate::try_decommit)'s validate-first order — that
/// ordering is unchanged by task #1180, only the `Ok` payload is new).
/// Never panics on any build profile: the violation is rejected here,
/// before the eager path's tripwire can see it.
///
/// **`Ok` payload, task #1180 (PUB-R2 phase 2):** before this task the
/// `Ok` case was a bare `Ok(())`, unable to distinguish "the range was
/// empty", "this is a huge-page reservation and the backend call was
/// skipped", "the backend was called and the OS refused it", and "the
/// backend was called and the OS accepted it" — all four collapsed into
/// the same signal. [`DecommitOutcome`] now names each case:
/// - [`DecommitOutcome::Skipped`] — an empty page-aligned range
/// (`start == end`), OR a well-formed non-empty range on a huge-page
/// reservation that does not reach the real backend (see the
/// "huge-page reservations" paragraph below for exactly which ranges
/// those are). No syscall was issued either way.
/// - [`DecommitOutcome::Advised`] — the SELECTED BACKEND accepted the
/// call — see that variant's own doc for the native-vs-mock-vs-miri
/// split. **Never a claim that physical pages were actually
/// reclaimed**, even on the native backend. Task #1174 (closed) added
/// `ci_hugetlb_real_pool_decommit_actually_zeroes_memory_on_reaccess`
/// (`tests/decommit_capability.rs`), hard-enabled in the
/// `aligned-vmem-hugetlb-real` CI job: it writes a non-zero pattern,
/// decommits an eligible huge-aligned range under a real `MAP_HUGETLB`
/// grant, and hard-asserts EVERY byte reads back zero — proving
/// zero-fill-on-readback for that one case. **What #1174 did NOT
/// prove and does not claim to: physical reclaim to the OS/hugetlb
/// pool.** That same CI job's own comments are explicit that
/// `HugePages_Free` (the kernel's pool-page-count) is logged only as
/// an OBSERVATION around the test, never a pass/fail gate, because it
/// is a kernel-global counter shared with the job's other concurrent
/// reservations and cannot be safely attributed to one test's own
/// `decommit()` call. So: zero-fill on readback is proven for the
/// real-HugeTLB/eligible-range case, on a Linux runner — the code
/// path itself is gated on Linux **and Android** as a pair (as every
/// huge-page mechanism in this crate is), so the Android half is
/// inherited from that shared `cfg`, not separately executed by any
/// CI job; physical page return to
/// the pool remains unmeasured. Do not conflate the two when reading
/// `Advised`.
/// - [`DecommitOutcome::Refused`] — the backend call was made and the
/// OS/kernel refused it (carries the captured [`VmemError`]).
///
/// This is the safe, bounds-checked alternative to the free [`try_decommit`](crate::try_decommit)
/// function for callers already holding a `Reservation` — and the form to
/// reach for when [`Self::decommit`]'s DEBUG-build tripwire is itself
/// unwelcome. Until task #1079 this was the one fallible pair with no
/// safe-method twin: `recommit`/`try_recommit` and `commit_range`/
/// `try_commit_range` already existed at both layers, and
/// [`Self::decommit`]'s forwarded tripwire message ("Use try_decommit
/// for the fallible form") pointed safe-API callers straight at an
/// `unsafe fn` with a raw-pointer signature.
///
/// **Huge-page reservations** — FOR A WELL-FORMED RANGE: on Windows, or
/// on a Linux/Android range that is NOT huge-page-size-aligned at both
/// endpoints, this method skips the backend call entirely, same as
/// [`Self::decommit`], incrementing the same `bench-internals`
/// `huge_decommit_attempts` counter (not an intra-doc link: `bench-internals` is excluded from the published docs.rs feature set) and returning
/// `Ok(DecommitOutcome::Skipped)`. On Linux/Android kernel >= 5.18 (task
/// #1140), a well-formed range that IS huge-page-size-aligned at both
/// endpoints instead forwards to the real backend (same as a non-huge
/// reservation) and returns whatever that call reports —
/// [`DecommitOutcome::Advised`] or [`DecommitOutcome::Refused`], never
/// `Err`, per the "best-effort" note below, but now backed by a real
/// attempt rather than a guaranteed skip. A malformed range is `Err` even
/// on a huge reservation: validation runs before the skip/forward
/// decision, so neither the counter nor the real backend ever sees a
/// malformed range (task #1084/M3).
///
/// Decommit is best-effort by nature; use
/// [`Self::decommit_reclaims_and_zeroes`] to learn what the platform
/// actually does. `Refused` is reported through the `Ok` payload, not as
/// an `Err` of the outer `Result` — see the free [`try_decommit`](crate::try_decommit)'s own
/// `# Errors` section for why the outer `Result` stays reserved for
/// caller-contract validity.
///
/// **The Linux/Android >= 5.18 eligible-forward path requires the
/// `huge-pages` feature (task #1156, finding F10); [`Self::is_huge`]
/// does not — it is a pure query, always compiled.** Without
/// `huge-pages` enabled, this method always takes the
/// skip-and-return-`Ok(DecommitOutcome::Skipped)` path on a huge-flagged
/// reservation, on every platform, regardless of range or kernel
/// version. **A huge-flagged reservation cannot even be constructed
/// without `huge-pages`, as of task #1172/M1-hybrid** —
/// [`Self::from_raw_parts`] now requires the feature to accept
/// `granted_huge: true` — see [`Self::decommit`]'s doc and
/// [`Self::from_raw_parts`]'s "Correctness contract" section for the
/// full explanation.
pub fn try_decommit(&mut self, start: usize, end: usize) -> Result<DecommitOutcome, VmemError> {
// Bounds check: the range must be within the reservation's usable span.
if end > self.len() {
return Err(VmemError::invalid_argument());
}
// Range-contract validation BEFORE the huge-page skip (task #1084,
// finding M3). The huge early-return below used to sit ahead of ALL
// validation, so on a reservation with `is_huge() == true` a
// malformed range — the exact input a caller uses the fallible form
// to detect — was answered `Ok(())`, contradicting both this
// method's own `Err` contract and the free `try_decommit`'s
// validate-first order. The three conditions mirror the free
// function's private `decommit_range_is_well_formed`
// (`api/decommit.rs`) — this is now the ONLY place in this method's
// call chain that reads `page_size_or_poison()` (task #1180/P2: the
// free `try_decommit` used to re-validate the same range a second
// time on the forwarded path; `dispatch_try_decommit` below takes an
// already-validated non-empty range and does no page-size read of
// its own) — so the two layers cannot drift apart silently —
// `method_try_decommit_reports_malformed_range_on_huge_flagged_
// reservation` and `method_try_decommit_reports_violations_and_
// never_panics` (tests/reservation_decommit_contract.rs) pin the
// agreement, on huge-flagged and ordinary reservations
// respectively.
let ps = page_size_or_poison();
// Failed OS page-size query: fail closed with the OS-side no-code
// error, BEFORE the huge skip and the range validation — mirroring
// the free `try_decommit` (NOT `invalid_argument`; the caller's
// arguments are not at fault). See `page_size`'s "If the one-time
// OS query fails" paragraph.
if ps == PAGE_SIZE_QUERY_FAILED {
return Err(VmemError::os_refusal_unknown_code());
}
if start > end || !start.is_multiple_of(ps) || !end.is_multiple_of(ps) {
return Err(VmemError::invalid_argument());
}
if start == end {
// Well-formed empty range: a deliberate no-op, on every
// reservation kind including huge — no backend call, no huge
// eligibility check, no counter increment.
return Ok(DecommitOutcome::Skipped);
}
// Huge-page reservations (finding R6-7, revised task #1140): a
// WELL-FORMED range that reaches this point (validated above) is still
// not guaranteed to actually decommit anything — see `Self::decommit`'s
// doc comment for the Windows-vs-Linux/Android split this mirrors.
// Whether the backend call is skipped (no-op) or actually issued
// (Linux/Android 5.18+, huge-aligned range), this method's `Err`
// contract is "the range was well-formed", not "the OS actually
// reclaimed anything" — the free `try_decommit` deliberately does not
// report OS refusal/ignore as an `Err` either, and this decision
// applies equally to "the OS was never even asked" (Windows, or a
// page-size-but-not-huge-size-granular range on Linux/Android) and
// "the OS was asked and may have declined" (any ordinary
// reservation) — both now distinguishable through the `Ok` payload
// instead of being silently folded together.
if self.is_huge() {
#[cfg(all(
not(miri),
not(aligned_vmem_mock),
any(target_os = "linux", target_os = "android"),
feature = "huge-pages"
))]
if crate::os::linux_huge_range_is_madvise_eligible(start, end) {
// SAFETY: `self.as_ptr()` is a valid reservation base, and
// `[start, end)` was just validated as well-formed, non-empty,
// and in-span.
return Ok(unsafe { dispatch_try_decommit(self.as_ptr(), start, end) });
}
// Same reasoning and same cfg placement rule as `Self::decommit`
// above — the `if`/`return` are unconditional, only the counter is
// gated.
#[cfg(feature = "bench-internals")]
HUGE_DECOMMIT_ATTEMPTS.fetch_add(1, Ordering::Relaxed);
return Ok(DecommitOutcome::Skipped);
}
// SAFETY: `self.as_ptr()` is a valid reservation base, and we've just
// verified `[start, end)` is within `self.len()`, well-formed, and
// non-empty.
Ok(unsafe { dispatch_try_decommit(self.as_ptr(), start, end) })
}
/// Lazy decommit variant: hint the OS it MAY reclaim `[start, end)` under memory
/// pressure, cheaper than [`Self::decommit`] (Linux `MADV_FREE`, macOS/iOS
/// `MADV_FREE_REUSABLE`, FreeBSD/DragonFly `MADV_FREE`, NetBSD/OpenBSD
/// `MADV_FREE`, other Unix (including tvOS/watchOS) falls back to `MADV_DONTNEED`;
/// Windows falls back to the eager [`Self::decommit`] path, which has no lazy equivalent).
///
/// This is the safe, bounds-checked alternative to the free [`decommit_lazy`]
/// function for callers already holding a `Reservation`. It delegates to the
/// underlying implementation with `self.as_ptr()` as base and automatically
/// ensures `[start, end)` is within the reservation's usable span.
///
/// `start` and `end` must be multiples of the runtime page size
/// ([`page_size()`](crate::page_size::page_size)); an empty or
/// out-of-bounds (`end > self.len()`) range is a no-op, and a VIOLATED
/// range (`start > end`, or a misaligned endpoint) is a silent no-op on
/// EVERY build profile — the deliberate eager/lazy asymmetry settled by
/// task #1072: the eager [`Self::decommit`] trips a debug-build
/// tripwire, this lazy variant has none on any profile.
///
/// See [`decommit_lazy`] for the platform-specific cost inversion on macOS/iOS
/// (this variant actually drops RSS immediately there, unlike the eager path)
/// and other caveats. Under the `bench-internals` feature, the
/// `huge_decommit_attempts` counter (not an intra-doc link: `bench-internals` is excluded from the published docs.rs feature set) is incremented when decommit is called
/// on a huge-page reservation (same logic as `Self::decommit`).
pub fn decommit_lazy(&mut self, start: usize, end: usize) {
// Bounds check: the range must be within the reservation's usable span.
if end > self.len() {
return;
}
// Huge-page reservations: skip the backend call entirely (finding R6-7).
// Same reasoning, same cfg placement rule as `Self::decommit` above —
// the `if`/`return` are unconditional, only the counter is gated.
//
// Deliberately NOT extended to match `Self::decommit`'s task #1140
// Linux-5.18+ carve-out: `man 2 madvise` documents `MADV_DONTNEED`
// gaining HugeTLB support in 5.18, but says nothing of the kind for
// `MADV_FREE` (the backend `decommit_lazy` uses) — the lazy advice
// family is a different kernel code path with its own support
// history, and this crate does not assume one advice value's support
// change implies another's without a citation. Windows large pages
// remain a `MEM_DECOMMIT` no-op unconditionally either way (there is
// no lazy/eager split on Windows — see this method's own rustdoc).
if self.is_huge() {
#[cfg(feature = "bench-internals")]
HUGE_DECOMMIT_ATTEMPTS.fetch_add(1, Ordering::Relaxed);
return;
}
// SAFETY: same safety argument as `decommit` above.
unsafe { decommit_lazy(self.as_ptr(), start, end) };
}
/// Recommit pages `[start, end)` previously passed to [`Self::decommit`].
///
/// This is the safe, bounds-checked alternative to the free [`recommit`]
/// function for callers already holding a `Reservation`. It delegates to
/// the underlying implementation with `self.as_ptr()` as base and automatically
/// ensures `[start, end)` is within the reservation's usable span.
///
/// Returns `true` if the range is now committed (or the call was a well-formed
/// no-op — an empty PAGE-ALIGNED range, `start == end`), and `false` if the
/// OS refused to
/// commit the pages (commit-charge exhaustion / true OOM) OR the offsets
/// violated the contract below. On `false` the caller MUST NOT write into
/// `[start, end)`. Never panics. For the cause use [`Self::try_recommit`].
///
/// `start` and `end` must be multiples of the runtime page size ([`page_size()`](crate::page_size::page_size)).
/// A well-formed no-op (an empty PAGE-ALIGNED range, `start == end`)
/// returns `true`; any other contract violation (misaligned, or
/// `start > end`, or `end > self.len()`) returns `false`.
#[must_use]
pub fn recommit(&mut self, start: usize, end: usize) -> bool {
// Bounds check: the range must be within the reservation's usable span.
if end > self.len() {
return false;
}
// SAFETY: `self.as_ptr()` is a valid reservation base, and we've just
// verified `[start, end)` is within `self.len()`. The free function's
// own contract (multiples of page_size(), etc.) is validated inside it.
unsafe { recommit(self.as_ptr(), start, end) }
}
/// Fallible [`Self::recommit`]: `Ok(())` if the range is now committed
/// (or was a well-formed no-op), `Err(VmemError::invalid_argument())` if the
/// offsets violated the contract (misaligned, or `start > end`, or `end > self.len()`),
/// `Err(VmemError)` carrying the OS cause on genuine commit failure.
///
/// This is the safe, bounds-checked alternative to the free [`try_recommit`]
/// function for callers already holding a `Reservation`.
pub fn try_recommit(&mut self, start: usize, end: usize) -> Result<(), VmemError> {
// Bounds check: the range must be within the reservation's usable span.
if end > self.len() {
return Err(VmemError::invalid_argument());
}
// SAFETY: same safety argument as `recommit` above.
unsafe { try_recommit(self.as_ptr(), start, end) }
}
/// Commit pages `[start, end)` within this reservation.
///
/// This is the safe, bounds-checked alternative to the free [`commit_range`]
/// function for callers already holding a `Reservation`. It delegates to
/// the underlying implementation with `self.as_ptr()` as base and automatically
/// ensures `[start, end)` is within the reservation's usable span.
///
/// After a [`reserve_aligned_lazy`](crate::api::reserve_aligned_lazy) call that left some pages reserved-but-uncommitted,
/// `commit_range` commits exactly the requested sub-range so it becomes writable.
///
/// Returns `true` if the range is now committed, `false` if the OS refused
/// (commit-charge exhaustion / true OOM) OR the offsets violated the contract
/// above. On `false` the caller MUST NOT write into the range. Never panics.
/// For the cause use [`Self::try_commit_range`].
///
/// `start` and `end` must be multiples of the runtime page size ([`page_size()`](crate::page_size::page_size)).
/// A well-formed no-op (an empty PAGE-ALIGNED range, `start == end`)
/// returns `true`; any other contract violation (misaligned, or
/// `start > end`, or `end > self.len()`) returns `false`.
#[must_use]
#[cfg(feature = "lazy-commit")]
#[cfg_attr(docsrs, doc(cfg(feature = "lazy-commit")))]
pub fn commit_range(&mut self, start: usize, end: usize) -> bool {
// Bounds check: the range must be within the reservation's usable span.
if end > self.len() {
return false;
}
// SAFETY: same safety argument as `recommit` above.
unsafe { commit_range(self.as_ptr(), start, end) }
}
/// Fallible [`Self::commit_range`]: `Ok(())` on success (or was a well-formed no-op),
/// `Err(VmemError::invalid_argument())` if the offsets violated the contract
/// (misaligned, or `start > end`, or `end > self.len()`), `Err(VmemError)` carrying
/// the OS cause on genuine commit failure.
///
/// This is the safe, bounds-checked alternative to the free [`try_commit_range`]
/// function for callers already holding a `Reservation`.
#[cfg(feature = "lazy-commit")]
#[cfg_attr(docsrs, doc(cfg(feature = "lazy-commit")))]
pub fn try_commit_range(&mut self, start: usize, end: usize) -> Result<(), VmemError> {
// Bounds check: the range must be within the reservation's usable span.
if end > self.len() {
return Err(VmemError::invalid_argument());
}
// SAFETY: same safety argument as `recommit` above.
unsafe { try_commit_range(self.as_ptr(), start, end) }
}
/// Wrap a pre-existing OS reservation (e.g. one obtained from
/// `VirtualAllocExNuma` or another platform-specific allocator that
/// `reserve_aligned` does not call directly) in a [`Reservation`] handle.
///
/// The handle then participates in the normal RAII lifecycle: on `Drop`
/// (or via [`release`](crate::api::release)) the underlying reservation is returned to the OS
/// using the platform-appropriate release routine
/// (`VirtualFree(MEM_RELEASE)` on Windows, `munmap` on Unix,
/// `std::alloc::dealloc` on miri).
///
/// This is **not** the inverse of [`into_parts`](Self::into_parts): that
/// method returns only 3 of the 6 fields this constructor requires
/// (`reservation_ptr, reservation_len, align`), discarding `base`, `len`,
/// and `granted_huge` entirely. [`into_parts`](Self::into_parts)'s true structural complement
/// is [`release`](crate::api::release), whose signature is exactly the 3-tuple `into_parts`
/// returns — that is the intended matched pair for "take ownership out of
/// RAII, then give it back to the OS manually". `from_raw_parts` is a
/// separate, more general constructor for the cross-crate handoff pattern:
/// a sibling crate (`numa-shim` on Windows) issues a platform-specific
/// reservation call that `aligned-vmem` itself does not wrap, then adopts
/// the result via this constructor — it needs `base`/`len` too because the
/// adopted reservation's usable span need not start at the OS reservation's
/// own base (this crate over-reserves `size + align` and keeps the full
/// mapping whenever the exact-size fast path misses, or on Windows when
/// `align > 64 KiB`, which is exactly that shape).
///
/// # Safety
///
/// This section covers ONLY memory-safety preconditions: liveness,
/// exclusive ownership, pointer provenance, and exact-once release. A
/// violation here is undefined behaviour. Functional/behavioral
/// requirements — whether `granted_huge` accurately describes the
/// mapping, and Windows commit-state compatibility — are NOT memory-
/// safety preconditions and live in the "Correctness contract" section
/// below instead (task #1172/M3: this section used to mix both kinds
/// together, which made it impossible to state honestly that this
/// crate's own integration tests deliberately violate some of the
/// mixed-in conditions while remaining sound — see that section's
/// opening paragraph for why that is not a contradiction).
///
/// All six values must describe a **live, exclusively-owned OS
/// reservation** compatible with `aligned-vmem`'s release path:
///
/// - `base` is the *aligned usable* start; non-null, valid for `len` bytes,
/// aligned to `align`. For correct `decommit`/`decommit_lazy` behavior,
/// `base` must also be aligned to the runtime [`page_size()`](crate::page_size::page_size) (not just
/// the compile-time [`PAGE`]). On systems with non-4 KiB pages (e.g., 16 KiB on
/// Apple Silicon), passing a 4 KiB-aligned `base` will cause `decommit`,
/// `decommit_lazy`, or `munmap` calls to fail silently or return `EINVAL`.
/// **This alignment to page_size() is NOT checked by the constructor's
/// `assert!`** — it is the caller's responsibility to ensure it.
/// - `len` is the usable span size, a non-zero multiple of [`PAGE`].
/// - `reservation` is the *underlying OS reservation* start (often equal
/// to `base`, but may be lower because the reservation is over-reserved
/// to achieve alignment and the full mapping is kept). For correct OS
/// release behavior, it must be aligned to the runtime [`page_size()`](crate::page_size::page_size).
/// **This alignment to page_size() is NOT checked by the constructor's
/// `assert!`** — it is the caller's responsibility to ensure it.
/// - Under miri specifically, `reservation` — NOT `base` — MUST be the exact
/// pointer returned by a `std::alloc::alloc` call, and that call's `Layout`
/// must equal `Layout::from_size_align(reservation_len, align)`. The miri
/// `release_reservation` reconstructs precisely that `Layout` and hands
/// `reservation` to `std::alloc::dealloc`, which requires the pointer to be
/// the one `alloc` returned and the layout to match exactly; anything else
/// is undefined behaviour, not a leak.
///
/// The distinction between `reservation` and `base` is load-bearing here
/// and is why this bullet names one and not the other: they are SEPARATE
/// parameters, and `base` MAY sit at a non-zero offset inside the region
/// `reservation` points at whenever the caller obtained that region with
/// extra slack to satisfy alignment. Satisfying the provenance
/// requirement at `base` while `reservation` points somewhere else is
/// exactly the mistake this wording exists to prevent. (This crate's own
/// miri backend returns `base == reservation`, so the distinction never
/// bites internally — which is what makes it easy to get wrong for a
/// caller-supplied pair.)
///
/// This requirement is specific to the miri backend; the Windows and Unix
/// backends release by address and do not track allocator provenance.
/// It complements — and does not restate — the `reservation_len`
/// precision rule below: that one governs the SIZE, this one governs
/// WHICH POINTER and WHERE THE MEMORY CAME FROM.
/// - `reservation_len` must cover the underlying OS mapping/allocation.
/// The required PRECISION differs per backend, and is spelled out here
/// because the two halves of this rule used to contradict each other
/// (task #1035, finding F9: this bullet said an undersized value "leaks
/// memory (Unix)", while the "Important" note below said under-reporting
/// on a large-page host is "harmless for correctness" — both about Unix):
/// - **Native Unix, ORDINARY (non-huge) mapping:** `release` passes this
/// value straight to `munmap`, which ROUNDS THE LENGTH UP to a whole
/// page. A value short of the true mapping by less than one runtime
/// page therefore still unmaps the whole mapping and is harmless —
/// that is exactly the case the "Important" note below describes, and
/// it is the case this crate itself produces on a host whose page
/// size exceeds [`PAGE`]. What DOES leak is a value short by a whole
/// page or more: those trailing pages stay mapped for the life of the
/// process.
/// - **Native Unix, `granted_huge == true` (task #1172/M1, HugeTLB
/// exception to the paragraph above):** the "rounds up, so a
/// less-than-one-page shortfall is harmless" reasoning does NOT
/// transfer to a HugeTLB mapping. Linux's — and Android's, which
/// shares that kernel interface and is covered by the same `#[cfg]`
/// gate on the assert below — `mmap(2)` "Huge TLB
/// mappings" section requires BOTH `munmap(2)`'s `addr` and `length`
/// to be multiples of the huge page size — an undersized
/// `reservation_len` that is not huge-page-size-aligned gets `EINVAL`
/// from `munmap`, not a rounded-up unmap, and the ENTIRE mapping
/// (plus its pinned physical huge pages) leaks for the life of the
/// process. This crate's own `reserve_aligned_huge` path already
/// documents and upholds this requirement (see
/// `crates/aligned-vmem/src/os/unix.rs`'s huge-page-alignment
/// comments); `from_raw_parts` did not previously carry the same
/// requirement for an ADOPTED huge mapping. See the "2 MiB-multiple
/// requirement" bullet in "Correctness contract" below for the
/// checked form of this requirement.
/// - **miri:** `release` reconstructs a `Layout` from
/// `reservation_len`/`align` and hands it to `std::alloc::dealloc`,
/// which requires the EXACT size the allocation was made with — no
/// rounding, and a mismatch is undefined behaviour rather than a leak.
/// The rounding case above cannot arise here: under `cfg(miri)`
/// `query_os_page_size()` returns [`PAGE`] unconditionally, so
/// `page_size() == PAGE` and there is no larger runtime page to round
/// up to. The exact-size requirement is unqualified under miri.
/// - **Windows:** `VirtualFree(MEM_RELEASE)` ignores the length
/// entirely, so the value is advisory — reporting whatever
/// `Reservation::reservation_len` would report for an equivalent
/// reservation is sufficient.
///
/// **Important:** On hosts where the OS page size exceeds [`PAGE`]
/// (e.g., 16 KiB on Apple Silicon macOS, 64 KiB on some Linux
/// configurations), `reservation_len` may under-report the actual OS
/// mapping size — `mmap` rounds its length argument up to the page size,
/// so `reserve_aligned(PAGE, PAGE)` actually maps a full 16 KiB page
/// while `reservation_len()` returns `4096`. This is harmless for
/// correctness on an ORDINARY mapping (`munmap` rounds its length
/// argument up the same way; `VirtualFree(MEM_RELEASE)` ignores the
/// length on Windows) — it does NOT apply to a `granted_huge == true`
/// mapping on Linux or Android, per the HugeTLB bullet above — but it
/// means
/// `reservation_len` is a **logical** length, not a measure of the true
/// OS reservation size. It must be a non-zero multiple of [`PAGE`] with
/// `reservation_len >= len + (base - reservation)`.
/// - `align` is a power of two `>= PAGE` and matches the alignment the OS
/// reservation was created with.
/// - `granted_huge` itself carries NO memory-safety precondition: it is
/// stored and read back verbatim by every unsafe operation this
/// constructor, `Drop`, and `release_reservation` perform, and branched
/// on by neither. Its accuracy requirement, its interaction with
/// `reservation_len`'s HugeTLB exception above, and Windows commit-state
/// compatibility are all functional requirements — see "Correctness
/// contract" below.
///
/// The reservation must be released **exactly once** — by dropping this
/// handle, or by extracting via `into_parts` and calling [`release`](crate::api::release)
/// manually. Constructing two `Reservation` handles over the same OS
/// reservation is undefined behaviour (double release).
///
/// # Correctness contract
///
/// These requirements are NOT memory-safety preconditions — violating one
/// changes observable *behavior* (a query result, a dispatch decision, or
/// an OS-level no-op/leak) but never causes undefined behavior by itself.
/// This crate's own integration tests deliberately violate the
/// `granted_huge`-accuracy requirement below (see
/// `tests/reservation_decommit_contract.rs`'s
/// `method_try_decommit_reports_malformed_range_on_huge_flagged_reservation`
/// and `tests/decommit_capability.rs`'s
/// `simulated_huge_flag_drives_the_same_branch_dispatch_on_any_host`) to
/// exercise huge-page branch dispatch without a real hugetlb-configured
/// host — both tests' own SAFETY comments enumerate every reader of the
/// flag and confirm none is memory-safety-relevant. That is a deliberate,
/// reviewed use of this contract's slack, not a bug in either the tests
/// or this documentation; production callers must still pass a truthful
/// `granted_huge`, because the CONSEQUENCE of getting it wrong (below) is
/// real even though it is not UB.
///
/// - **`granted_huge` accuracy.** MUST accurately reflect whether the OS
/// actually granted huge pages for this reservation. Pass `true` only
/// if the reservation was obtained via a huge-page allocation (e.g.
/// `reserve_aligned_huge`) and the OS confirmed the grant (via
/// `Reservation::is_huge()` or equivalent platform-specific detection).
/// **Consequence of a wrong value:** `Reservation::is_huge()` reports
/// the wrong value, and any decommit-availability decision made from
/// that wrong result is wrong (on huge pages, `decommit` is a silent
/// no-op — RSS does not drop and reads return the old data). This
/// changes DISPATCH and query results, never memory safety. If you
/// cannot determine whether the OS granted huge pages, you MUST pass
/// `false` and use `reserve_aligned` instead. If you KNOW the
/// mapping is a HugeTLB mapping whose granularity is not 2 MiB
/// (e.g. 1 GiB on Linux or Android), NEITHER flag value is legal:
/// `true` violates the 2 MiB-multiple contract below, and `false`
/// does not make the kernel's `munmap` alignment requirement go
/// away — it only misreports `is_huge()` (violating this accuracy
/// bullet) and routes release through ordinary-munmap assumptions,
/// where `munmap(2)` on a `MAP_HUGETLB` mapping still requires
/// `addr` and `length` to be multiples of THAT mapping's huge-page
/// size, so a release whose shape satisfies 2 MiB but not the
/// mapping's real granularity can fail `EINVAL` and leak the entire
/// mapping, including its pinned pages from the (bounded) hugetlb
/// pool. Do not construct a `Reservation` over such a mapping at
/// all. "Can leak", not "will leak": this consequence is reasoned
/// from `man 2 munmap` and this crate's own `os/unix.rs` contract
/// docs (`unix_reserve`'s task-#714 note), not executed in CI — no
/// CI host configures a hugetlb pool larger than 2 MiB, and this
/// crate never creates such a mapping itself, always requesting
/// `MAP_HUGE_2MB` (`crates/aligned-vmem/src/os/unix.rs`).
///
/// - **2 MiB-multiple requirement, Linux/Android, `granted_huge == true`
/// (task #1172/M1-hybrid).** On `target_os = "linux"` or `"android"`,
/// when `granted_huge` is `true`, this constructor additionally
/// `assert!`s that `len`, `reservation_len`, `reservation`, `base`,
/// and the offset `base - reservation` are all multiples of 2 MiB
/// (this crate's one supported HugeTLB granularity, `MAP_HUGE_2MB`; see
/// `crates/aligned-vmem/src/os/unix.rs`'s `LINUX_HUGE_PAGE_SIZE`). Five
/// names are listed, but only FOUR are independent: `reservation` and
/// `base` both being 2-MiB multiples already implies their difference
/// `base - reservation` is too, so the offset conjunct can never be
/// the one that fails (task #1196/OX6-L1). It stays in the assert
/// anyway — for the panic message's diagnostics, and because it would
/// become load-bearing again if either address conjunct were ever
/// dropped.
/// **Consequence of a violation:** an immediate, loud, attributable
/// panic at the call site — not deferred to `Drop`, and not a silent
/// leak — because a non-2-MiB-aligned `reservation`/`reservation_len`
/// on a real HugeTLB mapping would otherwise make the eventual
/// `munmap` fail `EINVAL` and leak the entire mapping (see the
/// `reservation_len` bullet in `# Safety` above). This assert narrows
/// what `granted_huge == true` is allowed to MEAN through this
/// constructor to "the mapping is in this crate's own 2 MiB HugeTLB
/// format" — it does not by itself prove the memory is really
/// `MAP_HUGETLB`-backed (that remains a `# Safety` precondition the
/// assert cannot check), only that its shape is consistent with being
/// so. **Owner decision (2026-08-20, task #1190): NO.** A HugeTLB
/// mapping whose page granularity is not 2 MiB (e.g. 1 GiB) is NOT
/// supported for adoption through this constructor — this assert is
/// the crate's CURRENT contract, not a temporary narrowing pending a
/// wider one (the decision is recorded in
/// <https://github.com/PHPCraftdream/sefer-alloc/blob/main/docs/CORRECTNESS_OPEN_ITEMS.md>
/// item 90's OPEN QUESTION block). A
/// future "yes", if it ever comes, would arrive as an ADDITIVE new
/// constructor carrying typed huge-granularity metadata — not as a
/// relaxation of this assert presented as a bugfix. That widening
/// would be additive rather than semver-breaking precisely because
/// task #1172 already narrowed what `granted_huge == true` MEANS
/// here to "the mapping is in this crate's own 2 MiB HugeTLB
/// format", so this `bool` stays truthful forever for the one case
/// it admits, and because the adoption surface is structurally
/// extensible where it is public: `Reservation`'s fields are
/// private, and `ReservationParts`, `ReservationFullParts`, and
/// every `mock::Call` variant are `#[non_exhaustive]`.
///
/// - **`huge-pages` feature required to pass `granted_huge: true` (task
/// #1172/M1-hybrid, closing finding M2 as a consequence).** Passing
/// `granted_huge: true` when this crate is built WITHOUT the
/// `huge-pages` feature is itself a contract violation and `assert!`s
/// immediately, for the same "loud at the call site, not silently
/// divergent later" reason as the 2 MiB bullet above. Before this
/// requirement, a caller who adopted a `MAP_HUGETLB` mapping through
/// their own crate but did not separately enable `huge-pages` in THIS
/// crate's `Cargo.toml` got `is_huge() == true` (accurately reflecting
/// what they passed) while `decommit`/`try_decommit` silently,
/// unconditionally skipped the backend call regardless of range or
/// kernel version (finding M2: the SAME live mapping served or skipped
/// decommit depending on the CONSUMER's Cargo feature set, invisible at
/// the call site). Requiring the feature to accept the flag at all
/// means there is no longer a huge-flagged ADOPTED reservation without
/// `huge-pages` enabled, so the divergent-behavior scenario cannot
/// arise — see [`Self::decommit`]'s doc for the full eligible-forward
/// explanation this closes the gap in.
///
/// - **Windows commit state.** On Windows, the reservation's commit state
/// (which pages are committed vs. reserved-only) must be compatible
/// with the `granted_huge` value:
///
/// - If `granted_huge == false`, the reservation may be in any valid
/// commit state: fully committed (created via `reserve_aligned` or
/// the single-call Windows fast path), partially committed (created
/// via the two-call `reserve_aligned_lazy` path), or reserved-only
/// (not a common pattern but valid).
///
/// - If `granted_huge == true`, the reservation MUST have been created
/// with `MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES` in a single call
/// (the only way Windows grants large pages). The crate itself only
/// produces such reservations via its `reserve_aligned_huge`
/// single-call fast path. The crate's own two-call
/// `reserve_aligned_lazy` path (which issues
/// `VirtualAlloc(MEM_RESERVE)` followed by `VirtualAlloc(MEM_COMMIT)`)
/// is incompatible with `granted_huge == true`, because `MEM_COMMIT`
/// cannot be combined with `MEM_LARGE_PAGES` on a pre-reserved region
/// — MSDN requires all three flags in a single call.
///
/// **Consequence of a violation:** `Reservation::is_huge()` reports a
/// value inconsistent with the reservation's actual commit state —
/// the same DISPATCH/query-result consequence as the accuracy bullet
/// above, not a new failure mode. If you adopted a reservation from
/// another source and cannot determine whether it was created with the
/// one-call large-page path, you MUST pass `granted_huge == false`.
#[must_use]
pub unsafe fn from_raw_parts(
base: *mut u8,
len: usize,
reservation: *mut u8,
reservation_len: usize,
align: usize,
granted_huge: bool,
) -> Self {
// Historical notes (task #719, #776, #916):
//
// - task #719: validate the documented `align`/`reservation_len` contract
// HERE, at the unsafe call site, rather than leaving it to surface later
// as a panic inside `Drop::drop` (via the miri backend's
// `Layout::from_size_align(reservation_len, align).expect(...)` in
// `release_reservation`) -- a panic reachable from `Drop` is far more
// dangerous than one at construction time: if this `Reservation` is ever
// dropped while ANOTHER panic is already unwinding the stack, Rust
// aborts the whole process on the second panic. Every other construction
// path in this crate already produces a valid `(align, reservation_len)`
// pair by construction (validated at each public entry point), so this
// check is specific to the caller-supplied values `from_raw_parts`
// accepts. Violating the documented contract is already undefined
// behaviour per this function's own `# Safety` section; panicking
// immediately here converts a silently-deferred hazard into a loud,
// attributable failure at the actual point of misuse.
//
// - task #776 (F2 revision -- round-closing review finding F7): the
// original check validated only `align`, but `Layout::from_size_align`
// also fails when `reservation_len` overflows `isize::MAX` once rounded
// up to `align` -- an `align`-only check left that half of the SAME
// Drop-reachable-panic hazard open (e.g. `from_raw_parts(b, PAGE, r,
// usize::MAX, PAGE)` still constructed successfully and still panicked
// inside `Drop` under miri). The explicit `reservation_len != 0 &&
// reservation_len.is_multiple_of(PAGE)` checks enforce the documented
// nonzero/page-multiple invariants, while `Layout::from_size_align(...).
// is_ok()` catches overflow cases.
//
// - task #916 (H2C3): the comment above previously claimed these checks
// "cover all documented contract violations immediately at the call
// site" -- this was false. Four documented invariants were uncheckable
// from the arguments alone (pointer validity, liveness, exclusivity,
// and exact-once release), but three MORE were cheaply checkable and
// were NOT checked:
// - `len` must be a non-zero multiple of `PAGE` (documented, not checked)
// - `base` must be aligned to `align` (documented, not checked)
// - `reservation <= base` (documented, now checked below via `base_addr >= res_addr`)
// - `reservation_len >= len + (base - reservation)` (documented, not checked)
// All four are now checked explicitly below, leaving only the genuinely
// uncheckable invariants (pointer validity, liveness, exclusivity) as
// unchecked caller responsibilities.
let base_nn = NonNull::new(base).expect("from_raw_parts: base must be non-null");
let res_nn =
NonNull::new(reservation).expect("from_raw_parts: reservation must be non-null");
let base_addr = base.addr();
let res_addr = reservation.addr();
assert!(
align.is_power_of_two()
&& align >= PAGE
&& reservation_len != 0
&& reservation_len.is_multiple_of(PAGE)
&& len != 0
&& len.is_multiple_of(PAGE)
&& base_addr >= res_addr
&& base_addr.is_multiple_of(align)
&& len
.checked_add(base_addr - res_addr)
.is_some_and(|required| reservation_len >= required)
&& std::alloc::Layout::from_size_align(reservation_len, align).is_ok(),
"Reservation::from_raw_parts: \
align must be a power of two >= PAGE; \
reservation_len must be non-zero and a multiple of PAGE; \
len must be non-zero and a multiple of PAGE; \
base must be >= reservation; \
base must be aligned to align; \
reservation_len must be >= len + (base - reservation); \
(reservation_len, align) must form a valid Layout; \
NOTE: alignment to runtime page_size() is NOT checked — \
caller must ensure base/reservation are page_size()-aligned; \
got align={align}, reservation_len={reservation_len}, len={len}, \
base={base:?}, reservation={reservation:?}"
);
// Task #1172 (M1-hybrid, item 90 of docs/CORRECTNESS_OPEN_ITEMS.md):
// narrow what `granted_huge == true` is allowed to MEAN through this
// constructor. Two independent checks, both loud-at-the-call-site
// for the same task #719 reason as the block above (a Drop-reachable
// panic is far more dangerous than one here):
//
// 1. `huge-pages` feature required to accept `granted_huge: true` at
// all (closes finding M2 as a consequence: with no feature there
// are no huge-flagged ADOPTED reservations, so the "same live
// mapping served or skipped depending on the CONSUMER's feature
// set" divergence cannot arise).
#[cfg(not(feature = "huge-pages"))]
assert!(
!granted_huge,
"Reservation::from_raw_parts: granted_huge=true requires this crate's \
`huge-pages` feature to be enabled. Without it, this constructor would \
accept an adopted huge-flagged reservation whose eligible-forward \
decommit path is compiled out, making `is_huge()` report true while \
decommit/try_decommit silently skip on every call regardless of range \
or kernel version -- see Reservation::decommit's rustdoc. Enable \
`huge-pages`, or pass granted_huge: false."
);
// 2. On Linux/Android, when `granted_huge` is true, `len`,
// `reservation_len`, `reservation`, `base`, and the offset
// `base - reservation` must all be 2 MiB multiples (this crate's
// one supported HugeTLB granularity). Linux's `mmap(2)`/`munmap(2)`
// require both the address and the length of a `MAP_HUGETLB`
// mapping's release call to be huge-page-size-aligned; violating
// this on a REAL HugeTLB mapping would make the eventual `munmap`
// fail EINVAL and leak the entire mapping (see the
// `reservation_len` HugeTLB bullet in `# Safety` above). This
// assert cannot by itself prove the memory really is
// `MAP_HUGETLB`-backed (a `# Safety` precondition it has no way
// to check) -- only that its shape is consistent with being so.
// Five names are listed below, but only FOUR are independent
// checks (task #1196/OX6-L1): if `res_addr` and `base_addr` are
// both 2-MiB multiples, their difference is necessarily a 2-MiB
// multiple too, so the offset conjunct can never be the one that
// fails -- it is implied by the two address conjuncts, not a
// fifth independent requirement.
#[cfg(any(target_os = "linux", target_os = "android"))]
if granted_huge {
// 2 MiB: this crate's one supported HugeTLB granularity
// (`MAP_HUGE_2MB`), matching `os::unix::LINUX_HUGE_PAGE_SIZE`.
// Not imported directly: that constant is private to a module
// compiled only under `#[cfg(all(unix, not(miri)))]` plus
// `huge-pages`, narrower than this assert's own gate (Linux/
// Android, any feature set, so the `huge-pages`-off panic above
// fires first and with a clearer message on that combination).
const HUGE_PAGE_SIZE_2MIB: usize = 2 * 1024 * 1024;
assert!(
len.is_multiple_of(HUGE_PAGE_SIZE_2MIB)
&& reservation_len.is_multiple_of(HUGE_PAGE_SIZE_2MIB)
&& res_addr.is_multiple_of(HUGE_PAGE_SIZE_2MIB)
&& base_addr.is_multiple_of(HUGE_PAGE_SIZE_2MIB)
// Implied by the two conjuncts directly above (res_addr
// and base_addr both 2-MiB-aligned => their difference
// is too), so this can never be the failing conjunct
// today. Kept for the panic message's diagnostics and
// as an explicit statement of intent: if either address
// conjunct above is ever weakened or removed, this one
// becomes load-bearing again.
&& (base_addr - res_addr).is_multiple_of(HUGE_PAGE_SIZE_2MIB),
"Reservation::from_raw_parts: granted_huge=true on Linux/Android \
requires len, reservation_len, reservation, base, and the offset \
(base - reservation) to ALL be multiples of 2 MiB (this crate's \
supported HugeTLB granularity) -- a non-2-MiB-aligned reservation \
or reservation_len would make munmap(2) fail EINVAL and leak the \
entire mapping on release; \
got len={len}, reservation_len={reservation_len}, \
base={base:?}, reservation={reservation:?}, \
offset={}",
base_addr - res_addr
);
}
Self {
base: base_nn,
len,
reservation: res_nn,
reservation_len,
align,
granted_huge,
}
}
}
impl Drop for Reservation {
fn drop(&mut self) {
// Record the release for mock observers (RAII path visibility).
#[cfg(aligned_vmem_mock)]
crate::mock::record(crate::mock::Call::Release {
reservation: self.reservation.as_ptr().addr(),
reservation_len: self.reservation_len,
});
// SAFETY: every safe constructor of `Self` (`reserve_aligned` and
// friends) upholds `from_raw_parts`'s `# Safety` contract by
// construction, so that contract covers `self.reservation` /
// `self.reservation_len` / `self.align` regardless of which
// constructor built this handle: `self.reservation` describes a
// live OS reservation valid for `self.reservation_len` bytes,
// release-compatible with the platform backend below (an exact
// `std::alloc::alloc` provenance/`Layout` match under miri; a live
// `mmap`'d region on Unix; a `VirtualAlloc(MEM_RESERVE)` region on
// Windows), and this handle owns it exclusively (no aliasing —
// `Reservation` is `Send` but not `Sync`). Dropping returns the
// entire reservation to the OS exactly once.
unsafe { release_reservation(self.reservation, self.reservation_len, self.align) };
}
}
// SAFETY (Send): a `Reservation` owns its OS reservation exclusively; moving it
// to another thread moves ownership of every byte, leaving no aliasing on the
// origin thread. The memory is plain uninitialised bytes (no `Rc`/`Cell`/TLS
// affinity).
unsafe impl Send for Reservation {}