jam 0.0.1
A compacting generational garbage collector for C++26
Loading...
Searching...
No Matches
heap.ccm
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2026 Edward Kmett <ekmett@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR Apache-2.0
3
8module;
9#if defined(__linux__) && !defined(_GNU_SOURCE)
10#define _GNU_SOURCE
11#endif
12#include <native/attributes.h>
13#include <cstdio>
14#include <cstdlib>
15#include <algorithm>
16#include <array>
17#include <atomic>
18#include <cassert>
19#include <cerrno>
20#include <cstddef>
21#include <concepts>
22#include <compare>
23#include <limits>
24#include <memory>
25#include <tuple>
26#include <type_traits>
27#include <variant>
28#include <utility>
29#include <vector>
30#include <deque>
31#include <unordered_map>
32#if defined(_WIN32)
33#define WIN32_LEAN_AND_MEAN
34#define NOMINMAX
35#include <windows.h>
36#else
37#include <sys/mman.h>
38#include <unistd.h>
39#endif
40#if defined(__APPLE__)
41#include <mach/mach.h>
42#include <mach/mach_vm.h>
43#elif !defined(__linux__) && !defined(_WIN32)
44#error "jam requires macOS, Linux or Windows virtual-memory aliases"
45#endif
46
47#include <bit>
48#include <cstdint>
49#include <cstring>
50#include <functional>
51#include <span>
52#include <stdexcept>
53
54
55export module jam:heap;
56import :work;
57import :compact;
58export import :units;
59
60namespace jam::detail {
61
62native_cold native_noreturn
63inline void heap_failure(char const * operation, int code = 0) noexcept {
64 std::fprintf(stderr, "jam: %s (%d)\n", operation, code);
65 std::abort();
66}
67
68// One reservation gives both generations stable, ordered address ranges. Only
69// the active ring views acquire backing; maximum capacities reserve address space.
70class generation_reservation {
71 std::byte * base_;
72 std::size_t bytes_;
73public:
74 generation_reservation(std::size_t old_maximum, std::size_t young_maximum) noexcept
75 : bytes_(2 * (old_maximum + young_maximum)) {
76#if defined(_WIN32)
77 base_ = static_cast<std::byte *>(::VirtualAlloc2(nullptr, nullptr, bytes_,
78 MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0));
79 if (!base_) heap_failure("reserve generation address space", ::GetLastError());
80#else
81 auto * p = ::mmap(nullptr, bytes_, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
82 if (p == MAP_FAILED) heap_failure("reserve generation address space", errno);
83 base_ = static_cast<std::byte *>(p);
84#endif
85 }
86 generation_reservation(generation_reservation const &) = delete;
87 ~generation_reservation() noexcept {
88#if defined(_WIN32)
89 MEMORY_BASIC_INFORMATION region{};
90 if (!::VirtualQuery(base_, &region, sizeof(region))) heap_failure("query generation reservation", ::GetLastError());
91 if (region.RegionSize != bytes_ &&
92 !::VirtualFree(base_, bytes_, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
93 heap_failure("coalesce generation reservation", ::GetLastError());
94 if (!::VirtualFree(base_, 0, MEM_RELEASE)) heap_failure("release generation reservation", ::GetLastError());
95#else
96 if (::munmap(base_, bytes_)) heap_failure("release generation reservation", errno);
97#endif
98 }
99 std::byte * data() const noexcept { return base_; }
100};
101
104class heap_mapping {
105#if defined(_WIN32)
106 struct backing {
107 HANDLE handle;
108 explicit backing(std::size_t bytes) noexcept
109 : handle(::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE,
110 static_cast<DWORD>(bytes >> 32), static_cast<DWORD>(bytes), nullptr)) {
111 if (!handle) system_failure("create heap backing");
112 }
113 ~backing() noexcept { ::CloseHandle(handle); }
114 };
115#endif
116 struct span {
117 std::size_t begin, end;
118#if defined(_WIN32)
119 std::shared_ptr<backing> section;
120 std::size_t offset;
121#endif
122 };
123 struct reservation {};
124 std::byte * address = nullptr;
125 std::size_t capacity = 0;
126 std::size_t fixed_extent = 0;
127 // Linux's zero-old-size mremap must remain within one original VMA/object.
128 std::vector<span> spans;
129
130 [[noreturn]] static void system_failure(char const * operation) noexcept {
131#if defined(_WIN32)
132 heap_failure(operation, static_cast<int>(::GetLastError()));
133#else
134 heap_failure(operation, errno);
135#endif
136 }
137 static void validate_size(std::size_t bytes) noexcept {
138 if (!bytes || bytes % page_size() || bytes > std::numeric_limits<std::size_t>::max() / 2)
139 heap_failure("capacity must be a nonzero whole-page size");
140 }
141 explicit heap_mapping(reservation, std::size_t bytes) noexcept {
142 validate_size(bytes);
143#if defined(_WIN32)
144 auto const result = ::VirtualAlloc2(nullptr, nullptr, bytes * 2,
145 MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, PAGE_NOACCESS, nullptr, 0);
146 if (!result) system_failure("reserve heap placeholders");
147#else
148 auto const result = ::mmap(nullptr, bytes * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
149 if (result == MAP_FAILED) system_failure("jam: reserve virtual memory");
150#endif
151 address = static_cast<std::byte *>(result);
152 capacity = bytes;
153 }
154#if defined(_WIN32)
155 static void alias(span const & source, std::byte * target, std::size_t count,
156 std::size_t offset = 0) noexcept {
157 MEMORY_BASIC_INFORMATION region{};
158 if (!::VirtualQuery(target, &region, sizeof(region))) system_failure("query heap placeholder");
159 auto const begin = static_cast<std::byte *>(region.BaseAddress);
160 auto const prefix = static_cast<std::size_t>(target - begin);
161 if (prefix && !::VirtualFree(begin, prefix, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
162 system_failure("split heap placeholder prefix");
163 if (count < region.RegionSize - prefix &&
164 !::VirtualFree(target, count, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER))
165 system_failure("split heap placeholder suffix");
166 if (!::MapViewOfFile3(source.section->handle, ::GetCurrentProcess(), target,
167 source.offset + offset, count, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0))
168 system_failure("alias heap backing");
169 }
170#else
171 static void alias(std::byte * source, std::byte * target, std::size_t count) noexcept {
172#if defined(__APPLE__)
173 auto destination = reinterpret_cast<mach_vm_address_t>(target);
174 vm_prot_t current = 0, maximum = 0;
175 auto const result = ::mach_vm_remap(::mach_task_self(), &destination, count, 0,
176 VM_FLAGS_FIXED | VM_FLAGS_OVERWRITE, ::mach_task_self(),
177 reinterpret_cast<mach_vm_address_t>(source), false, &current, &maximum, VM_INHERIT_SHARE);
178 if (result != KERN_SUCCESS)
179 heap_failure("alias virtual memory", result);
180 assert(destination == reinterpret_cast<mach_vm_address_t>(target));
181#else
182 auto const result = ::mremap(source, 0, count, MREMAP_MAYMOVE | MREMAP_FIXED, target);
183 if (result == MAP_FAILED) system_failure("jam: alias virtual memory");
184 assert(result == target);
185#endif
186 }
187#endif
188 void fresh(std::size_t begin, std::size_t count) noexcept {
189 if (!count) return;
190#if defined(_WIN32)
191 spans.push_back({begin, begin + count, std::make_shared<backing>(count), 0});
192 alias(spans.back(), address + begin, count);
193#else
194 auto const result = ::mmap(address + begin, count, PROT_READ | PROT_WRITE,
195 MAP_FIXED | MAP_SHARED | MAP_ANONYMOUS, -1, 0);
196 if (result == MAP_FAILED) system_failure("jam: map fresh backing");
197 spans.push_back({begin, begin + count});
198#endif
199 }
200 void finish_aliases() noexcept {
201 std::sort(spans.begin(), spans.end(), [](auto const & a, auto const & b) noexcept { return a.begin < b.begin; });
202 for (auto const & s : spans)
203#if defined(_WIN32)
204 alias(s, address + capacity + s.begin, s.end - s.begin);
205#else
206 alias(address + s.begin, address + capacity + s.begin, s.end - s.begin);
207#endif
208 }
209 void discard_gap(std::size_t start, std::size_t live) const noexcept {
210#if defined(_WIN32)
211 // Advisory discard releases dead physical pages; a partially retained section
212 // keeps its commit charge until the last surviving view/handle is released.
213 for (auto const & s : spans) {
214 auto const discard = [&](std::size_t begin, std::size_t end) noexcept {
215 begin = std::max(begin, s.begin);
216 end = std::min(end, s.end);
217 if (begin < end) static_cast<void>(::VirtualAlloc(address + begin, end - begin, MEM_RESET, PAGE_READWRITE));
218 };
219 if (start + live <= capacity) {
220 discard(0, start);
221 discard(start + live, capacity);
222 } else discard(start + live - capacity, start);
223 }
224#else
225 auto const begin = (start + live) % capacity;
226 auto const count = capacity - live;
227 auto const first = std::min(count, capacity - begin);
228#if defined(__APPLE__)
229 constexpr auto advice = MADV_FREE_REUSABLE;
230#else
231 constexpr auto advice = MADV_REMOVE;
232#endif
233 // dev: advisory release; kernels may retain dead pages until memory pressure.
234 if (first) static_cast<void>(::madvise(address + begin, first, advice));
235 if (count > first) static_cast<void>(::madvise(address, count - first, advice));
236#endif
237 }
238
239 void reset_fixed() noexcept {
240 assert(fixed_extent);
241#if defined(_WIN32)
242 for (auto const & s : spans) {
243 if (!::UnmapViewOfFile2(::GetCurrentProcess(), address + s.begin, MEM_PRESERVE_PLACEHOLDER) ||
244 !::UnmapViewOfFile2(::GetCurrentProcess(), address + capacity + s.begin, MEM_PRESERVE_PLACEHOLDER))
245 system_failure("restore generation placeholders");
246 }
247 MEMORY_BASIC_INFORMATION region{};
248 if (!::VirtualQuery(address, &region, sizeof(region))) system_failure("query generation placeholder");
249 if (region.RegionSize < fixed_extent &&
250 !::VirtualFree(address, fixed_extent, MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS))
251 system_failure("coalesce generation placeholders");
252#else
253 if (::mmap(address, fixed_extent, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) == MAP_FAILED)
254 system_failure("restore generation reservation");
255#endif
256 spans.clear();
257 }
258
259public:
261 explicit heap_mapping(std::size_t bytes) noexcept : heap_mapping(reservation{}, bytes) {
262 spans.reserve(1);
263 fresh(0, bytes);
264 finish_aliases();
265 }
266 heap_mapping(std::size_t bytes, std::byte * base, std::size_t maximum) noexcept
267 : address(base), capacity(bytes), fixed_extent(2 * maximum) {
268 validate_size(bytes);
269 if (bytes > maximum) heap_failure("generation exceeds reserved address range");
270 spans.reserve(1);
271 fresh(0, bytes);
272 finish_aliases();
273 }
274 // Stage surviving aliases before touching the fixed range: source and target
275 // rings may overlap during growth, shrinking or view rotation.
276 void replace(heap_mapping & staged) noexcept {
277 if (!fixed_extent) { swap(staged); return; }
278 if (staged.capacity * 2 > fixed_extent) heap_failure("generation growth exceeds maximum");
279 reset_fixed();
280 capacity = staged.capacity;
281 for (auto const & s : staged.spans) {
282#if defined(_WIN32)
283 alias(s, address + s.begin, s.end - s.begin);
284#else
285 alias(staged.address + s.begin, address + s.begin, s.end - s.begin);
286#endif
287 }
288 spans = staged.spans;
289 finish_aliases();
290 }
291 heap_mapping(heap_mapping const &) = delete;
292 heap_mapping & operator=(heap_mapping const &) = delete;
293 heap_mapping(heap_mapping && other) noexcept
294 : address(std::exchange(other.address, nullptr)), capacity(std::exchange(other.capacity, 0)),
295 fixed_extent(std::exchange(other.fixed_extent, 0)), spans(std::move(other.spans)) {}
296 heap_mapping & operator=(heap_mapping && other) noexcept { swap(other); return *this; }
298 ~heap_mapping() noexcept {
299 if (!address) return;
300 if (fixed_extent) { reset_fixed(); return; }
301#if defined(_WIN32)
302 for (auto const & s : spans) {
303 ::UnmapViewOfFile(address + s.begin);
304 ::UnmapViewOfFile(address + capacity + s.begin);
305 }
306#else
307 static_cast<void>(::munmap(address, capacity * 2));
308#endif
309 }
311 native_inline native_pure std::byte * data() const noexcept { return address; }
313 native_inline native_pure std::size_t bytes() const noexcept { return capacity; }
315 static std::size_t page_size() noexcept {
316#if defined(_WIN32)
317 SYSTEM_INFO info{};
318 ::GetSystemInfo(&info);
319 return info.dwPageSize;
320#else
321 return static_cast<std::size_t>(::getpagesize());
322#endif
323 }
327 void publish(std::size_t start, std::byte * target, std::size_t count) const noexcept {
328#if defined(_WIN32)
329 static_cast<void>(start); static_cast<void>(target); static_cast<void>(count);
330 heap_failure("host publication is not supported on Windows");
331#else
332 if (start >= capacity || start % page_size() || count % page_size() ||
333 count > capacity || reinterpret_cast<std::uintptr_t>(target) % page_size())
334 heap_failure("invalid published heap arc");
335 while (count) {
336 auto const where = std::lower_bound(spans.begin(), spans.end(), start,
337 [](span const & s, std::size_t value) noexcept { return s.end <= value; });
338 assert(where != spans.end() && where->begin <= start);
339 auto const part = std::min(count, where->end - start);
340 alias(address + start, target, part);
341 start = (start + part) % capacity;
342 target += part;
343 count -= part;
344 }
345#endif
346 }
348 void swap(heap_mapping & other) noexcept {
349 std::swap(address, other.address);
350 std::swap(capacity, other.capacity);
351 std::swap(fixed_extent, other.fixed_extent);
352 spans.swap(other.spans);
353 }
358 heap_mapping resized(std::size_t old_start_bytes, std::size_t live_bytes,
359 std::size_t new_bytes, std::size_t new_start_bytes) const noexcept {
360 validate_size(new_bytes);
361 auto const page = page_size();
362 if (!address || old_start_bytes >= capacity || new_start_bytes >= new_bytes ||
363 old_start_bytes % page || new_start_bytes % page ||
364 live_bytes > capacity || live_bytes > new_bytes)
365 heap_failure("invalid circular remap range");
366 auto const live = ((live_bytes + page - 1) / page) * page;
367 heap_mapping result(reservation{}, new_bytes);
368 result.spans.reserve(spans.size() + 4);
369 auto source = old_start_bytes;
370 auto target = new_start_bytes;
371 auto remaining = live;
372 while (remaining) {
373 auto const where = std::lower_bound(spans.begin(), spans.end(), source,
374 [](span const & s, std::size_t value) noexcept { return s.end <= value; });
375 assert(where != spans.end() && where->begin <= source);
376 auto const count = std::min({remaining, where->end - source, new_bytes - target});
377#if defined(_WIN32)
378 alias(*where, result.address + target, count, source - where->begin);
379 result.spans.push_back({target, target + count, where->section, where->offset + source - where->begin});
380#else
381 alias(address + source, result.address + target, count);
382 result.spans.push_back({target, target + count});
383#endif
384 source = (source + count) % capacity;
385 target = (target + count) % new_bytes;
386 remaining -= count;
387 }
388 if (!live) result.fresh(0, new_bytes);
389 else if (new_start_bytes + live <= new_bytes) {
390 result.fresh(0, new_start_bytes);
391 result.fresh(new_start_bytes + live, new_bytes - new_start_bytes - live);
392 } else result.fresh(new_start_bytes + live - new_bytes, new_bytes - live);
393 result.finish_aliases();
394 discard_gap(old_start_bytes, live);
395 return result;
396 }
397};
398}
399
400namespace jam {
401
402export class heap;
403export template<class T> class root;
404export template<class T> class ptr;
405export template<class T> class weak_ptr;
406export template<class T> class weak_root;
407export template<class T> class weak;
408
411export template<class V>
412concept visitor = std::same_as<typename V::heap_type, heap>;
413
414namespace detail {
415#if defined(JAM_CONTEXT_X28)
416#if !defined(__aarch64__)
417#error "JAM_CONTEXT_X28 requires AArch64 and -ffixed-x28"
418#endif
419register heap * active_heap asm("x28");
420#else
421inline thread_local heap * active_heap = nullptr;
422#endif
423template<class T, class H = heap>
424concept member_trace = requires(T & value, typename H::visitor & visit) {
425 value.trace(visit);
426};
427template<class T, class H = heap>
428concept cooperative_trace = requires(typename H::visitor & visit, ptr<T> const & at) {
429 T::trace(visit, at);
430};
431template<class T, class H = heap>
432concept allocation_trace = requires(T const & value, typename H::visitor & visit) {
433 { value.claim_and_trace(visit) } noexcept -> std::same_as<void>;
434};
435template<class T>
436concept has_allocation_trace = requires { &T::claim_and_trace; } || allocation_trace<T>;
437template<class T>
438concept has_trace = requires { &T::trace; } || member_trace<T> || cooperative_trace<T>;
439
440struct structural_tracer {};
441template<class T, class... Members>
442struct member_manifest {
443 std::tuple<Members T::*...> members;
444 template<class Visitor>
445 static constexpr bool accepts = (requires(Visitor & visit, Members const & value) {
446 visit(value);
447 } && ...);
448};
449template<class T, class Descriptor> inline constexpr bool manifest_for = false;
450template<class T, class... Members>
451inline constexpr bool manifest_for<T, member_manifest<T, Members...>> = true;
452template<class T>
453concept has_manifest = requires {
454 requires manifest_for<T, std::remove_cvref_t<decltype(T::manifest)>>;
455};
456struct invalid_tracer {};
457}
458
475export template<class T, class... Members> requires (std::is_object_v<Members> && ...)
476[[nodiscard]] consteval auto make_manifest(Members T::*... members) {
477 if (((members == nullptr) || ...)) throw "manifest members must not be null";
478 return detail::member_manifest<T, Members...>{{members...}};
479}
480
485export class heap_scope {
486 heap * previous;
487public:
489 [[nodiscard]] native_inline explicit heap_scope(heap & arena) noexcept;
491 native_inline ~heap_scope() noexcept { detail::active_heap = previous; }
492 heap_scope(heap_scope const &) = delete;
493 heap_scope & operator=(heap_scope const &) = delete;
494};
495
500export template<class T>
501struct leaf {
503 template<class Visitor>
504 static constexpr void trace(Visitor &, T const &) noexcept {}
505};
506
514export template<class T>
515struct tracer : std::conditional_t<requires { T::manifest; } || detail::has_allocation_trace<T>, detail::invalid_tracer, leaf<T>> {};
516
522export template<class T> requires detail::has_trace<T>
523struct tracer<T> {
525 template<class Visitor>
526 static constexpr auto trace(Visitor & visit, T const & value)
527 noexcept(noexcept(value.trace(visit))) -> decltype(value.trace(visit)) {
528 return value.trace(visit);
529 }
530
531 template<class Visitor>
532 static constexpr decltype(auto) trace(Visitor & visit, ptr<T> const & at) noexcept
533 requires requires { T::trace(visit, at); } {
534 return T::trace(visit, at);
535 }
536};
537
538namespace detail {
539template<class T, class H>
540concept traceable_in = (!has_allocation_trace<T> || allocation_trace<T, H>)
541 && (allocation_trace<T, H> || requires(T const & value, typename H::visitor & visit) {
542 sizeof(T);
543 tracer<T>::trace(visit, value);
544} || requires(ptr<T> const & at, typename H::visitor & visit) {
545 sizeof(T);
546 tracer<T>::trace(visit, at);
547});
548}
549
556export template<class T>
557concept traceable = detail::traceable_in<T, heap>;
558
569export template<class T>
570class ptr {
571public:
573 using element_type = T;
575 using offset = std::uint32_t;
576
577private:
578 mutable offset position = 0;
579 native_inline constexpr void update_barrier() noexcept;
580
581public:
583 constexpr ptr() noexcept = default;
585 constexpr ptr(std::same_as<std::nullptr_t> auto) noexcept {}
587 native_inline explicit constexpr ptr(offset at) noexcept;
589 native_inline constexpr ptr(ptr const & other) noexcept;
592 template<class U> requires (!std::same_as<T, U>) && std::derived_from<U, T>
593 && detail::allocation_trace<T> && requires(T * p) { static_cast<U *>(p); }
594 native_inline constexpr ptr(ptr<U> const & other) noexcept;
595
596 native_inline constexpr ptr & operator=(ptr const & other) noexcept;
598 native_inline constexpr ptr(ptr && other) noexcept;
600 native_inline constexpr ptr & operator=(ptr && other) noexcept;
602 native_inline constexpr void swap(ptr & other) noexcept;
604 friend native_inline constexpr void swap(ptr & left, ptr & right) noexcept { left.swap(right); }
606 native_inline constexpr ~ptr() noexcept;
608 constexpr ptr & unsafe_assign(ptr const & other) noexcept { position = other.position; return *this; }
610 constexpr ptr & unsafe_assign(offset bits) noexcept { position = bits; return *this; }
612 [[nodiscard]] constexpr bool is_young() const noexcept { return (position & 0x80000000u) != 0; }
614 [[nodiscard]] constexpr offset local_offset() const noexcept { return position & 0x7fffffffu; }
616 [[nodiscard]] native_inline native_pure
617 constexpr offset get() const noexcept { return position; }
619 [[nodiscard]] native_inline native_pure
620 explicit constexpr operator bool() const noexcept {
621 return position != 0;
622 }
623
625 [[nodiscard]] native_inline T * operator->() const noexcept;
627 [[nodiscard]] native_inline T & operator*() const noexcept;
631 native_inline void prefetch_marks() const noexcept;
635 native_inline void prefetch() const noexcept;
638 [[nodiscard]] constexpr auto operator<=>(ptr const &) const noexcept = default;
639};
640
648export template<class T>
649class weak_ptr {
650public:
652 using element_type = T;
654 using offset = std::uint32_t;
655private:
656 mutable offset position = 0;
657 native_inline constexpr void update_barrier() noexcept;
658public:
660 constexpr weak_ptr() noexcept = default;
662 constexpr weak_ptr(std::same_as<std::nullptr_t> auto) noexcept {}
664 native_inline constexpr weak_ptr(ptr<T> const & value) noexcept;
666 native_inline weak_ptr(root<T> const & value) noexcept;
668 template<class U> requires (!std::same_as<T, U>) && std::convertible_to<ptr<U>, ptr<T>>
669 native_inline constexpr weak_ptr(ptr<U> const & value) noexcept : weak_ptr(ptr<T>{value}) {}
671 native_inline constexpr weak_ptr(weak_ptr const & other) noexcept;
672 native_inline constexpr weak_ptr & operator=(weak_ptr const & other) noexcept;
674 native_inline constexpr weak_ptr(weak_ptr && other) noexcept;
676 native_inline constexpr weak_ptr & operator=(weak_ptr && other) noexcept;
678 native_inline constexpr ~weak_ptr() noexcept;
680 native_inline constexpr void swap(weak_ptr & other) noexcept;
682 friend native_inline constexpr void swap(weak_ptr & a, weak_ptr & b) noexcept { a.swap(b); }
684 constexpr weak_ptr & unsafe_assign(offset bits) noexcept { position = bits; return *this; }
686 constexpr weak_ptr & unsafe_assign(weak_ptr const & other) noexcept { return unsafe_assign(other.position); }
688 [[nodiscard]] native_inline native_pure constexpr offset get() const noexcept { return position; }
690 [[nodiscard]] constexpr bool is_young() const noexcept { return (position & 0x80000000u) != 0; }
692 [[nodiscard]] constexpr bool expired() const noexcept { return position == 0; }
695 [[nodiscard]] native_inline root<T> lock() const noexcept requires traceable<T>;
697 [[nodiscard]] constexpr auto operator<=>(weak_ptr const &) const noexcept = default;
698};
699
700export template<class T> weak_ptr(ptr<T> const &) -> weak_ptr<T>;
702export template<class T> weak_ptr(root<T> const &) -> weak_ptr<T>;
703
704namespace detail {
705template<class T> inline constexpr bool is_ptr = false;
706template<class T> inline constexpr bool is_ptr<ptr<T>> = true;
707template<class T> inline constexpr bool is_weak_ptr = false;
708template<class T> inline constexpr bool is_weak_ptr<weak_ptr<T>> = true;
709template<class T, class Visitor>
710concept visits = requires(Visitor & visit, std::remove_reference_t<T> const & value) {
711 tracer<std::remove_cvref_t<T>>::trace(visit, value);
712};
713}
714
717export template<class T>
718struct tracer<ptr<T>> {
720 template<visitor Visitor>
721 static constexpr void trace(Visitor & visit, ptr<T> const & value)
722 noexcept(noexcept(visit(value))) {
723 visit(value);
724 }
725};
726
729export template<class T>
730struct tracer<weak_ptr<T>> {
732 template<visitor Visitor>
733 static constexpr void trace(Visitor & visit, weak_ptr<T> const & value)
734 noexcept(noexcept(visit(value))) { visit(value); }
735};
736
737namespace detail {
738template<class Visitor, class... Ts>
739constexpr void trace_parts(Visitor & visit, Ts const &... values) noexcept {
740 auto part = [&](auto const & value) constexpr noexcept {
741 if constexpr (requires { visit(value); }) visit(value);
742 else tracer<std::remove_cvref_t<decltype(value)>>::trace(visit, value);
743 };
744 (part(values), ...);
745}
746}
747
750export template<class... Ts>
751struct tracer<std::tuple<Ts...>> : detail::structural_tracer {
753 template<class Visitor>
754 static constexpr void trace(Visitor & visit, std::tuple<Ts...> const & value)
755 requires (detail::visits<Ts, Visitor> && ...) {
756 std::apply([&](auto const &... part) constexpr {
757 detail::trace_parts(visit, part...);
758 }, value);
759 }
760};
761
764export template<class T, std::size_t N>
765struct tracer<std::array<T, N>> : detail::structural_tracer {
767 template<class Visitor>
768 static constexpr void trace(Visitor & visit, std::array<T, N> const & value)
769 requires detail::visits<T, Visitor> {
770 for (auto const & part : value) detail::trace_parts(visit, part);
771 }
772};
773
776export template<class... Ts>
777struct tracer<std::variant<Ts...>> : detail::structural_tracer {
779 template<class Visitor>
780 static constexpr void trace(Visitor & visit, std::variant<Ts...> const & value)
781 requires (detail::visits<Ts, Visitor> && ...) {
782 if (value.valueless_by_exception()) return;
783 std::visit([&](auto const & part) constexpr {
784 detail::trace_parts(visit, part);
785 }, value);
786 }
787};
788
791export template<class T> requires (!detail::has_trace<T>) && detail::has_manifest<T>
792struct tracer<T> : detail::structural_tracer {
794 template<class Visitor>
795 static native_inline constexpr void trace(Visitor & visit, T const & value) noexcept
796 requires std::remove_cvref_t<decltype(T::manifest)>::template accepts<Visitor> {
797 auto const & [...member] = T::manifest.members;
798 visit((value.*member)...);
799 }
800
801 template<class Visitor>
802 static void trace(Visitor & visit, ptr<T> const & at) noexcept
803 requires std::remove_cvref_t<decltype(T::manifest)>::template accepts<Visitor> {
804 visit.template trace_manifest<T>(at);
805 }
806};
807
820
822export struct heap_options {
828 std::size_t workers = 1;
830 std::size_t minor_collections = 7;
831};
832
847export class heap {
848 template<class T> friend class ptr;
849 template<class T> friend class weak_ptr;
850 template<class T> friend class weak;
851
852public:
856 [[nodiscard]] native_pure native_inline
857 static heap * current() noexcept { return detail::active_heap; }
858
860 using word = std::uint64_t;
862 using offset = std::uint32_t;
864 using bitmap = offset;
866 static constexpr offset null = 0;
868 static constexpr std::size_t block_words = 32;
870 static constexpr unsigned fields_per_word = 2;
872 using block = detail::heap_block;
875 template<class T> using ptr = jam::ptr<T>;
877 template<class T> using weak_ptr = jam::weak_ptr<T>;
878 static_assert(sizeof(std::size_t) == 8, "jam requires 64-bit addressing");
879 static_assert(sizeof(block) == 16);
880 static_assert(std::atomic_ref<bitmap>::is_always_lock_free);
881 static_assert(std::atomic_ref<word>::is_always_lock_free);
882
883 class visitor;
884
885private:
886 using trace_function = void (*)(visitor &, offset);
887 struct trace_job { offset at; trace_function trace; };
888 struct weak_entry {
889 trace_job key, value, finalizer;
890 void (*invoke)(heap &, weak_entry &) noexcept;
891 bool active = true;
892 weak_entry * previous_running = nullptr;
893 };
894 template<class F> struct finalizer_entry : weak_entry {
895 void (*runner)(F *) noexcept;
896 };
897
898public:
905 class root_handle {
906 friend class heap;
907 std::uint32_t slot = 0;
908 native_inline void retain() noexcept {
909 if (slot) heap::current()->retain_root(slot);
910 }
911 native_inline void detach() noexcept {
912 if (slot) heap::current()->release_root(std::exchange(slot, 0));
913 }
914 native_inline root_handle(heap & arena, offset at, trace_function fn = nullptr) noexcept
915 : slot(arena.add_root(at, fn)) {}
916 public:
918 root_handle() noexcept = default;
920 native_inline root_handle(root_handle const & other) noexcept : slot(other.slot) { retain(); }
922 native_inline root_handle(root_handle && other) noexcept : slot(std::exchange(other.slot, 0)) {}
924 native_inline root_handle & operator=(root_handle const & other) noexcept {
925 if (this != &other) { detach(); slot = other.slot; retain(); }
926 return *this;
927 }
928
929 native_inline root_handle & operator=(root_handle && other) noexcept {
930 if (this != &other) { detach(); slot = std::exchange(other.slot, 0); }
931 return *this;
932 }
933
934 native_inline ~root_handle() noexcept { detach(); }
936 [[nodiscard]] native_inline offset get() const noexcept {
937 return slot ? heap::current()->root_slots[slot - 1].position : null;
938 }
939
940 [[nodiscard]] native_inline offset operator*() const noexcept { return get(); }
942 [[nodiscard]] native_inline explicit operator bool() const noexcept { return get() != null; }
943 };
944
950 class visitor {
951 friend class heap;
952 template<class T> friend struct jam::tracer;
953 heap & arena;
954 detail::frontier<trace_job> & pending;
955 std::size_t worker;
956 void const * record = nullptr;
957 offset record_offset = 0;
958 std::size_t record_bytes = 0;
959 visitor(heap & h, detail::frontier<trace_job> & tasks, std::size_t id) noexcept
960 : arena(h), pending(tasks), worker(id) {}
961
962 native_inline std::size_t pointer_slot(void const * field) const noexcept {
963 auto const address = reinterpret_cast<std::uintptr_t>(field);
964 auto const begin = reinterpret_cast<std::uintptr_t>(record);
965 auto const delta = address - begin;
966 if (!record || address < begin || record_bytes < sizeof(offset)
967 || delta > record_bytes - sizeof(offset) || delta % sizeof(offset))
968 detail::heap_failure("typed visitor requires a field in its tracing value");
969 return delta / sizeof(offset);
970 }
971 template<class T>
972 native_inline void enqueue(ptr<T> const & value) noexcept {
973 static_assert(traceable<T>, "reference target must support tracing");
974 if (value && (!arena.minor_collection || value.is_young())) pending.push(worker, {value.get(), &heap::template trace_record<T>});
975 }
976
977 // One mask window bounds stack use even for large, sparsely described records.
978 template<class T>
979 struct manifest_visit {
980 using heap_type = heap;
981 visitor & visit;
982 ptr<T> const * next = nullptr;
983 word bits = 0;
984 std::size_t first_slot = 0;
985
986 native_inline void flush() noexcept {
987 visit.pointers(bits, first_slot);
988 bits = 0;
989 }
990 template<class U>
991 native_inline void operator()(ptr<U> const & value) noexcept {
992 auto const slot = visit.pointer_slot(&value);
993 auto const window = slot & ~std::size_t{63};
994 if (window != first_slot) { flush(); first_slot = window; }
995 bits |= word{1} << (slot - first_slot);
996 if constexpr (std::same_as<T, U>) {
997 if (value) {
998 if (next) visit.enqueue(*next);
999 next = std::addressof(value);
1000 }
1001 } else visit.enqueue(value);
1002 }
1003 template<class U>
1004 native_inline void operator()(weak_ptr<U> const & value) noexcept { visit(value); }
1005 template<class U>
1006 native_inline void operator()(U const & value) noexcept
1007 requires (!detail::is_ptr<U> && !detail::is_weak_ptr<U>) && detail::visits<U, visitor> {
1008 if constexpr (std::derived_from<tracer<U>, detail::structural_tracer>)
1009 tracer<U>::trace(*this, value);
1010 else visit(value); // Custom hooks keep the actual heap visitor.
1011 }
1012 template<class... Us>
1013 native_inline void operator()(Us const &... values) noexcept
1014 requires (sizeof...(Us) != 1) && (detail::visits<Us, visitor> && ...) {
1015 ((*this)(values), ...);
1016 }
1017 };
1018
1019 template<class T>
1020 void trace_manifest(ptr<T> const & at) noexcept {
1021 // Pointer fields stay put throughout marking; borrow the next edge.
1022 auto const * current = std::addressof(at);
1023 while (current) {
1024 auto const * value = claim_target(*current);
1025 if (!value) break;
1026 manifest_visit<T> fields{*this};
1027 tracer<T>::trace(fields, *value);
1028 fields.flush();
1029 current = fields.next;
1030 poll();
1031 }
1032 }
1033
1034 public:
1036 using heap_type = heap;
1037 visitor(visitor const &) = delete;
1038 visitor & operator=(visitor const &) = delete;
1041 [[nodiscard]] native_inline bool claim(offset start, std::size_t words = 1,
1042 std::size_t alignment_bytes = 8) noexcept {
1043 return arena.claim(start, words, alignment_bytes);
1044 }
1045
1050 template<class T>
1051 [[nodiscard]] native_inline T const * claim_target(ptr<T> const & value) noexcept {
1052 heap::require_record<T>();
1053 if (!value || !claim(value.get(), record_words<T>, std::max(alignof(T), sizeof(word))))
1054 return nullptr;
1055 auto const * result = arena.template record_address<T>(value.get());
1056 record = result;
1057 record_offset = value.get();
1058 record_bytes = sizeof(T);
1059 return result;
1060 }
1061
1064 template<class T>
1065 [[nodiscard]] native_inline T const * claim_target(T const * value) noexcept {
1066 heap::require_record<T>();
1067 if (!value) return nullptr;
1068 auto const address = reinterpret_cast<std::uintptr_t>(value);
1069 auto const old_delta = address - reinterpret_cast<std::uintptr_t>(arena.arena.data());
1070 auto const young_delta = address - reinterpret_cast<std::uintptr_t>(arena.nursery.data());
1071 bool const young = young_delta < arena.nursery.used() * sizeof(word);
1072 auto const delta = young ? young_delta : old_delta;
1073 if ((!young && old_delta >= arena.arena.used() * sizeof(word)) || delta % sizeof(word))
1074 detail::heap_failure("claim_target requires a complete object in this heap");
1075 auto const at = static_cast<offset>(delta / sizeof(word)) | (young ? young_bit : 0);
1076 if (!claim(at, record_words<T>, std::max(alignof(T), sizeof(word)))) return nullptr;
1077 record = value;
1078 record_offset = at;
1079 record_bytes = sizeof(T);
1080 return value;
1081 }
1082
1084 template<class T>
1085 native_inline void pointer(ptr<T> const & value) noexcept {
1086 auto const heap_delta = std::size_t(record_offset & offset_mask) * sizeof(word) + pointer_slot(&value) * sizeof(offset);
1087 arena.pointer((record_offset & young_bit) | static_cast<offset>(heap_delta / sizeof(word)),
1088 static_cast<unsigned>((heap_delta % sizeof(word)) / sizeof(offset)));
1089 }
1090
1092 template<class T>
1093 native_inline void pointer(weak_ptr<T> const & value) noexcept {
1094 pointers(word{1}, pointer_slot(&value));
1095 }
1096
1100 native_inline void pointers(word bits, std::size_t first_slot = 0) noexcept {
1101 if (!bits) return;
1102 auto const slots = record_bytes / sizeof(offset);
1103 if (!record || first_slot > slots
1104 || static_cast<std::size_t>(std::bit_width(bits)) > slots - first_slot)
1105 detail::heap_failure("pointer pattern exceeds its tracing value");
1106 auto & g = arena.containing(record_offset);
1107 auto const slot = std::size_t(record_offset & offset_mask) * fields_per_word + first_slot;
1108 constexpr auto width = block_words * fields_per_word;
1109 auto const shift = slot % width;
1110 std::atomic_ref<word>{g.metadata[slot / width].pointers}
1111 .fetch_or(bits << shift, std::memory_order_relaxed);
1112 if (shift && (bits >> (width - shift)))
1113 std::atomic_ref<word>{g.metadata[slot / width + 1].pointers}
1114 .fetch_or(bits >> (width - shift), std::memory_order_relaxed);
1115 }
1116
1118 template<class T>
1119 [[nodiscard]] native_inline T const * claim(ptr<T> const & value) noexcept {
1120 pointer(value);
1121 return claim_target(value);
1122 }
1123
1127 native_inline offset field(offset cell, unsigned slot = 0) noexcept {
1128 arena.pointer(cell, slot);
1129 auto const target = arena.field(cell, slot);
1130 if (target != null && (!arena.minor_collection || (target & young_bit)))
1131 pending.push(worker, {target, nullptr});
1132 return target;
1133 }
1134
1135 native_inline void target(offset at) noexcept {
1136 if (at && (!arena.minor_collection || (at & young_bit))) pending.push(worker, {at, nullptr});
1137 }
1138
1141 template<class T>
1142 native_inline void operator()(T const & value) noexcept
1143 requires (!detail::is_ptr<T> && !detail::is_weak_ptr<T>) && detail::visits<T, visitor> {
1144 tracer<T>::trace(*this, value);
1145 }
1146
1150 template<class T>
1151 native_inline ptr<T> operator()(ptr<T> const & value) noexcept {
1152 // Defer target validation until use: deducing a recursive member hook's
1153 // return type must not require that hook's own traceable constraint.
1154 pointer(value);
1155 enqueue(value);
1156 return value;
1157 }
1158
1159 template<class T>
1160 native_inline void operator()(weak_ptr<T> const & value) noexcept { pointer(value); }
1162 template<class... Ts>
1163 native_inline void operator()(Ts const &... values) noexcept
1164 requires (sizeof...(Ts) != 1) && (detail::visits<Ts, visitor> && ...) {
1165 detail::trace_parts(*this, values...);
1166 }
1167
1169 native_inline void poll() noexcept { pending.communicate(worker); }
1170 };
1171
1172private:
1173 template<class T>
1174 static constexpr std::size_t record_words = (sizeof(T) + sizeof(word) - 1) / sizeof(word);
1175
1176 template<class T>
1177 static consteval void require_record() noexcept {
1178 // Byte relocation is a managed-storage precondition, not something a trace
1179 // hook or a standard type trait proves. Compaction has no per-record type
1180 // descriptor and never invokes a move constructor or destructor.
1181 static_assert(std::is_object_v<T> && !std::is_const_v<T> && !std::is_volatile_v<T>,
1182 "jam records must be unqualified objects that permit byte relocation");
1183 static_assert(alignof(T) <= 64, "record alignment exceeds 64 bytes");
1184 }
1185
1186 // Marking never changes mappings; no payload copy or stack snapshot is needed.
1187 template<class T>
1188 native_inline T * record_address(offset at) noexcept {
1189 require_record<T>();
1190 assert(at && (at & offset_mask) < containing(at).cursor
1191 && record_words<T> <= containing(at).cursor - (at & offset_mask));
1192 auto * p = decode(at);
1193 if constexpr (std::is_trivially_copyable_v<T>) {
1194 return static_cast<T *>(std::memmove(p, p, sizeof(T)));
1195 } else {
1196 // Nontrivial records use Jam's byte-relocation contract. This cast does
1197 // not claim to establish arbitrary nontrivial lifetimes in ISO C++.
1198 return reinterpret_cast<T *>(p);
1199 }
1200 }
1201
1202 template<class T>
1203 static void trace_record(visitor & visit, offset at) noexcept {
1204 require_record<T>();
1205 static_assert(traceable<T>, "tracing hooks must accept the heap visitor");
1206 if constexpr (detail::allocation_trace<T>) {
1207 // Dispatch before claiming: only the dynamic override knows the extent.
1208 visit.arena.template record_address<T>(at)->claim_and_trace(visit);
1209 } else if constexpr (requires { tracer<T>::trace(visit, ptr<T>{at}); }) {
1210 // Cooperative hooks receive an unclaimed entry and own the walk.
1211 tracer<T>::trace(visit, ptr<T>{at});
1212 } else if (auto const * value = visit.claim_target(ptr<T>{at})) {
1213 tracer<T>::trace(visit, *value);
1214 }
1215 }
1216
1217public:
1219 struct generation {
1220 friend class heap;
1221 private:
1222 static constexpr offset young_bit = 0x80000000u;
1223 static constexpr offset offset_mask = 0x7fffffffu;
1224 struct settings { std::size_t page_words, reserved, minimum, bytes; generation_options policy; };
1225 generation_options const policy;
1226 detail::compactor const & compactor;
1227 detail::work_pool & workers;
1228 std::size_t const page_word_count;
1229 std::size_t const reserve_word_count;
1230 std::size_t const minimum_capacity;
1231 detail::heap_mapping storage;
1232 word * view_base;
1233 std::vector<block> metadata;
1234 // One alignment nibble per 256 bytes.
1235 // Low3 bits are a thermometer exponent (0,1,3,7); bit3 joins the next block.
1236 // Atomic OR therefore implements max(k), even for simultaneous markers.
1237 std::vector<std::uint8_t> alignment;
1238 bool has_alignment = false;
1239 std::size_t cursor = 1;
1240 std::size_t origin = 0;
1241 bool collecting = false;
1242 void require_mutator() const noexcept {
1243 if (collecting) detail::heap_failure("mutation during collection");
1244 }
1245 static constexpr unsigned block_shift = 5;
1246 static constexpr std::size_t maximum_bytes = std::size_t{1} << 34;
1247
1248 static constexpr bool valid_options(generation_options const & config) noexcept {
1249 return config.capacity.count() != 0 && config.capacity <= config.maximum && config.maximum <= units::bytes{maximum_bytes}
1250 && config.reserve.count() != 0 && config.reserve <= units::bytes{maximum_bytes / 2}
1251 && config.shrink_shift < std::numeric_limits<std::size_t>::digits;
1252 }
1253 static settings configure(generation_options config) {
1254 if (config.maximum > units::bytes{maximum_bytes} || config.capacity > units::bytes{maximum_bytes} || config.reserve > units::bytes{maximum_bytes})
1255 throw std::length_error("jam generation exceeds 16 GiB");
1256 constexpr auto page = static_cast<std::size_t>(units::pages::ratio::num);
1257 if (detail::heap_mapping::page_size() != page)
1258 detail::heap_failure("platform page size differs from build invariant");
1259 if (!valid_options(config) || page < 512 || page > maximum_bytes || !std::has_single_bit(page))
1260 detail::heap_failure("invalid byte sizes, page size or worker count");
1261 if (config.reserve > config.capacity / 2)
1262 detail::heap_failure("capacity must hold twice the reserve");
1263 auto const capacity = units::bytes{config.capacity}.count();
1264 auto const reserve = units::bytes{config.reserve}.count();
1265 return {page / 8, reserve / 8, 2 * reserve / 8, capacity, config};
1266 }
1267 explicit generation(settings s, detail::compactor const & c, detail::work_pool & w, std::byte * address) noexcept
1268 : policy(s.policy), compactor(c), workers(w),
1269 page_word_count(s.page_words), reserve_word_count(s.reserved),
1270 minimum_capacity(s.minimum), storage(s.bytes, address, units::bytes{s.policy.maximum}.count()),
1271 view_base(reinterpret_cast<word *>(storage.data())), metadata(s.bytes / (8 * block_words)) {
1272 alignment.resize(((metadata.size() + 7) / 8) * 4);
1273 }
1274 static constexpr unsigned exponent(std::size_t bytes) noexcept {
1275 if (!std::has_single_bit(bytes) || bytes < 8 || bytes > 64u)
1276 detail::heap_failure("unsupported record alignment");
1277 return std::countr_zero(bytes) - 3;
1278 }
1279 unsigned flags(std::size_t index) const noexcept {
1280 return (alignment[index >> 1] >> ((index & 1) * 4)) & 15;
1281 }
1282 native_inline void add_flags(std::size_t index, unsigned bits) noexcept {
1283 std::atomic_ref<std::uint8_t>{alignment[index >> 1]}.fetch_or(
1284 static_cast<std::uint8_t>(bits << ((index & 1) * 4)), std::memory_order_relaxed);
1285 }
1286 native_inline void note_record(offset first, std::size_t words, std::size_t bytes) noexcept {
1287 assert(std::has_single_bit(bytes) && bytes >= 8 && bytes <= 64);
1288 assert((static_cast<std::size_t>(first) * 8) % bytes == 0);
1289 if (!words) return;
1290 auto const k = std::countr_zero(bytes) - 3;
1291 if (k) add_flags(first >> block_shift, (1u << k) - 1);
1292 auto const last = (static_cast<std::size_t>(first) + words - 1) >> block_shift;
1293 for (auto i = static_cast<std::size_t>(first) >> block_shift; i < last; ++i) add_flags(i, 8);
1294 }
1295 native_inline void mark_range(offset first, std::size_t words) noexcept {
1296 while (words != 0) {
1297 auto const bit = static_cast<std::size_t>(first & (block_words - 1));
1298 auto const count = std::min(words, block_words - bit);
1299 std::atomic_ref<bitmap>{metadata[first >> block_shift].live}.fetch_or(
1300 static_cast<bitmap>((~bitmap{0} >> (block_words - count)) << bit), std::memory_order_relaxed);
1301 first += static_cast<offset>(count);
1302 words -= count;
1303 }
1304 }
1305 static constexpr word pointer_cells(bitmap live) noexcept {
1306 word x = live;
1307 x = (x | (x << 16)) & 0x0000ffff0000ffffULL;
1308 x = (x | (x << 8)) & 0x00ff00ff00ff00ffULL;
1309 x = (x | (x << 4)) & 0x0f0f0f0f0f0f0f0fULL;
1310 x = (x | (x << 2)) & 0x3333333333333333ULL;
1311 x = (x | (x << 1)) & 0x5555555555555555ULL;
1312 return x | (x << 1);
1313 }
1314 std::uint8_t const * alignment_table() const noexcept {
1315 return has_alignment ? alignment.data() : nullptr;
1316 }
1317 [[nodiscard]] offset base(block const & item) const noexcept {
1318 return item.destination;
1319 }
1320 [[nodiscard]] offset forward(offset value) const noexcept {
1321 if (value == null) return null;
1322 assert(value < cursor);
1323 auto const & item = metadata[value >> block_shift];
1324 auto const bit = static_cast<bitmap>(bitmap{1} << (value & (block_words - 1)));
1325 if (!(item.live & bit)) return null;
1326 return base(item) + static_cast<offset>(std::popcount(detail::retained(item, alignment_table(), value >> block_shift) & (bit - 1)));
1327 }
1328 void resize(std::size_t words) noexcept {
1329 assert(words % page_word_count == 0 && words >= minimum_capacity && words <= maximum_bytes / 8);
1330 assert(cursor <= words - reserve_word_count);
1331 std::vector<block> next_metadata(words / block_words);
1332 std::copy_n(metadata.begin(), (cursor + block_words - 1) / block_words, next_metadata.begin());
1333 auto const next_origin = words - reserve_word_count;
1334 auto next = storage.resized(origin * 8, cursor * 8, words * 8, next_origin * 8);
1335 alignment.resize(((next_metadata.size() + 7) / 8) * 4);
1336 storage.replace(next); metadata.swap(next_metadata);
1337 origin = next_origin;
1338 view_base = reinterpret_cast<word *>(storage.data()) + origin;
1339 }
1340
1341 generation(generation_options config, detail::compactor const & c, detail::work_pool & w, std::byte * address)
1342 : generation(configure(config), c, w, address) {}
1343
1344 public:
1346 [[nodiscard]] std::size_t capacity() const noexcept { return storage.bytes() / 8; }
1348 [[nodiscard]] std::size_t reserved() const noexcept { return reserve_word_count; }
1350 [[nodiscard]] std::size_t used() const noexcept { return cursor; }
1352 [[nodiscard]] std::size_t start() const noexcept { return origin; }
1354 [[nodiscard]] std::size_t page_words() const noexcept { return page_word_count; }
1356 template<class Self>
1357 [[nodiscard]] native_inline native_pure
1358 auto data(this Self & self native_lifetimebound) noexcept
1359 -> std::conditional_t<std::is_const_v<Self>, word const, word> * {
1360 using cell = std::conditional_t<std::is_const_v<Self>, word const, word>;
1361 return static_cast<cell *>(self.view_base);
1362 }
1363
1364 template<class Self>
1365 [[nodiscard]] decltype(auto) operator[](this Self & self native_lifetimebound, offset index) noexcept {
1366 assert(index < self.cursor); return self.data()[index];
1367 }
1368
1374 [[nodiscard]] offset field(offset cell, unsigned slot = 0) const noexcept {
1375 assert(cell < cursor && slot < fields_per_word);
1376 offset value;
1377 auto const address = reinterpret_cast<std::byte const *>(data() + cell) + slot * sizeof(offset);
1378 std::memcpy(&value, address, sizeof(value));
1379 return value;
1380 }
1381
1389 void set_field(offset cell, offset value, unsigned slot = 0) noexcept {
1390 require_mutator();
1391 assert(cell < cursor && slot < fields_per_word);
1392 auto const address = reinterpret_cast<std::byte *>(data() + cell) + slot * sizeof(offset);
1393 std::memcpy(address, &value, sizeof(value));
1394 }
1395
1396 [[nodiscard]] std::span<block const> blocks() const noexcept { return metadata; }
1406 [[nodiscard]] offset allocate(std::size_t words, std::size_t alignment_bytes = 8) noexcept
1407 native_diagnose_if(words == 0, "jam records must contain at least one cell")
1408 native_diagnose_if(!std::has_single_bit(alignment_bytes) || alignment_bytes < 8 || alignment_bytes > 64u, "unsupported jam record alignment") {
1409 require_mutator();
1410 auto const k = exponent(alignment_bytes);
1411 auto const aligned = (cursor + (std::size_t{1} << k) - 1) & ~((std::size_t{1} << k) - 1);
1412 if (!words) detail::heap_failure("zero-word allocation");
1413 auto const limit = units::bytes{policy.maximum}.count() / 8 - reserve_word_count;
1414 if (aligned > limit || words > limit - aligned)
1415 detail::heap_failure("capacity overflow");
1416 while (words > capacity() - reserve_word_count - std::min(aligned, capacity() - reserve_word_count))
1417 resize(std::min(capacity() * 2, units::bytes{policy.maximum}.count() / 8));
1418 std::fill(data() + cursor, data() + aligned, word{0});
1419 cursor = aligned + words;
1420 return static_cast<offset>(aligned);
1421 }
1422
1423 void clear_marks() noexcept {
1424 for (auto & item : metadata) {
1425 item.live = 0;
1426 item.destination = 0;
1427 }
1428 std::fill(alignment.begin(), alignment.end(), 0);
1429 has_alignment = false;
1430 }
1431
1439 void mark(offset first, std::size_t words = 1, std::size_t alignment_bytes = 8) noexcept {
1440 assert(first != null && first <= cursor && words <= cursor - first);
1441 note_record(first, words, alignment_bytes); mark_range(first, words);
1442 }
1443
1450 [[nodiscard]] native_inline bool claim(offset first, std::size_t words = 1,
1451 std::size_t alignment_bytes = 8) noexcept {
1452 assert(first != null && words && first < cursor && words <= cursor - first);
1453 auto const bit = static_cast<bitmap>(bitmap{1} << (first & (block_words - 1)));
1454 auto const previous = std::atomic_ref<bitmap>{metadata[first >> block_shift].live}
1455 .fetch_or(bit, std::memory_order_relaxed);
1456 if ((previous & bit) != 0) return false;
1457 note_record(first, words, alignment_bytes);
1458 mark_range(first + 1, words - 1);
1459 return true;
1460 }
1461
1468 native_inline void pointer(offset where, unsigned slot = 0) noexcept {
1469 assert(where != null && where < cursor && slot < fields_per_word);
1470 auto const bit = (where & (block_words - 1)) * fields_per_word + slot;
1471 std::atomic_ref<word>{metadata[where >> block_shift].pointers}
1472 .fetch_or(word{1} << bit, std::memory_order_relaxed);
1473 }
1474
1488 void compact(std::span<offset> roots = {}) noexcept {
1489 require_mutator();
1490 std::atomic_signal_fence(std::memory_order_seq_cst);
1491 collecting = true;
1492 compact_impl(roots);
1493 collecting = false;
1494 std::atomic_signal_fence(std::memory_order_seq_cst);
1495 }
1496
1497 private:
1498 std::size_t prepare(std::size_t start, bool keep_null) noexcept {
1499 // Keep the null cell in the packed prefix, including aligned collections.
1500 // It is padding, never a record or a source of outgoing pointers.
1501 data()[0] = 0;
1502 if (keep_null) metadata[0].live |= bitmap{1};
1503 else metadata[0].live &= ~bitmap{1};
1504 metadata[0].pointers &= ~word{(1u << fields_per_word) - 1};
1505 auto const old_blocks = (cursor + block_words - 1) / block_words;
1506 // A parallel prefix scan could compose joined groups' layout functions:
1507 // F(x) = align_up(x, 1 << k) + retained_cells_after_dilation. Resolve group
1508 // boundaries/k first; composing (max k, summed counts) loses padding.
1509 // Eight input-residue displacements suffice (k <= 3), with associative
1510 // composition c[r] = a[r] + b[(r + a[r]) & 7]. All destinations must be
1511 // ready before workers forward arbitrary pointers into distant blocks.
1512 // This whole pass measured only 0.5-1.7% of GC on eight-byte-aligned trees
1513 // and chains with 1/4 workers, so scan overhead could erase the benefit.
1514 // Sparse retention and higher alignments were not covered; reprofile those
1515 // workloads before adding a parallel scan.
1516 has_alignment = false;
1517 std::size_t live_words = start;
1518 for (std::size_t begin = 0; begin < old_blocks;) {
1519 auto end = begin + 1;
1520 unsigned k = std::popcount(flags(begin) & 7u);
1521 while ((flags(end - 1) & 8u) && end < old_blocks) {
1522 k = std::max(k, static_cast<unsigned>(std::popcount(flags(end) & 7u))); ++end;
1523 }
1524 live_words = (live_words + (std::size_t{1} << k) - 1) & ~((std::size_t{1} << k) - 1);
1525 for (auto i = begin; i != end; ++i) {
1526 auto & item = metadata[i];
1527 item.pointers &= pointer_cells(item.live);
1528 // Publish the joined group's effective k in the existing nibbles.
1529 // live remains the exact claims; dilation is only a register value.
1530 auto const shift = (i & 1) * 4;
1531 alignment[i >> 1] = static_cast<std::uint8_t>((alignment[i >> 1] & ~(7u << shift))
1532 | (((1u << k) - 1) << shift));
1533 has_alignment |= k != 0;
1534 item.destination = static_cast<offset>(live_words);
1535 live_words += std::popcount(detail::dilate(item.live, k));
1536 }
1537 begin = end;
1538 }
1539 // Dilation can retain unallocated cells at the final record's rounded end.
1540 auto const padded = std::min(capacity() - reserve_word_count, (cursor + 7) & ~std::size_t{7});
1541 std::fill(data() + cursor, data() + padded, word{0});
1542
1543 return live_words;
1544 }
1545 void move_to(word * target, detail::forwarding_tables tables) noexcept {
1546 auto const old_blocks = (cursor + block_words - 1) / block_words;
1547 auto const source = data();
1548 auto const source_pages = (cursor + page_word_count - 1) / page_word_count;
1549 auto const wave_pages = reserve_word_count / page_word_count;
1550 auto move_page = [&](std::size_t page) noexcept {
1551 auto const begin = page * (page_word_count / block_words);
1552 auto const end = std::min(old_blocks, begin + page_word_count / block_words);
1553 compactor.move(source, target, metadata.data(), begin, end, tables, alignment_table());
1554 };
1555 if (workers.available_workers() > 1 && source_pages > 1) {
1556 auto done = std::make_unique<std::atomic<bool>[]>(source_pages);
1557 std::atomic<std::size_t> next{0}, front{0};
1558 auto task = [&](std::size_t) noexcept {
1559 for (;;) {
1560 auto const page = next.fetch_add(1, std::memory_order_relaxed);
1561 if (page >= source_pages) return;
1562 auto floor = front.load(std::memory_order_acquire);
1563 while (page - floor >= wave_pages) {
1564 front.wait(floor, std::memory_order_relaxed);
1565 floor = front.load(std::memory_order_acquire);
1566 }
1567 move_page(page);
1568 done[page].store(true, std::memory_order_release);
1569 // Every completion publishes through this RMW chain, even when an
1570 // older page still blocks the prefix. The last publisher therefore
1571 // sees all earlier done flags; a store-buffering cycle cannot stall.
1572 floor = front.fetch_add(0, std::memory_order_acq_rel);
1573 for (;;) {
1574 auto end = floor;
1575 while (end != source_pages && done[end].load(std::memory_order_acquire)) ++end;
1576 if (end == floor) break;
1577 if (front.compare_exchange_strong(floor, end, std::memory_order_acq_rel)) {
1578 front.notify_all(); break;
1579 }
1580 floor = front.load(std::memory_order_acquire);
1581 }
1582 }
1583 };
1584 workers.run(std::min(workers.available_workers(), wave_pages),
1585 [](void * context, std::size_t i) noexcept {
1586 (*static_cast<decltype(task) *>(context))(i);
1587 }, &task);
1588 } else for (std::size_t page = 0; page < source_pages; ++page) move_page(page);
1589 }
1590 void zero_gaps(word * target, std::size_t previous_end) const noexcept {
1591 auto const old_blocks = (cursor + block_words - 1) / block_words;
1592 for (std::size_t i = 0; i != old_blocks; ++i) {
1593 auto const next = std::size_t(metadata[i].destination & offset_mask);
1594 std::fill(target + previous_end, target + next, word{0});
1595 previous_end = next + std::popcount(detail::retained(metadata[i], alignment_table(), i));
1596 }
1597 }
1598 void pack_into(std::vector<block> & output) const noexcept {
1599 auto const old_blocks = (cursor + block_words - 1) / block_words;
1600 compactor.pack(metadata.data(), output.data(), old_blocks, alignment_table());
1601 }
1602 void ensure(std::size_t words) noexcept {
1603 auto const maximum = units::bytes{policy.maximum}.count() / sizeof(word);
1604 if (words > maximum - reserve_word_count) detail::heap_failure("generation capacity overflow");
1605 while (words > capacity() - reserve_word_count) resize(std::min(2 * capacity(), maximum));
1606 }
1607 std::size_t rotated_origin() const noexcept {
1608 return (origin + capacity() - reserve_word_count) % capacity();
1609 }
1610 word * at_origin(std::size_t start) const noexcept {
1611 return reinterpret_cast<word *>(storage.data()) + start;
1612 }
1613 void adopt(std::size_t next_origin, std::size_t words) noexcept {
1614 auto const old_blocks = (cursor + block_words - 1) / block_words;
1615 cursor = words; origin = next_origin; view_base = at_origin(origin);
1616 auto const blocks = (cursor + block_words - 1) / block_words;
1617 for (std::size_t i = 0; i != blocks; ++i) {
1618 auto const count = std::min(block_words, cursor - i * block_words);
1619 metadata[i].live = ~bitmap{0} >> (block_words - count);
1620 metadata[i].destination = 0;
1621 }
1622 for (auto i = blocks; i < old_blocks; ++i) metadata[i] = {};
1623 std::fill(alignment.begin(), alignment.end(), 0);
1624 has_alignment = false;
1625 auto const smaller = std::max(minimum_capacity, (capacity() / page_word_count / 2) * page_word_count);
1626 if (policy.shrink_shift && smaller < capacity()
1627 && cursor < (capacity() >> policy.shrink_shift)
1628 && cursor <= smaller - reserve_word_count) resize(smaller);
1629 }
1630 void reset() noexcept {
1631 for (auto & item : metadata) item = {};
1632 std::fill(alignment.begin(), alignment.end(), 0);
1633 has_alignment = false;
1634 cursor = 1; data()[0] = 0;
1635 }
1636 void compact_impl(std::span<offset> roots) noexcept {
1637 auto const live_words = prepare(0, true);
1638 auto const next_origin = rotated_origin();
1639 auto * target = at_origin(next_origin);
1640 move_to(target, {metadata.data(), nullptr, alignment_table(), nullptr});
1641 for (auto & root : roots) root = forward(root);
1642 zero_gaps(target, 0);
1643 pack_into(metadata);
1644 adopt(next_origin, live_words);
1645 }
1646 };
1647private:
1648 detail::compactor const compactor = detail::choose_compactor();
1649 detail::work_pool workers;
1650 detail::generation_reservation reservation;
1651 generation arena;
1652 generation nursery;
1653 heap_options const policy;
1654 std::size_t minors_left;
1655 struct root_slot {
1656 offset position;
1657 std::uint32_t copies, previous, next;
1658 trace_function trace;
1659 };
1660 static_assert(sizeof(root_slot) == 24);
1661 std::vector<root_slot> root_slots;
1662 std::uint32_t roots_head = 0, free_root = 0;
1663 native_inline std::uint32_t add_root(offset at, trace_function trace) noexcept {
1664 assert(heap::current() == this);
1665 require_mutator();
1666 if (!at) return 0;
1667 auto slot = free_root;
1668 if (slot) free_root = root_slots[slot - 1].next;
1669 else {
1670 if (root_slots.size() == std::numeric_limits<std::uint32_t>::max())
1671 detail::heap_failure("root slot capacity overflow");
1672 root_slots.push_back({});
1673 slot = static_cast<std::uint32_t>(root_slots.size());
1674 }
1675 root_slots[slot - 1] = {at, 1, 0, roots_head, trace};
1676 if (roots_head) root_slots[roots_head - 1].previous = slot;
1677 roots_head = slot;
1678 return slot;
1679 }
1680 native_inline void retain_root(std::uint32_t slot) noexcept {
1681 require_mutator();
1682 auto & count = root_slots[slot - 1].copies;
1683 if (count == std::numeric_limits<std::uint32_t>::max())
1684 detail::heap_failure("root reference count overflow");
1685 ++count;
1686 }
1687 native_inline void release_root(std::uint32_t slot) noexcept {
1688 require_mutator();
1689 auto & entry = root_slots[slot - 1];
1690 if (--entry.copies) return;
1691 if (entry.previous) root_slots[entry.previous - 1].next = entry.next;
1692 else roots_head = entry.next;
1693 if (entry.next) root_slots[entry.next - 1].previous = entry.previous;
1694 entry = {0, 0, 0, free_root, nullptr};
1695 free_root = slot;
1696 }
1697 std::vector<std::shared_ptr<weak_entry>> weak_entries;
1698 std::deque<std::shared_ptr<weak_entry>> pending_finalizers;
1699 weak_entry * running_finalizer = nullptr;
1700 bool collecting = false;
1701 bool minor_collection = false;
1702 std::unordered_map<std::uint32_t, trace_function> remembered;
1703 // Callback identity distinguishes weak slots from raw strong slots (nullptr).
1704 // Weak slots participate in forwarding, never in the initial marking frontier.
1705 static void weak_slot(visitor &, offset) noexcept {}
1706 static constexpr unsigned block_shift = 5;
1707 static constexpr offset young_bit = 0x80000000u;
1708 static constexpr offset offset_mask = 0x7fffffffu;
1709 static heap_options configure(heap_options config) {
1710 static_cast<void>(generation::configure(config.old));
1711 static_cast<void>(generation::configure(config.young));
1712 if (!config.workers) detail::heap_failure("worker count must be positive");
1713 return config;
1714 }
1715 struct configured {};
1716 heap(configured, heap_options config) noexcept
1717 : workers(config.workers),
1718 reservation(units::bytes{config.old.maximum}.count(), units::bytes{config.young.maximum}.count()),
1719 arena(config.old, compactor, workers, reservation.data()),
1720 nursery(config.young, compactor, workers, reservation.data() + 2 * units::bytes{config.old.maximum}.count()),
1721 policy(config), minors_left(config.minor_collections) {}
1722 [[nodiscard]] native_inline generation & containing(offset at) noexcept { return at & young_bit ? nursery : arena; }
1723 [[nodiscard]] native_inline generation const & containing(offset at) const noexcept { return at & young_bit ? nursery : arena; }
1724 template<class Self>
1725 [[nodiscard]] native_inline native_pure
1726 auto decode(this Self & self native_lifetimebound, offset bits) noexcept
1727 -> std::conditional_t<std::is_const_v<Self>, std::byte const, std::byte> * {
1728 using byte = std::conditional_t<std::is_const_v<Self>, std::byte const, std::byte>;
1729 // Carry selects the generation; the wrapped sum strips its tag and doubles the cell offset.
1730 std::uint32_t doubled;
1731 auto * base = (__builtin_add_overflow(bits, bits, &doubled) ? self.nursery : self.arena).data();
1732 return reinterpret_cast<byte *>(base) + std::size_t(doubled) * 4;
1733 }
1734 native_inline bool old_slot(void const * address, std::uint32_t & slot) const noexcept {
1735 auto const delta = reinterpret_cast<std::uintptr_t>(address) - reinterpret_cast<std::uintptr_t>(arena.storage.data());
1736 auto const bytes = arena.storage.bytes();
1737 if (delta >= 2 * bytes) return false;
1738 auto const physical = delta >= bytes ? delta - bytes : delta;
1739 auto const start = arena.origin * sizeof(word);
1740 auto const logical = physical >= start ? physical - start : physical + bytes - start;
1741 assert(logical % sizeof(offset) == 0 && logical / sizeof(offset) < 2 * arena.cursor);
1742 slot = static_cast<std::uint32_t>(logical / sizeof(offset));
1743 return true;
1744 }
1745 native_inline void forget(void const * address) noexcept {
1746 std::uint32_t slot;
1747 if (old_slot(address, slot)) forget_slot(slot);
1748 }
1749 native_noinline void forget_slot(std::uint32_t slot) noexcept {
1750 remembered.erase(slot);
1751 }
1752 native_noinline void remember_slot(std::uint32_t slot, trace_function trace) noexcept {
1753 require_mutator();
1754 remembered.insert_or_assign(slot, trace);
1755 }
1756 void require_mutator() const noexcept {
1757 if (collecting) detail::heap_failure("mutation during collection");
1758 }
1759 offset forward(offset value, bool minor) const noexcept {
1760 if (!value || (minor && !(value & young_bit))) return value;
1761 return containing(value).forward(value & offset_mask);
1762 }
1763 // Read only with the marking frontier drained; workers may otherwise own these bits.
1764 bool marked(offset at) const noexcept {
1765 if (!at) return false;
1766 if (minor_collection && !(at & young_bit)) return true;
1767 auto const local = at & offset_mask;
1768 return (containing(at).metadata[local >> block_shift].live >> (local % block_words)) & 1;
1769 }
1770 void run_finalizer(std::shared_ptr<weak_entry> const & entry) noexcept {
1771 entry->previous_running = running_finalizer;
1772 running_finalizer = entry.get();
1773 {
1774 heap_scope binding{*this};
1775 entry->invoke(*this, *entry);
1776 }
1777 running_finalizer = entry->previous_running;
1778 }
1779 void drain_finalizers() noexcept {
1780 if (running_finalizer) return;
1781 while (!pending_finalizers.empty()) {
1782 auto entry = std::move(pending_finalizers.front());
1783 pending_finalizers.pop_front();
1784 run_finalizer(entry);
1785 }
1786 }
1787 void finalize(std::shared_ptr<weak_entry> const & entry) noexcept {
1788 require_mutator();
1789 if (!entry->active) return;
1790 entry->active = false;
1791 std::erase(weak_entries, entry);
1792 run_finalizer(entry);
1793 drain_finalizers();
1794 }
1795 void compact_impl(std::span<offset> roots, bool minor = false, bool promote = false) noexcept {
1796 auto const old_end = minor ? arena.cursor : arena.prepare(0, true);
1797 auto const young_start = minor && !promote ? std::size_t(young_bit) : old_end;
1798 auto const end = nursery.prepare(young_start, minor && !promote);
1799 auto & destination = minor && !promote ? nursery : arena;
1800 auto const live_words = end - (minor && !promote ? std::size_t(young_bit) : 0);
1801 destination.ensure(live_words);
1802 // Growth may rebase metadata, so form the shared forwarding tables afterward.
1803 detail::forwarding_tables tables{minor ? nullptr : arena.metadata.data(), nursery.metadata.data(),
1804 minor ? nullptr : arena.alignment_table(), nursery.alignment_table()};
1805 auto const next_origin = minor && promote ? destination.origin : destination.rotated_origin();
1806 auto * target = destination.at_origin(next_origin);
1807 if (!minor) {
1808 arena.move_to(target, tables);
1809 arena.zero_gaps(target, 0);
1810 }
1811 nursery.move_to(target, tables);
1812 nursery.zero_gaps(target, minor && !promote ? 0 : old_end);
1813 for (auto & root : roots) root = forward(root, minor);
1814 for (auto slot = roots_head; slot; slot = root_slots[slot - 1].next) {
1815 auto & entry = root_slots[slot - 1];
1816 entry.position = forward(entry.position, minor);
1817 }
1818 if (minor) for (auto entry = remembered.begin(); entry != remembered.end();) {
1819 auto const slot = entry->first;
1820 auto * address = reinterpret_cast<std::byte *>(arena.data()) + std::size_t(slot) * sizeof(offset);
1821 offset value;
1822 std::memcpy(&value, address, sizeof(value));
1823 value = forward(value, true);
1824 std::memcpy(address, &value, sizeof(value)); // Collector writes bypass barriers.
1825 if (value) ++entry; else entry = remembered.erase(entry);
1826 }
1827 auto forward_entry = [&](weak_entry & entry) noexcept {
1828 entry.key.at = forward(entry.key.at, minor);
1829 entry.value.at = entry.active ? forward(entry.value.at, minor) : null;
1830 entry.finalizer.at = forward(entry.finalizer.at, minor);
1831 };
1832 for (auto const & entry : weak_entries) forward_entry(*entry);
1833 for (auto const & entry : pending_finalizers) forward_entry(*entry);
1834 for (auto * entry = running_finalizer; entry; entry = entry->previous_running) forward_entry(*entry);
1835 // Both generations and every external/remembered root have consumed the
1836 // forwarding tables. Packing may now overwrite their pointer masks.
1837 if (!minor) arena.pack_into(arena.metadata);
1838 nursery.pack_into(destination.metadata);
1839 destination.adopt(next_origin, live_words);
1840 if (!minor || promote) { nursery.reset(); remembered.clear(); }
1841 }
1842
1843public:
1847 class host {
1848 heap & owner;
1849 std::size_t const prefix;
1850 enum class phase { idle, marking, retry, prepared };
1851 phase state = phase::idle;
1852 std::size_t old_words = 0, young_words = 0;
1853 bool promoting = false;
1854 public:
1856 enum class generation { old, young };
1861 explicit host(heap & storage, std::size_t prefix) noexcept : owner(storage), prefix(prefix) {
1862 owner.require_mutator();
1863 for (auto * g : {&owner.arena, &owner.nursery}) {
1864 if (g->used() != 1 || g->policy.capacity != g->policy.maximum || g->policy.shrink_shift ||
1865 !prefix || prefix % g->page_words() || prefix >= g->capacity() - g->reserved())
1866 detail::heap_failure("host requires fresh fixed generations and a whole-page guard");
1867 }
1868 static_cast<void>(owner.arena.allocate(prefix - 1));
1869 static_cast<void>(owner.nursery.allocate(prefix - 1));
1870 }
1871 host(host const &) = delete;
1872 host & operator=(host const &) = delete;
1874 ~host() noexcept {
1875 if (state != phase::idle) detail::heap_failure("unfinished host collection");
1876 }
1877
1880 void publish(generation which, void * target, std::size_t skip, std::size_t count) const noexcept {
1881 if (state != phase::idle) detail::heap_failure("publish during host collection");
1882 auto const & g = which == generation::young ? owner.nursery : owner.arena;
1883 auto const & config = which == generation::young ? owner.policy.young : owner.policy.old;
1884 if (config.capacity != config.maximum || config.shrink_shift ||
1885 skip > g.capacity() || count > g.capacity() - skip)
1886 detail::heap_failure("published heap requires a fixed generation");
1887 g.storage.publish(((g.origin + skip) % g.capacity()) * 8,
1888 static_cast<std::byte *>(target), count * 8);
1889 }
1890
1893 [[nodiscard]] offset allocate(generation which, std::size_t words) noexcept {
1894 owner.require_mutator();
1895 return (which == generation::young ? owner.nursery.allocate(words) : owner.arena.allocate(words)) | (which == generation::young ? young_bit : 0);
1896 }
1897
1899 void begin(bool minor) noexcept {
1900 owner.require_mutator();
1901 if (state != phase::idle) detail::heap_failure("host begin outside idle phase");
1902 if (owner.roots_head || !owner.weak_entries.empty() || !owner.pending_finalizers.empty() ||
1903 owner.running_finalizer || !owner.remembered.empty())
1904 detail::heap_failure("host collection requires exclusively VM-owned roots");
1905 std::atomic_signal_fence(std::memory_order_seq_cst);
1906 owner.collecting = true;
1907 owner.minor_collection = minor;
1908 state = phase::marking;
1909 owner.nursery.clear_marks();
1910 for (auto & item : owner.nursery.metadata) item.pointers = 0;
1911 if (!minor) {
1912 owner.arena.clear_marks();
1913 for (auto & item : owner.arena.metadata) item.pointers = 0;
1914 }
1915 }
1916
1920 template<class Trace>
1921 void trace(std::span<offset const> roots, Trace && trace,
1922 std::size_t marker_limit = 1, bool old_owners = false) noexcept
1923 requires std::is_nothrow_invocable_v<Trace &, visitor &, offset> {
1924 if (state != phase::marking || !marker_limit ||
1925 (old_owners && (!owner.minor_collection || marker_limit != 1)))
1926 detail::heap_failure("host trace outside marking phase");
1927 std::vector<trace_job> initial;
1928 initial.reserve(roots.size());
1929 for (auto at : roots)
1930 if (at && (!owner.minor_collection || old_owners || (at & young_bit))) initial.push_back({at, nullptr});
1931 owner.drain_jobs(trace, initial, marker_limit);
1932 }
1933
1934 [[nodiscard]] bool marked(offset at) const noexcept {
1935 if (state != phase::marking && state != phase::retry)
1936 detail::heap_failure("host liveness outside marking phase");
1937 return owner.marked(at);
1938 }
1939
1943 [[nodiscard]] bool prepare(bool promote = false) noexcept {
1944 if (state != phase::marking && state != phase::retry)
1945 detail::heap_failure("host prepare outside marking phase");
1946 promoting = owner.minor_collection && promote;
1947 if (!owner.minor_collection) {
1948 if (prefix > 1) owner.arena.mark(1, prefix - 1);
1949 old_words = owner.arena.prepare(0, true);
1950 } else old_words = owner.arena.cursor;
1951 if (promoting) {
1952 old_words = owner.nursery.prepare(owner.arena.cursor, false);
1953 if (old_words > owner.arena.capacity() - owner.arena.reserved()) {
1954 state = phase::retry;
1955 return false;
1956 }
1957 young_words = prefix;
1958 } else {
1959 if (prefix > 1) owner.nursery.mark(1, prefix - 1);
1960 young_words = owner.nursery.prepare(young_bit, true) - young_bit;
1961 }
1962 state = phase::prepared;
1963 return true;
1964 }
1965
1966 [[nodiscard]] offset forward(offset at) const noexcept {
1967 if (state != phase::prepared)
1968 detail::heap_failure("host forward outside prepared phase");
1969 return owner.forward(at, owner.minor_collection);
1970 }
1971
1974 void finish() noexcept {
1975 if (state != phase::prepared)
1976 detail::heap_failure("host finish outside prepared phase");
1977 detail::forwarding_tables tables{owner.minor_collection ? nullptr : owner.arena.metadata.data(), owner.nursery.metadata.data(),
1978 owner.minor_collection ? nullptr : owner.arena.alignment_table(), owner.nursery.alignment_table()};
1979 auto const old_origin = owner.minor_collection ? owner.arena.origin : owner.arena.rotated_origin();
1980 auto const young_origin = owner.nursery.rotated_origin();
1981 if (!owner.minor_collection) {
1982 auto * target = owner.arena.at_origin(old_origin);
1983 owner.arena.move_to(target, tables);
1984 owner.arena.zero_gaps(target, 0);
1985 }
1986 auto * target = promoting ? owner.arena.data() : owner.nursery.at_origin(young_origin);
1987 owner.nursery.move_to(target, tables);
1988 owner.nursery.zero_gaps(target, promoting ? owner.arena.cursor : 0);
1989 // Both moves consumed the original tables. Packing may overwrite them now.
1990 if (!owner.minor_collection) owner.arena.pack_into(owner.arena.metadata);
1991 owner.nursery.pack_into(promoting ? owner.arena.metadata : owner.nursery.metadata);
1992 if (!owner.minor_collection || promoting) owner.arena.adopt(old_origin, old_words);
1993 if (promoting) owner.nursery.reset();
1994 else owner.nursery.adopt(young_origin, young_words);
1995 owner.collecting = false;
1996 owner.minor_collection = false;
1997 state = phase::idle;
1998 if (promoting && young_words > 1)
1999 static_cast<void>(owner.nursery.allocate(young_words - 1));
2000 std::atomic_signal_fence(std::memory_order_seq_cst);
2001 }
2002 };
2003
2004 [[nodiscard]] char const * compactor_name() const noexcept { return compactor.name; }
2006 [[nodiscard]] std::size_t remembered_size() const noexcept { return remembered.size(); }
2008 template<class T> native_inline void remember(ptr<T> const & value) noexcept {
2009 if (!value.is_young()) return;
2010 std::uint32_t slot;
2011 if (!old_slot(&value, slot)) return;
2012 remember_slot(slot, &heap::template trace_record<T>);
2013 }
2014
2015 template<class T> native_inline void remember(std::span<ptr<T>> values) noexcept {
2016 if (values.empty()) return;
2017 std::uint32_t first;
2018 if (!old_slot(values.data(), first)) return;
2019 require_mutator();
2020 assert(std::size_t(first) + values.size() <= 2 * arena.cursor);
2021 for (std::size_t i = 0; i != values.size(); ++i)
2022 if (values[i].is_young()) remember_slot(first + static_cast<std::uint32_t>(i), &heap::template trace_record<T>);
2023 }
2024
2025 template<class T> native_inline void remember(weak_ptr<T> const & value) noexcept {
2026 std::uint32_t slot;
2027 if (!old_slot(&value, slot)) return;
2028 if (value.is_young()) remember_slot(slot, &weak_slot); else forget_slot(slot);
2029 }
2030
2031 template<class T> native_inline void remember(std::span<weak_ptr<T>> values) noexcept {
2032 if (values.empty()) return;
2033 std::uint32_t first;
2034 if (!old_slot(values.data(), first)) return;
2035 require_mutator();
2036 assert(std::size_t(first) + values.size() <= 2 * arena.cursor);
2037 for (std::size_t i = 0; i != values.size(); ++i) {
2038 auto const slot = first + static_cast<std::uint32_t>(i);
2039 if (values[i].is_young()) remember_slot(slot, &weak_slot); else forget_slot(slot);
2040 }
2041 }
2042
2047 explicit heap(heap_options config = {})
2048 : heap(configured{}, configure(config)) {}
2049
2050 [[nodiscard]] generation const & old() const noexcept { return arena; }
2052 [[nodiscard]] generation const & young() const noexcept { return nursery; }
2054 [[nodiscard]] heap_options configuration() const noexcept { return policy; }
2055 heap(heap const &) = delete;
2056 heap & operator=(heap const &) = delete;
2058 ~heap() noexcept {
2059 if (roots_head || running_finalizer) detail::heap_failure("heap destroyed with attached roots or callbacks");
2060 for (auto const & entry : weak_entries) entry->active = false;
2061 }
2062
2064 [[nodiscard]] native_inline root_handle root(offset at) noexcept {
2065 assert(at == null || (at & offset_mask) < containing(at).cursor);
2066 return root_handle{*this, at};
2067 }
2068
2071 template<class T>
2072 [[nodiscard]] native_inline jam::root<T> root(ptr<T> const & at) noexcept requires traceable<T> {
2073 require_record<T>();
2074 assert(!at || at.local_offset() < containing(at.get()).cursor);
2075 return jam::root<T>{root_handle{*this, at.get(), &heap::template trace_record<T>}};
2076 }
2077
2080 template<class T>
2081 [[nodiscard]] native_inline jam::weak_root<T> weak_root(ptr<T> const & at) noexcept requires traceable<T> {
2082 require_record<T>();
2083 assert(!at || at.local_offset() < containing(at.get()).cursor);
2084 return jam::weak_root<T>{root_handle{*this, at.get(), &weak_slot}};
2085 }
2086
2091 template<traceable K, traceable V, traceable F>
2092 [[nodiscard]] jam::weak<V> mk_weak(ptr<K> const & key, ptr<V> const & value,
2093 ptr<F> const & finalizer, std::type_identity_t<void (*)(F *) noexcept> runner) noexcept;
2101 void collect() noexcept {
2102 collect([](visitor &, offset) noexcept { detail::heap_failure("untyped target in typed collection"); });
2103 }
2104
2106 void collect_major() noexcept {
2107 collect_major([](visitor &, offset) noexcept { detail::heap_failure("untyped target in typed collection"); });
2108 }
2109
2111 void collect_minor(bool promote = false) noexcept {
2112 collect_minor([](visitor &, offset) noexcept { detail::heap_failure("untyped target in typed collection"); }, promote);
2113 }
2114
2126 template<class Trace>
2127 void collect(Trace && trace) noexcept
2128 requires std::is_nothrow_invocable_v<Trace &, visitor &, offset> {
2129 require_mutator();
2130 if (minors_left) {
2131 --minors_left;
2132 collect_minor(std::forward<Trace>(trace));
2133 } else collect_major(std::forward<Trace>(trace));
2134 }
2135
2138 template<class Trace>
2139 void collect_major(Trace && trace) noexcept
2140 requires std::is_nothrow_invocable_v<Trace &, visitor &, offset> {
2141 run_collection(std::forward<Trace>(trace), false, false);
2142 }
2143
2146 template<class Trace>
2147 void collect_minor(Trace && trace, bool promote = false) noexcept
2148 requires std::is_nothrow_invocable_v<Trace &, visitor &, offset> {
2149 run_collection(std::forward<Trace>(trace), true, promote);
2150 }
2151private:
2152 template<class Trace>
2153 void drain_jobs(Trace & trace, std::vector<trace_job> & initial, std::size_t marker_limit) noexcept {
2154 if (initial.empty()) return;
2155 auto const marker_count = std::min(marker_limit, workers.available_workers());
2156 detail::frontier<trace_job> pending{marker_count, initial};
2157 struct context {
2158 heap * arena;
2159 std::remove_reference_t<Trace> * trace;
2160 detail::frontier<trace_job> * pending;
2161 } state{this, std::addressof(trace), &pending};
2162 workers.run(marker_count, [](void * raw, std::size_t worker) noexcept {
2163 auto & state = *static_cast<context *>(raw);
2164 heap_scope binding{*state.arena};
2165 visitor visit{*state.arena, *state.pending, worker};
2166 trace_job job;
2167 while (state.pending->pop(worker, job)) {
2168 if (job.trace) job.trace(visit, job.at);
2169 else std::invoke(*state.trace, visit, job.at);
2170 }
2171 }, &state);
2172 initial.clear();
2173 }
2174 template<class Trace>
2175 void run_collection(Trace && trace, bool minor, bool promote) noexcept {
2176 require_mutator();
2177 // Compiler-only safepoint: publish mutator stores before tracing. Worker
2178 // synchronization is supplied by the pool; this is not a hardware fence.
2179 std::atomic_signal_fence(std::memory_order_seq_cst);
2180 collecting = true;
2181 minor_collection = minor;
2182 if (minor) nursery.clear_marks(); else clear_marks();
2183 // Each collection redeclares fields: conditional tracers may change which
2184 // slots are references. Raw clear_marks()/compact() retain their old contract.
2185 for (auto & item : nursery.metadata) item.pointers = 0;
2186 if (!minor) for (auto & item : arena.metadata) item.pointers = 0;
2187 std::vector<trace_job> initial;
2188 for (auto slot = roots_head; slot; slot = root_slots[slot - 1].next) {
2189 auto const & entry = root_slots[slot - 1];
2190 if (entry.trace != &weak_slot && entry.position != null && (!minor || (entry.position & young_bit)))
2191 initial.push_back({entry.position, entry.trace});
2192 }
2193 if (minor) for (auto const & [slot, callback] : remembered) {
2194 offset value;
2195 std::memcpy(&value, reinterpret_cast<std::byte const *>(arena.data()) + std::size_t(slot) * sizeof(offset), sizeof(value));
2196 if ((value & young_bit) && callback != &weak_slot) initial.push_back({value, callback});
2197 }
2198 auto seed = [&](trace_job job) noexcept {
2199 if (job.at && !marked(job.at)) initial.push_back(job);
2200 };
2201 auto seed_finalizer = [&](weak_entry const & entry) noexcept {
2202 seed(entry.key); seed(entry.finalizer);
2203 };
2204 for (auto const & entry : pending_finalizers) seed_finalizer(*entry);
2205 for (auto * entry = running_finalizer; entry; entry = entry->previous_running) seed_finalizer(*entry);
2206 auto drain = [&]() noexcept {
2207 drain_jobs(trace, initial, workers.available_workers());
2208 };
2209 drain();
2210 std::stable_sort(weak_entries.begin(), weak_entries.end(), [](auto const & a, auto const & b) noexcept {
2211 return a->key.at < b->key.at;
2212 });
2213 std::size_t retained = 0;
2214 for (auto const & entry : weak_entries) {
2215 if (marked(entry->key.at)) {
2216 seed(entry->value); seed(entry->finalizer);
2217 weak_entries[retained++] = entry;
2218 } else {
2219 // Retire before retaining K/F: this entry never rearms, even on resurrection.
2220 entry->active = false;
2221 pending_finalizers.push_back(entry);
2222 seed_finalizer(*entry);
2223 }
2224 drain();
2225 }
2226 weak_entries.resize(retained);
2227 compact_impl({}, minor, promote);
2228 collecting = false; minor_collection = false;
2229 if (!minor) minors_left = policy.minor_collections;
2230 std::atomic_signal_fence(std::memory_order_seq_cst);
2231 drain_finalizers();
2232 }
2233public:
2238 template<class T, class... Args>
2239 [[nodiscard]] ptr<T> mk(Args &&... args) noexcept requires traceable<T> {
2240 require_record<T>();
2241 static_assert(noexcept(T{std::forward<Args>(args)...}), "record construction must be noexcept");
2242 require_mutator();
2243 // Arguments can borrow the current nursery view. Construct before allocation
2244 // can remap it; transport the result under the managed byte-relocation contract.
2245 T const value{std::forward<Args>(args)...};
2246 auto const at = nursery.allocate(record_words<T>, std::max(alignof(T), sizeof(word)));
2247 std::fill_n(nursery.data() + at, record_words<T>, word{0});
2248 // Transport the complete representation, including a polymorphic vptr.
2249 std::memcpy(nursery.data() + at, static_cast<void const *>(&value), sizeof(T));
2250 return ptr<T>{young_bit | at};
2251 }
2252
2256 template<class T>
2257 [[nodiscard]] native_inline T * address(ptr<T> const & at) noexcept requires traceable<T> {
2258 require_record<T>();
2259 assert(!collecting);
2260 return record_address<T>(at.get());
2261 }
2262
2265 template<class T>
2266 [[nodiscard]] T load(ptr<T> const & at) const noexcept requires traceable<T> {
2267 require_record<T>();
2268 assert(at && at.local_offset() < containing(at.get()).cursor
2269 && record_words<T> <= containing(at.get()).cursor - at.local_offset());
2270 return *std::launder(reinterpret_cast<T const *>(decode(at.get())));
2271 }
2272
2275 template<class T>
2276 void store(ptr<T> const & at, T const & value) noexcept requires traceable<T> {
2277 require_record<T>();
2278 require_mutator();
2279 heap_scope binding{*this};
2280 auto * destination = record_address<T>(at.get());
2281 if (destination != &value) *destination = value;
2282 }
2283 [[nodiscard]] std::size_t capacity() const noexcept { return arena.capacity(); }
2284 [[nodiscard]] std::size_t reserved() const noexcept { return arena.reserved(); }
2285 [[nodiscard]] std::size_t used() const noexcept { return arena.used() + nursery.used() - 1; }
2286 [[nodiscard]] std::size_t start() const noexcept { return arena.start(); }
2287 [[nodiscard]] std::size_t page_words() const noexcept { return arena.page_words(); }
2288 template<class Self>
2289 [[nodiscard]] native_inline native_pure auto data(this Self & self) noexcept
2290 -> std::conditional_t<std::is_const_v<Self>, word const, word> * { return self.arena.data(); }
2291 template<class Self>
2292 [[nodiscard]] decltype(auto) operator[](this Self & self, offset index) noexcept { return self.containing(index)[index & offset_mask]; }
2293 [[nodiscard]] offset field(offset cell, unsigned slot = 0) const noexcept { return containing(cell).field(cell & offset_mask, slot); }
2294 void set_field(offset cell, offset value, unsigned slot = 0) noexcept { require_mutator(); containing(cell).set_field(cell & offset_mask, value, slot);
2295 if (!(cell & young_bit) && (value & young_bit)) remembered.insert_or_assign(cell * 2 + slot, nullptr); }
2296 [[nodiscard]] std::span<block const> blocks() const noexcept { return arena.blocks(); }
2297 [[nodiscard]] offset allocate(std::size_t words, std::size_t alignment_bytes = 8) noexcept {
2298 require_mutator(); return arena.allocate(words, alignment_bytes);
2299 }
2300 void clear_marks() noexcept { arena.clear_marks(); nursery.clear_marks(); }
2301 void mark(offset first, std::size_t words = 1, std::size_t alignment_bytes = 8) noexcept { containing(first).mark(first & offset_mask, words, alignment_bytes); }
2302 [[nodiscard]] native_inline bool claim(offset first, std::size_t words = 1, std::size_t alignment_bytes = 8) noexcept {
2303 if (minor_collection && !(first & young_bit)) return false;
2304 return containing(first).claim(first & offset_mask, words, alignment_bytes);
2305 }
2306 native_inline void pointer(offset where, unsigned slot = 0) noexcept { containing(where).pointer(where & offset_mask, slot); }
2307 void compact(std::span<offset> roots = {}) noexcept {
2308 require_mutator();
2309 if (!weak_entries.empty() || !pending_finalizers.empty() || running_finalizer)
2310 detail::heap_failure("raw compact requires no weak registrations or pending finalizers");
2311 std::atomic_signal_fence(std::memory_order_seq_cst);
2312 collecting = true; compact_impl(roots); collecting = false;
2313 std::atomic_signal_fence(std::memory_order_seq_cst);
2314 }
2315
2316};
2317
2318inline heap_scope::heap_scope(heap & arena) noexcept : previous(heap::current()) {
2319 detail::active_heap = &arena;
2320}
2321
2326export template<class T>
2327class root : private heap::root_handle {
2328 using base = heap::root_handle;
2329 friend class heap;
2330 native_inline explicit root(base value) noexcept : base(std::move(value)) {}
2331public:
2333 root() noexcept = default;
2336 native_inline root(ptr<T> const & value) noexcept requires traceable<T> : root(heap::current()->root(value)) {}
2338 template<class U> requires (!std::same_as<T, U>) && std::convertible_to<ptr<U>, ptr<T>>
2339 native_inline root(ptr<U> const & value) noexcept : root(ptr<T>{value}) {}
2340
2342 [[nodiscard]] native_inline ptr<T> get() const noexcept { return ptr<T>{base::get()}; }
2344 [[nodiscard]] native_inline operator ptr<T>() const noexcept { return get(); }
2346 [[nodiscard]] native_inline T * operator->() const noexcept { return get().operator->(); }
2348 [[nodiscard]] native_inline T & operator*() const noexcept { return *get(); }
2350 [[nodiscard]] native_inline explicit operator bool() const noexcept { return base::get() != heap::null; }
2351};
2352
2359export template<class T>
2360class weak_root : private heap::root_handle {
2361 using base = heap::root_handle;
2362 friend class heap;
2363 native_inline explicit weak_root(base value) noexcept : base(std::move(value)) {}
2364public:
2366 weak_root() noexcept = default;
2368 native_inline weak_root(ptr<T> const & value) noexcept requires traceable<T>
2369 : weak_root(heap::current()->weak_root(value)) {}
2370
2371 native_inline weak_root(root<T> const & value) noexcept requires traceable<T>
2372 : weak_root(value.get()) {}
2373
2374 template<class U> requires (!std::same_as<T, U>) && std::convertible_to<ptr<U>, ptr<T>>
2375 native_inline weak_root(ptr<U> const & value) noexcept : weak_root(ptr<T>{value}) {}
2377 [[nodiscard]] native_inline bool expired() const noexcept { return base::get() == heap::null; }
2380 [[nodiscard]] native_inline root<T> lock() const noexcept requires traceable<T> {
2381 if (expired()) return {};
2382 return heap::current()->root(ptr<T>{base::get()});
2383 }
2384
2385 native_inline void reset() noexcept { base::operator=(base{}); }
2387 native_inline void swap(weak_root & other) noexcept {
2388 std::swap(static_cast<base &>(*this), static_cast<base &>(other));
2389 }
2390
2391 friend native_inline void swap(weak_root & a, weak_root & b) noexcept { a.swap(b); }
2392};
2393
2398export template<class V>
2399class weak {
2400 friend class heap;
2401 std::shared_ptr<heap::weak_entry> entry;
2402 explicit weak(std::shared_ptr<heap::weak_entry> value) noexcept : entry(std::move(value)) {}
2403public:
2404 weak() noexcept = default;
2406 [[nodiscard]] bool expired() const noexcept { return !entry || !entry->active; }
2409 [[nodiscard]] native_inline root<V> lock() const noexcept {
2410 if (expired()) return {};
2411 return heap::current()->root(ptr<V>{entry->value.at});
2412 }
2413
2415 native_inline void finalize() const noexcept {
2416 // A callback may destroy the very handle used to invoke it.
2417 auto keep = entry;
2418 if (keep && keep->active) heap::current()->finalize(keep);
2419 }
2420};
2421
2422template<traceable K, traceable V, traceable F>
2423jam::weak<V> heap::mk_weak(ptr<K> const & key, ptr<V> const & value,
2424 ptr<F> const & finalizer, std::type_identity_t<void (*)(F *) noexcept> runner) noexcept {
2425 require_mutator();
2426 if (!runner) detail::heap_failure("mk_weak requires a runner");
2427 auto entry = std::make_shared<finalizer_entry<F>>();
2428 entry->key = {key.get(), &trace_record<K>};
2429 entry->value = {value.get(), &trace_record<V>};
2430 entry->finalizer = {finalizer.get(), &trace_record<F>};
2431 entry->runner = runner;
2432 entry->invoke = [](heap & owner, weak_entry & base) noexcept {
2433 auto & entry = static_cast<finalizer_entry<F> &>(base);
2434 entry.runner(entry.finalizer.at ? owner.template record_address<F>(entry.finalizer.at) : nullptr);
2435 };
2436 weak_entries.push_back(entry);
2437 return jam::weak<V>{std::move(entry)};
2438}
2439
2442export template<traceable K, traceable V, traceable F>
2443[[nodiscard]] native_inline weak<V> mk_weak(ptr<K> const & key, ptr<V> const & value,
2444 ptr<F> const & finalizer, std::type_identity_t<void (*)(F *) noexcept> runner) noexcept {
2445 return heap::current()->mk_weak(key, value, finalizer, runner);
2446}
2447
2449export template<traceable T, class... Args>
2450[[nodiscard]] native_inline ptr<T> mk(Args &&... args) noexcept {
2451 return heap::current()->mk<T>(std::forward<Args>(args)...);
2452}
2453
2455export template<class T, std::size_t N>
2456native_inline constexpr void unsafe_assign(std::array<ptr<T>, N> & target, std::array<ptr<T>, N> const & source) noexcept {
2457 for (std::size_t i = 0; i != N; ++i) target[i].unsafe_assign(source[i]);
2458}
2459
2460export template<class T, std::size_t N>
2461native_inline constexpr void assign(std::array<ptr<T>, N> & target, std::array<ptr<T>, N> const & source) noexcept {
2462 unsafe_assign(target, source);
2463 if !consteval { if (auto * h = heap::current()) h->remember(std::span<ptr<T>>{target}); }
2464}
2465
2467export template<class T, std::size_t N>
2468native_inline constexpr void unsafe_assign(std::array<weak_ptr<T>, N> & target,
2469 std::array<weak_ptr<T>, N> const & source) noexcept {
2470 for (std::size_t i = 0; i != N; ++i) target[i].unsafe_assign(source[i]);
2471}
2472
2473export template<class T, std::size_t N>
2474native_inline constexpr void assign(std::array<weak_ptr<T>, N> & target,
2475 std::array<weak_ptr<T>, N> const & source) noexcept {
2476 unsafe_assign(target, source);
2477 if !consteval { if (auto * h = heap::current()) h->remember(std::span<weak_ptr<T>>{target}); }
2478}
2479
2481export native_inline void collect() noexcept { heap::current()->collect(); }
2483export native_inline void collect_major() noexcept { heap::current()->collect_major(); }
2485export native_inline void collect_minor(bool promote = false) noexcept { heap::current()->collect_minor(promote); }
2486
2487template<class T>
2488constexpr void weak_ptr<T>::update_barrier() noexcept {
2489 if !consteval { if (auto * h = heap::current()) h->remember(*this); }
2490}
2491template<class T>
2492constexpr weak_ptr<T>::weak_ptr(ptr<T> const & value) noexcept : position(value.get()) {
2493 if (is_young()) update_barrier();
2494}
2495template<class T>
2496weak_ptr<T>::weak_ptr(root<T> const & value) noexcept : weak_ptr(value.get()) {}
2497template<class T>
2498constexpr weak_ptr<T>::weak_ptr(weak_ptr const & other) noexcept : position(other.position) {
2499 if (is_young()) update_barrier();
2500}
2501template<class T>
2502constexpr weak_ptr<T> & weak_ptr<T>::operator=(weak_ptr const & other) noexcept {
2503 auto const changed = position ^ other.position;
2504 position = other.position;
2505 if (changed & 0x80000000u) update_barrier();
2506 return *this;
2507}
2508template<class T>
2509constexpr weak_ptr<T>::weak_ptr(weak_ptr && other) noexcept : position(std::exchange(other.position, 0)) {
2510 if (is_young()) { update_barrier(); other.update_barrier(); }
2511}
2512template<class T>
2513constexpr weak_ptr<T> & weak_ptr<T>::operator=(weak_ptr && other) noexcept {
2514 if (this == &other) return *this;
2515 *this = static_cast<weak_ptr const &>(other);
2516 auto const previous = std::exchange(other.position, 0);
2517 if (previous & 0x80000000u) other.update_barrier();
2518 return *this;
2519}
2520template<class T>
2521constexpr weak_ptr<T>::~weak_ptr() noexcept {
2522 if !consteval { if (auto * h = heap::current()) h->forget(this); }
2523}
2524template<class T>
2525constexpr void weak_ptr<T>::swap(weak_ptr & other) noexcept {
2526 auto const changed = position ^ other.position;
2527 std::swap(position, other.position);
2528 if (changed & 0x80000000u) { update_barrier(); other.update_barrier(); }
2529}
2530template<class T>
2531root<T> weak_ptr<T>::lock() const noexcept requires traceable<T> {
2532 if (!position) return {};
2533 return root<T>{ptr<T>{position}};
2534}
2535
2536template<class T>
2537constexpr void ptr<T>::update_barrier() noexcept {
2538 if !consteval { if (auto * h = heap::current()) {
2539 if (is_young()) h->remember(*this); else h->forget(this);
2540 } }
2541}
2542template<class T>
2543constexpr ptr<T>::ptr(offset at) noexcept : position(at) {
2544 if (is_young()) update_barrier();
2545}
2546template<class T>
2547constexpr ptr<T>::ptr(ptr const & other) noexcept : ptr(other.position) {}
2548template<class T>
2549template<class U> requires (!std::same_as<T, U>) && std::derived_from<U, T>
2550 && detail::allocation_trace<T> && requires(T * p) { static_cast<U *>(p); }
2551constexpr ptr<T>::ptr(ptr<U> const & other) noexcept : ptr(other.get()) {
2552 if !consteval {
2553 assert(!other || !heap::current() || static_cast<void const *>(static_cast<T const *>(other.operator->()))
2554 == static_cast<void const *>(other.operator->()));
2555 }
2556}
2557template<class T>
2558constexpr ptr<T> & ptr<T>::operator=(ptr const & other) noexcept {
2559 auto const changed = position ^ other.position;
2560 position = other.position;
2561 // Remembered entries name a slot and T, not its current young target.
2562 if (changed & heap::young_bit) update_barrier();
2563 return *this;
2564}
2565template<class T>
2566constexpr ptr<T>::ptr(ptr && other) noexcept : position(std::exchange(other.position, 0)) {
2567 if (is_young()) { update_barrier(); other.update_barrier(); }
2568}
2569template<class T>
2570constexpr ptr<T> & ptr<T>::operator=(ptr && other) noexcept {
2571 if (this == &other) return *this;
2572 *this = static_cast<ptr const &>(other);
2573 auto const previous = std::exchange(other.position, 0);
2574 if (previous & heap::young_bit) other.update_barrier();
2575 return *this;
2576}
2577template<class T>
2578constexpr void ptr<T>::swap(ptr & other) noexcept {
2579 auto const changed = position ^ other.position;
2580 std::swap(position, other.position);
2581 if (changed & heap::young_bit) { update_barrier(); other.update_barrier(); }
2582}
2583template<class T>
2584constexpr ptr<T>::~ptr() noexcept {
2585 if !consteval { if (auto * h = heap::current()) h->forget(this); }
2586}
2587template<class T>
2588T * ptr<T>::operator->() const noexcept {
2589 return heap::current()->address(*this);
2590}
2591template<class T>
2592T & ptr<T>::operator*() const noexcept { return *operator->(); }
2593
2594template<class T>
2595native_inline void ptr<T>::prefetch_marks() const noexcept {
2596 if (!position) return;
2597 auto const * arena = heap::current();
2598 assert(arena && local_offset() < arena->containing(position).used());
2599 __builtin_prefetch(&arena->containing(position).blocks()[local_offset() >> heap::block_shift].live, 0, 3);
2600}
2601template<class T>
2602native_inline void ptr<T>::prefetch() const noexcept {
2603 if (!position) return;
2604 auto const * arena = heap::current();
2605 assert(arena && local_offset() < arena->containing(position).used());
2606 __builtin_prefetch(arena->decode(position), 0, 3);
2607}
2608}
Borrow a heap whose roots, barriers and reference policy belong to a host runtime.
Definition heap.ccm:1847
bool marked(offset at) const noexcept
Exact collected-generation liveness; old objects are live during minors.
Definition heap.ccm:1934
offset allocate(generation which, std::size_t words) noexcept
Allocate raw cells in either generation, preserving the tag bit. A host may subdivide the extent; sca...
Definition heap.ccm:1893
generation
Select the arena for raw allocation or publication.
Definition heap.ccm:1856
void trace(std::span< offset const > roots, Trace &&trace, std::size_t marker_limit=1, bool old_owners=false) noexcept
Drain roots using jam's ordinary frontier and persistent worker pool.
Definition heap.ccm:1921
host(heap &storage, std::size_t prefix) noexcept
Reserve an identical guard in both fresh, fixed-size generations.
Definition heap.ccm:1861
~host() noexcept
Release the capability after finishing its last collection.
Definition heap.ccm:1874
void begin(bool minor) noexcept
Start a VM-owned marking epoch; the VM supplies barriers and weak policy.
Definition heap.ccm:1899
void finish() noexcept
Move with the original SIMD kernel and pack only after both moves finish.
Definition heap.ccm:1974
bool prepare(bool promote=false) noexcept
Freeze both forwarding tables after resolving all reference policies.
Definition heap.ccm:1943
offset forward(offset at) const noexcept
Repair a root while original objects and both forwarding tables remain intact.
Definition heap.ccm:1966
void publish(generation which, void *target, std::size_t skip, std::size_t count) const noexcept
Publish one generation at a stable VM-owned address.
Definition heap.ccm:1880
A four-byte handle to a collector-updated external root slot.Copies share a registration; moves leave...
Definition heap.ccm:905
root_handle(root_handle &&other) noexcept
Transfer a registration, leaving the source detached.
Definition heap.ccm:922
offset get() const noexcept
Resolve the slot through the current heap; returned offsets expire on collection.
Definition heap.ccm:936
root_handle & operator=(root_handle &&other) noexcept
Transfer a registration in the same heap.
Definition heap.ccm:929
~root_handle() noexcept
Release this reference; the last release recycles the slot.
Definition heap.ccm:934
root_handle & operator=(root_handle const &other) noexcept
Replace this handle with a shared registration in the same heap.
Definition heap.ccm:924
root_handle() noexcept=default
Construct a detached null handle.
offset operator*() const noexcept
Read the current target offset.
Definition heap.ccm:940
void pointer(weak_ptr< T > const &value) noexcept
Declare a weak slot without following or claiming its target.
Definition heap.ccm:1093
void pointer(ptr< T > const &value) noexcept
Declare one pointer slot without following or claiming its target.
Definition heap.ccm:1085
void target(offset at) noexcept
Enqueue an external/wide target without declaring a narrow source slot.
Definition heap.ccm:1135
offset field(offset cell, unsigned slot=0) noexcept
Declare a managed field and enqueue its nonnull target for tracing.
Definition heap.ccm:1127
T const * claim_target(T const *value) noexcept
Claim the complete object from its allocation-level hook.
Definition heap.ccm:1065
T const * claim_target(ptr< T > const &value) noexcept
Claim a typed target without declaring the pointer's source slot.
Definition heap.ccm:1051
void operator()(Ts const &... values) noexcept
Visit parts in order, preserving their addresses; an empty pack does nothing.
Definition heap.ccm:1163
void pointers(word bits, std::size_t first_slot=0) noexcept
OR a pattern of pointer slots into the current record's metadata. Bit zero names the 32-bit slot at f...
Definition heap.ccm:1100
void operator()(T const &value) noexcept
Walk an embedded value without claiming cells again. Parts share the current allocation's pointer mas...
Definition heap.ccm:1142
T const * claim(ptr< T > const &value) noexcept
Declare a pointer slot, then claim its target as with claim_target(). Null and already-claimed target...
Definition heap.ccm:1119
void operator()(weak_ptr< T > const &value) noexcept
Declare a weak field for forwarding; never enqueue its target.
Definition heap.ccm:1160
ptr< T > operator()(ptr< T > const &value) noexcept
Declare a typed field and enqueue its nonnull target.
Definition heap.ccm:1151
void poll() noexcept
Service overdue stochastic donations without leaving this walker. Long cooperative hooks should poll ...
Definition heap.ccm:1169
heap heap_type
Heap accepted by this visitor.
Definition heap.ccm:1036
bool claim(offset start, std::size_t words=1, std::size_t alignment_bytes=8) noexcept
Claim a complete record; exactly one marker wins traversal.
Definition heap.ccm:1041
Bind an existing heap, restoring the previous binding on scope exit.The heap must outlive the scope....
Definition heap.ccm:485
~heap_scope() noexcept
Restore the binding saved at construction.
Definition heap.ccm:491
heap_scope(heap &arena) noexcept
Use the supplied heap for implicit operations on this thread.
Definition heap.ccm:2318
A circular, double-mapped arena with bounded parallel compaction.Compressed four-byte offsets address...
Definition heap.ccm:847
void remember(std::span< weak_ptr< T > > values) noexcept
Register weak slots after bulk unsafe assignment, dropping stale entries.
Definition heap.ccm:2031
void remember(ptr< T > const &value) noexcept
Explicitly register a pointer after a caller-managed unsafe write.
Definition heap.ccm:2008
generation const & young() const noexcept
Inspect generation one's arena.
Definition heap.ccm:2052
char const * compactor_name() const noexcept
Runtime-selected compaction implementation, including SIMD ISA admission.
Definition heap.ccm:2004
void remember(weak_ptr< T > const &value) noexcept
Register a weak old-to-young slot for rewriting, never for marking.
Definition heap.ccm:2025
void remember(std::span< ptr< T > > values) noexcept
Register a contiguous group after bulk unsafe assignment.
Definition heap.ccm:2015
jam::weak< V > mk_weak(ptr< K > const &key, ptr< V > const &value, ptr< F > const &finalizer, std::type_identity_t< void(*)(F *) noexcept > runner) noexcept
Associate a weak key and value with a managed finalizer and stateless runner.
Definition heap.ccm:2423
std::size_t remembered_size() const noexcept
Number of distinct old source slots retained in the current GC epoch.
Definition heap.ccm:2006
static heap * current() noexcept
Read the current thread's dynamically bound heap. Implicit operations require an active heap_scope....
Definition heap.ccm:857
jam::weak_ptr< T > weak_ptr
The weak field type for this heap.
Definition heap.ccm:877
generation const & old() const noexcept
Inspect generation zero's arena.
Definition heap.ccm:2050
root_handle root(offset at) noexcept
Register a record or null in the current heap.
Definition heap.ccm:2064
~heap() noexcept
Release the arena; every attached root must already be destroyed.
Definition heap.ccm:2058
A typed heap offset, with no root registration or ownership.
Definition heap.ccm:570
constexpr ptr & unsafe_assign(offset bits) noexcept
Install encoded bits without registration; the caller owns the barrier.
Definition heap.ccm:610
void prefetch() const noexcept
Prefetch the cache line containing the target's first byte for reading. Null does nothing,...
Definition heap.ccm:2602
constexpr void swap(ptr &other) noexcept
Exchange targets and update the remembered entries for both slots.
Definition heap.ccm:2578
constexpr ~ptr() noexcept
Remove a dying old pointer slot from the remembered set.
Definition heap.ccm:2584
T & operator*() const noexcept
Borrow the target in the current heap.
Definition heap.ccm:2592
T element_type
Target record type.
Definition heap.ccm:573
constexpr offset local_offset() const noexcept
Cell index within the target generation.
Definition heap.ccm:614
constexpr ptr() noexcept=default
Construct a null reference.
friend constexpr void swap(ptr &left, ptr &right) noexcept
Exchange pointers through ADL, preserving each slot's write barrier.
Definition heap.ccm:604
constexpr offset get() const noexcept
Read the cell offset; unregistered copies expire on collection.
Definition heap.ccm:617
std::uint32_t offset
Eight-byte cell index, stored in exactly four bytes.
Definition heap.ccm:575
void prefetch_marks() const noexcept
Prefetch the target's mark metadata without claiming or reading it. Null does nothing,...
Definition heap.ccm:2595
T * operator->() const noexcept
Borrow the target in the current heap; expires on growth or collection.
Definition heap.ccm:2588
constexpr bool is_young() const noexcept
Whether the target belongs to generation one.
Definition heap.ccm:612
constexpr ptr & unsafe_assign(ptr const &other) noexcept
Assign without a write barrier; the caller must register old-to-young edges.
Definition heap.ccm:608
A copyable external root, updated during collection.Four-byte slot index; no heap or registration add...
Definition heap.ccm:2327
root() noexcept=default
Construct a detached null root.
root(ptr< U > const &value) noexcept
Register a derived pointer through its base allocation-tracing hook.
Definition heap.ccm:2339
ptr< T > get() const noexcept
Obtain a pointer valid until the next collection.
Definition heap.ccm:2342
T * operator->() const noexcept
Borrow the target in its current heap scope.
Definition heap.ccm:2346
T & operator*() const noexcept
Borrow the target in its current heap scope.
Definition heap.ccm:2348
constexpr rep count() const noexcept
Retrieve the count in this unit explicitly.
Definition units.ccm:103
A four-byte weak heap field; tracing updates its slot without retaining its target....
Definition heap.ccm:649
constexpr bool expired() const noexcept
Whether null or cleared by collection; this does not initiate collection.
Definition heap.ccm:692
T element_type
Target record type; may be incomplete.
Definition heap.ccm:652
constexpr bool is_young() const noexcept
Whether this field targets the young generation.
Definition heap.ccm:690
constexpr void swap(weak_ptr &other) noexcept
Exchange targets while preserving each slot's weak barrier.
Definition heap.ccm:2525
constexpr ~weak_ptr() noexcept
Remove a dying old slot from the remembered set.
Definition heap.ccm:2521
constexpr weak_ptr & unsafe_assign(weak_ptr const &other) noexcept
Assign without registration; the caller must remember old-to-young slots.
Definition heap.ccm:686
root< T > lock() const noexcept
Retain the current target as a strong root, or return an empty root.
Definition heap.ccm:2531
constexpr weak_ptr & unsafe_assign(offset bits) noexcept
Assign encoded bits without registration; the caller owns the barrier.
Definition heap.ccm:684
std::uint32_t offset
The same generation-tagged cell index used by ptr.
Definition heap.ccm:654
constexpr weak_ptr() noexcept=default
Construct an empty weak pointer.
constexpr offset get() const noexcept
Read the encoded offset; an external copy expires on collection.
Definition heap.ccm:688
constexpr weak_ptr(ptr< U > const &value) noexcept
Observe a derived pointer through its supported base conversion.
Definition heap.ccm:669
An external weak slot, updated or cleared by collection without retaining its target....
Definition heap.ccm:2360
void swap(weak_root &other) noexcept
Exchange slots in the same heap without allocating or retaining either target.
Definition heap.ccm:2387
root< T > lock() const noexcept
Retain the current target, or return an empty root if expired. Requires the owning heap current; the ...
Definition heap.ccm:2380
void reset() noexcept
Unregister the slot and leave it detached and expired.
Definition heap.ccm:2385
weak_root(root< T > const &value) noexcept
Observe a strong root in the current heap without retaining its target.
Definition heap.ccm:2371
weak_root() noexcept=default
Construct a detached, expired weak root.
friend void swap(weak_root &a, weak_root &b) noexcept
Exchange two external weak roots.
Definition heap.ccm:2391
bool expired() const noexcept
Whether this slot is null; does not initiate collection.
Definition heap.ccm:2377
weak_root(ptr< U > const &value) noexcept
Register a derived pointer through its base allocation-tracing hook.
Definition heap.ccm:2375
An external conditional weak-value handle; registration survives dropped handles.Keep this handle out...
Definition heap.ccm:2399
bool expired() const noexcept
Whether the registration has retired or its heap has been destroyed.
Definition heap.ccm:2406
root< V > lock() const noexcept
Retain the current value, or return an empty root for a retired registration. Locking and using the r...
Definition heap.ccm:2409
void finalize() const noexcept
Retire the registration and run its callback now, at most once. Requires the owning heap current....
Definition heap.ccm:2415
T supports tracing after it is complete.Ordinary trace return types are unrestricted; collection uses...
Definition heap.ccm:557
A visitor for jam's compressed, alignment-preserving heap.
Definition heap.ccm:412
void collect_major() noexcept
Trace both generations and compact survivors into old; reset the countdown.
Definition heap.ccm:2106
void collect(Trace &&trace) noexcept
Scheduled collection with a fallback tracer for untyped records.
Definition heap.ccm:2127
jam::ptr< T > ptr
The compact pointer type for this heap.
Definition heap.ccm:875
jam::weak_root< T > weak_root(ptr< T > const &at) noexcept
Register an external weak slot, forwarded but never used to seed marking.
Definition heap.ccm:2081
void collect_minor(bool promote=false) noexcept
Collect young without walking old; optionally promote survivors; preserve the countdown.
Definition heap.ccm:2111
offset field(offset cell, unsigned slot=0) const noexcept
Read one managed reference field by value.
Definition heap.ccm:1374
std::size_t page_words() const noexcept
Cells per native OS page (distinct from a rank block).
Definition heap.ccm:1354
T load(ptr< T > const &at) const noexcept
Read a typed value snapshot; any pointers in it expire on collection.
Definition heap.ccm:2266
std::size_t capacity() const noexcept
Capacity in eight-byte cells, including the reserve.
Definition heap.ccm:1346
T * address(ptr< T > const &at) noexcept
Borrow a typed target; the address expires on growth or collection.
Definition heap.ccm:2257
detail::heap_block block
Live bitmap, forwarding base and pointer-field bitmap.
Definition heap.ccm:872
ptr< T > mk(Args &&... args) noexcept
Allocate and copy a typed record, returning an unrooted reference.Construct T from the arguments....
Definition heap.ccm:2239
offset bitmap
Allocation/live bitmap.
Definition heap.ccm:864
heap(heap_options config={})
Construct a heap and one persistent pool sized for both collection phases.
Definition heap.ccm:2047
static constexpr std::size_t block_words
Eight-byte cells in one rank block: 32.
Definition heap.ccm:868
auto data(this Self &self) noexcept -> std::conditional_t< std::is_const_v< Self >, word const, word > *
Borrow cells; dereference an offset as data()[offset].
Definition heap.ccm:1358
consteval auto make_manifest(Members T::*... members)
Describe the pointer-bearing members of T, including embedded values.T may still be incomplete....
Definition heap.ccm:476
offset allocate(std::size_t words, std::size_t alignment_bytes=8) noexcept
Allocate a positive complete record measured in eight-byte cells.
Definition heap.ccm:1406
bool claim(offset first, std::size_t words=1, std::size_t alignment_bytes=8) noexcept
Claim traversal by atomically marking the record's first cell.
Definition heap.ccm:1450
std::size_t reserved() const noexcept
Reserved cells for bounded parallel compaction.
Definition heap.ccm:1348
static constexpr unsigned fields_per_word
Four-byte pointer fields per allocation cell.
Definition heap.ccm:870
void clear_marks() noexcept
Clear marks and alignment requirements; pointer declarations survive.
Definition heap.ccm:1423
void pointer(offset where, unsigned slot=0) noexcept
Declare a managed reference field; may overlap frozen-heap markers.
Definition heap.ccm:1468
std::span< block const > blocks() const noexcept
Borrow rank descriptors while markers and compaction are quiescent.
Definition heap.ccm:1396
void collect_minor(Trace &&trace, bool promote=false) noexcept
Minor collection with a fallback tracer and optional promotion; preserve the countdown....
Definition heap.ccm:2147
static constexpr offset null
Unreachable reference preserved by forwarding.
Definition heap.ccm:866
void collect_major(Trace &&trace) noexcept
Major collection with a fallback tracer; reset the countdown.Uses the same tracing and synchronizatio...
Definition heap.ccm:2139
void mark(offset first, std::size_t words=1, std::size_t alignment_bytes=8) noexcept
Mark a complete reachable record; concurrent markers are permitted.
Definition heap.ccm:1439
std::uint32_t offset
Stored reference, in eight-byte units from data().
Definition heap.ccm:862
void store(ptr< T > const &at, T const &value) noexcept
Replace a typed record's bytes without changing its extent or type.
Definition heap.ccm:2276
void set_field(offset cell, offset value, unsigned slot=0) noexcept
Write a managed reference field's bytes.
Definition heap.ccm:1389
void collect() noexcept
Collect typed roots, running minor_collections minors between majors. The first minor_collections cal...
Definition heap.ccm:2101
heap_options configuration() const noexcept
Read the immutable construction policy with typed page counts.
Definition heap.ccm:2054
std::size_t start() const noexcept
Current view's offset in the circular backing, in cells.
Definition heap.ccm:1352
std::size_t used() const noexcept
Allocated prefix in eight-byte cells, including the reserved null cell.
Definition heap.ccm:1350
jam::root< T > root(ptr< T > const &at) noexcept
Register a typed external root with its type-specific tracing callback.
Definition heap.ccm:2072
std::uint64_t word
Eight-byte allocation cell.
Definition heap.ccm:860
void compact(std::span< offset > roots={}) noexcept
Pack marked records, forwarding managed fields and external roots.
Definition heap.ccm:1488
void collect_major() noexcept
Collect both generations of the current heap; reset its collection countdown.
Definition heap.ccm:2483
void collect() noexcept
Collect the current heap; only registered roots remain meaningful outside it.
Definition heap.ccm:2481
constexpr void unsafe_assign(std::array< ptr< T >, N > &target, std::array< ptr< T >, N > const &source) noexcept
Bulk-copy pointer bits without registration; the caller owns the barrier.
Definition heap.ccm:2456
weak< V > mk_weak(ptr< K > const &key, ptr< V > const &value, ptr< F > const &finalizer, std::type_identity_t< void(*)(F *) noexcept > runner) noexcept
Register a weak key/value pair and managed finalizer in the current heap. The captureless runner rece...
Definition heap.ccm:2443
ptr< T > mk(Args &&... args) noexcept
Allocate a record in the current heap; register a root before collection.
Definition heap.ccm:2450
constexpr void assign(std::array< ptr< T >, N > &target, std::array< ptr< T >, N > const &source) noexcept
Copy an array and register young lanes with one old-range check.
Definition heap.ccm:2461
void collect_minor(bool promote=false) noexcept
Collect the current heap's young generation; optionally promote survivors.
Definition heap.ccm:2485
Sizing policy for one generation. Sizes are platform pages.
Definition heap.ccm:810
units::pages maximum
Maximum ring size; reserves virtual addresses without committing backing.
Definition heap.ccm:816
units::pages reserve
Gap reserved for bounded parallel compaction.
Definition heap.ccm:814
units::pages capacity
Initial ring capacity, including the compaction reserve.
Definition heap.ccm:812
unsigned shrink_shift
Shrink below 1/2^shift occupancy; zero disables shrinking.
Definition heap.ccm:818
One generation's ordered double-mapped arena and collection metadata.
Definition heap.ccm:1219
Two generations and one shared worker pool.
Definition heap.ccm:822
generation_options young
Generation one; ordinary typed allocation starts here.
Definition heap.ccm:826
std::size_t workers
Workers shared by marking and compaction, including the caller.
Definition heap.ccm:828
generation_options old
Generation zero, below young in virtual memory.
Definition heap.ccm:824
std::size_t minor_collections
Minor collections between scheduled majors; zero means always major.
Definition heap.ccm:830
A value with no outgoing managed references.Specializing tracer<T> by deriving from leaf<T> explicitl...
Definition heap.ccm:501
static constexpr void trace(Visitor &, T const &) noexcept
Visit no fields; allocation tracing still marks the complete extent.
Definition heap.ccm:504
static constexpr auto trace(Visitor &visit, T const &value) noexcept(noexcept(value.trace(visit))) -> decltype(value.trace(visit))
Enumerate a value's parts and forward its member hook's result.
Definition heap.ccm:526
static constexpr decltype(auto) trace(Visitor &visit, ptr< T > const &at) noexcept
Forward an unclaimed target to its cooperative static hook.
Definition heap.ccm:532
static void trace(Visitor &visit, ptr< T > const &at) noexcept accepts< Visitor >
Claim records in a loop, queueing branches and walking one same-type edge locally.
Definition heap.ccm:802
static constexpr void trace(Visitor &visit, ptr< T > const &value) noexcept(noexcept(visit(value)))
Preserve the field address and dispatch to the matching heap visitor.
Definition heap.ccm:721
static constexpr void trace(Visitor &visit, std::array< T, N > const &value)
Visit elements in order, preserving their original addresses.
Definition heap.ccm:768
static constexpr void trace(Visitor &visit, std::tuple< Ts... > const &value)
Tuple views retain their elements' original addresses.
Definition heap.ccm:754
static constexpr void trace(Visitor &visit, std::variant< Ts... > const &value)
A valueless variant has no outgoing managed fields.
Definition heap.ccm:780
static constexpr void trace(Visitor &visit, weak_ptr< T > const &value) noexcept(noexcept(visit(value)))
Preserve the original field address for pointer-mask declaration.
Definition heap.ccm:733
Per-value tracing customization; values without a hook or manifest are leaves.An allocation-only clai...
Definition heap.ccm:515
constexpr To ceil(space< R, P > value) noexcept
Least whole destination count not less than the source.
Definition units.ccm:216
constexpr To floor(space< R, P > value) noexcept
Greatest whole destination count not exceeding the source.
Definition units.ccm:209
space< std::size_t, std::ratio< JAM_PAGE_BYTES > > pages
Platform pages, fixed at build time.
Definition units.ccm:244
space< std::size_t > bytes
Whole bytes.
Definition units.ccm:237