9#if defined(__linux__) && !defined(_GNU_SOURCE)
12#include <native/attributes.h>
31#include <unordered_map>
33#define WIN32_LEAN_AND_MEAN
42#include <mach/mach_vm.h>
43#elif !defined(__linux__) && !defined(_WIN32)
44#error "jam requires macOS, Linux or Windows virtual-memory aliases"
60namespace jam::detail {
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);
70class generation_reservation {
74 generation_reservation(std::size_t old_maximum, std::size_t young_maximum) noexcept
75 : bytes_(2 * (old_maximum + young_maximum)) {
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());
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);
86 generation_reservation(generation_reservation
const &) =
delete;
87 ~generation_reservation() noexcept {
89 MEMORY_BASIC_INFORMATION region{};
90 if (!::VirtualQuery(base_, ®ion,
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());
96 if (::munmap(base_, bytes_)) heap_failure(
"release generation reservation", errno);
99 std::byte * data() const noexcept {
return base_; }
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");
113 ~backing() noexcept { ::CloseHandle(handle); }
117 std::size_t begin, end;
119 std::shared_ptr<backing> section;
123 struct reservation {};
124 std::byte * address =
nullptr;
125 std::size_t capacity = 0;
126 std::size_t fixed_extent = 0;
128 std::vector<span> spans;
130 [[noreturn]]
static void system_failure(
char const * operation)
noexcept {
132 heap_failure(operation,
static_cast<int>(::GetLastError()));
134 heap_failure(operation, errno);
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");
141 explicit heap_mapping(reservation, std::size_t bytes)
noexcept {
142 validate_size(bytes);
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");
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");
151 address =
static_cast<std::byte *
>(result);
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, ®ion,
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");
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, ¤t, &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));
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);
188 void fresh(std::size_t begin, std::size_t count)
noexcept {
191 spans.push_back({begin, begin + count, std::make_shared<backing>(count), 0});
192 alias(spans.back(), address + begin, count);
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});
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)
204 alias(s, address + capacity + s.begin, s.end - s.begin);
206 alias(address + s.begin, address + capacity + s.begin, s.end - s.begin);
209 void discard_gap(std::size_t start, std::size_t live)
const noexcept {
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));
219 if (start + live <= capacity) {
221 discard(start + live, capacity);
222 }
else discard(start + live - capacity, start);
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;
231 constexpr auto advice = MADV_REMOVE;
234 if (first)
static_cast<void>(::madvise(address + begin, first, advice));
235 if (count > first)
static_cast<void>(::madvise(address, count - first, advice));
239 void reset_fixed() noexcept {
240 assert(fixed_extent);
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");
247 MEMORY_BASIC_INFORMATION region{};
248 if (!::VirtualQuery(address, ®ion,
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");
253 if (::mmap(address, fixed_extent, PROT_NONE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0) == MAP_FAILED)
254 system_failure(
"restore generation reservation");
261 explicit heap_mapping(std::size_t bytes) noexcept : heap_mapping(reservation{}, bytes) {
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");
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");
280 capacity = staged.capacity;
281 for (
auto const & s : staged.spans) {
283 alias(s, address + s.begin, s.end - s.begin);
285 alias(staged.address + s.begin, address + s.begin, s.end - s.begin);
288 spans = staged.spans;
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; }
302 for (
auto const & s : spans) {
303 ::UnmapViewOfFile(address + s.begin);
304 ::UnmapViewOfFile(address + capacity + s.begin);
307 static_cast<void>(::munmap(address, capacity * 2));
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 {
318 ::GetSystemInfo(&info);
319 return info.dwPageSize;
321 return static_cast<std::size_t
>(::getpagesize());
327 void publish(std::size_t start, std::byte * target, std::size_t count)
const noexcept {
329 static_cast<void>(start);
static_cast<void>(target);
static_cast<void>(count);
330 heap_failure(
"host publication is not supported on Windows");
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");
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;
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);
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;
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});
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});
381 alias(address + source, result.address + target, count);
382 result.spans.push_back({target, target + count});
384 source = (source + count) % capacity;
385 target = (target + count) % new_bytes;
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);
403export template<
class T>
class root;
404export template<
class T>
class ptr;
405export template<
class T>
class weak_ptr;
407export template<
class T>
class weak;
411export template<
class V>
412concept visitor = std::same_as<typename V::heap_type, heap>;
415#if defined(JAM_CONTEXT_X28)
416#if !defined(__aarch64__)
417#error "JAM_CONTEXT_X28 requires AArch64 and -ffixed-x28"
419register heap * active_heap
asm(
"x28");
421inline thread_local heap * active_heap =
nullptr;
423template<
class T,
class H = heap>
424concept member_trace =
requires(T & value,
typename H::visitor & visit) {
427template<
class T,
class H = heap>
428concept cooperative_trace =
requires(
typename H::visitor & visit,
ptr<T> const & at) {
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>;
436concept has_allocation_trace =
requires { &T::claim_and_trace; } || allocation_trace<T>;
438concept has_trace =
requires { &T::trace; } || member_trace<T> || cooperative_trace<T>;
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) {
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;
453concept has_manifest =
requires {
454 requires manifest_for<T, std::remove_cvref_t<
decltype(T::manifest)>>;
456struct invalid_tracer {};
475export template<
class T,
class... Members>
requires (std::is_object_v<Members> && ...)
477 if (((members ==
nullptr) || ...))
throw "manifest members must not be null";
478 return detail::member_manifest<T, Members...>{{members...}};
489 [[nodiscard]] native_inline
explicit heap_scope(
heap & arena)
noexcept;
491 native_inline
~heap_scope() noexcept { detail::active_heap = previous; }
500export template<
class T>
503 template<
class Visitor>
504 static constexpr void trace(Visitor &, T
const &)
noexcept {}
514export template<
class T>
515struct tracer : std::conditional_t<requires { T::manifest; } || detail::has_allocation_trace<T>, detail::invalid_tracer, leaf<T>> {};
522export template<
class T>
requires detail::has_trace<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);
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);
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) {
544} ||
requires(ptr<T>
const & at,
typename H::visitor & visit) {
556export template<
class T>
569export template<
class T>
578 mutable offset position = 0;
579 native_inline
constexpr void update_barrier() noexcept;
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;
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;
612 [[nodiscard]]
constexpr bool is_young() const noexcept {
return (position & 0x80000000u) != 0; }
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;
625 [[nodiscard]] native_inline T *
operator->() const noexcept;
627 [[nodiscard]] native_inline T & operator*() const noexcept;
635 native_inline
void prefetch() const noexcept;
638 [[nodiscard]] constexpr auto operator<=>(
ptr const &) const noexcept = default;
648export template<class T>
656 mutable offset position = 0;
657 native_inline
constexpr void update_barrier() noexcept;
662 constexpr
weak_ptr(std::same_as<std::nullptr_t> auto) noexcept {}
668 template<
class U>
requires (!std::same_as<T, U>) && std::convertible_to<
ptr<U>,
ptr<T>>
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;
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;
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) {
717export template<
class T>
720 template<visitor Visitor>
721 static constexpr void trace(Visitor & visit,
ptr<T> const & value)
722 noexcept(
noexcept(visit(value))) {
729export template<
class T>
732 template<visitor Visitor>
734 noexcept(
noexcept(visit(value))) { visit(value); }
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);
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...);
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);
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);
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)...);
801 template<
class Visitor>
803 requires std::remove_cvref_t<
decltype(T::manifest)>::template accepts<Visitor> {
804 visit.template trace_manifest<T>(at);
848 template<
class T>
friend class ptr;
849 template<
class T>
friend class weak_ptr;
850 template<
class T>
friend class weak;
856 [[nodiscard]] native_pure native_inline
857 static heap *
current() noexcept {
return detail::active_heap; }
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);
887 struct trace_job {
offset at; trace_function trace; };
889 trace_job key, value, finalizer;
890 void (*invoke)(
heap &, weak_entry &)
noexcept;
892 weak_entry * previous_running =
nullptr;
894 template<
class F>
struct finalizer_entry : weak_entry {
895 void (*runner)(F *)
noexcept;
907 std::uint32_t slot = 0;
908 native_inline
void retain()
noexcept {
911 native_inline
void detach()
noexcept {
912 if (slot)
heap::current()->release_root(std::exchange(slot, 0));
914 native_inline root_handle(heap & arena,
offset at, trace_function fn =
nullptr) noexcept
915 : slot(arena.add_root(at, fn)) {}
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(); }
929 native_inline root_handle &
operator=(root_handle && other)
noexcept {
930 if (
this != &other) { detach(); slot = std::exchange(other.slot, 0); }
936 [[nodiscard]] native_inline
offset get() const noexcept {
942 [[nodiscard]] native_inline
explicit operator bool() const noexcept {
return get() !=
null; }
954 detail::frontier<trace_job> & pending;
956 void const * record =
nullptr;
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) {}
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);
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>});
979 struct manifest_visit {
982 ptr<T> const * next =
nullptr;
984 std::size_t first_slot = 0;
986 native_inline
void flush()
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>) {
998 if (next) visit.enqueue(*next);
999 next = std::addressof(value);
1001 }
else visit.enqueue(value);
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);
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), ...);
1020 void trace_manifest(
ptr<T> const & at)
noexcept {
1022 auto const *
current = std::addressof(at);
1026 manifest_visit<T> fields{*
this};
1027 tracer<T>::trace(fields, *value);
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);
1052 heap::require_record<T>();
1053 if (!value || !
claim(value.
get(), record_words<T>, std::max(
alignof(T),
sizeof(
word))))
1055 auto const * result = arena.template record_address<T>(value.
get());
1057 record_offset = value.
get();
1058 record_bytes =
sizeof(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;
1079 record_bytes =
sizeof(T);
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)));
1100 native_inline
void pointers(
word bits, std::size_t first_slot = 0) noexcept {
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;
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);
1119 [[nodiscard]] native_inline T
const *
claim(
ptr<T> const & value)
noexcept {
1128 arena.pointer(cell, slot);
1129 auto const target = arena.field(cell, slot);
1131 pending.push(worker, {
target,
nullptr});
1136 if (at && (!arena.minor_collection || (at & young_bit))) pending.push(worker, {at,
nullptr});
1143 requires (!detail::is_ptr<T> && !detail::is_weak_ptr<T>) && detail::visits<T, visitor> {
1144 tracer<T>::trace(*
this, value);
1162 template<
class... Ts>
1164 requires (
sizeof...(Ts) != 1) && (detail::visits<Ts, visitor> && ...) {
1165 detail::trace_parts(*
this, values...);
1169 native_inline
void poll() noexcept { pending.communicate(worker); }
1174 static constexpr std::size_t record_words = (
sizeof(T) +
sizeof(word) - 1) /
sizeof(word);
1177 static consteval void require_record() noexcept {
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");
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)));
1198 return reinterpret_cast<T *
>(p);
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>) {
1208 visit.arena.template record_address<T>(at)->claim_and_trace(visit);
1209 }
else if constexpr (
requires { tracer<T>::trace(visit, ptr<T>{at}); }) {
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);
1222 static constexpr offset young_bit = 0x80000000u;
1223 static constexpr offset offset_mask = 0x7fffffffu;
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;
1233 std::vector<block> metadata;
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");
1245 static constexpr unsigned block_shift = 5;
1246 static constexpr std::size_t maximum_bytes = std::size_t{1} << 34;
1251 && config.
shrink_shift < std::numeric_limits<std::size_t>::digits;
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");
1262 detail::heap_failure(
"capacity must hold twice the reserve");
1265 return {page / 8, reserve / 8, 2 * reserve / 8,
capacity, config};
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);
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;
1279 unsigned flags(std::size_t index)
const noexcept {
1280 return (alignment[index >> 1] >> ((index & 1) * 4)) & 15;
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);
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);
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);
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(
1301 first +=
static_cast<offset>(count);
1305 static constexpr word pointer_cells(
bitmap live)
noexcept {
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);
1314 std::uint8_t
const * alignment_table()
const noexcept {
1315 return has_alignment ? alignment.data() :
nullptr;
1317 [[nodiscard]]
offset base(
block const & item)
const noexcept {
1318 return item.destination;
1320 [[nodiscard]]
offset forward(
offset value)
const noexcept {
1322 assert(value < cursor);
1323 auto const & item = metadata[value >> block_shift];
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)));
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);
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;
1342 : generation(configure(config), c, w,
address) {}
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);
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];
1377 auto const address =
reinterpret_cast<std::byte
const *
>(
data() + cell) + slot *
sizeof(
offset);
1378 std::memcpy(&value,
address,
sizeof(value));
1392 auto const address =
reinterpret_cast<std::byte *
>(
data() + cell) + slot *
sizeof(
offset);
1393 std::memcpy(
address, &value,
sizeof(value));
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") {
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))
1419 cursor = aligned + words;
1420 return static_cast<offset>(aligned);
1424 for (
auto & item : metadata) {
1426 item.destination = 0;
1428 std::fill(alignment.begin(), alignment.end(), 0);
1429 has_alignment =
false;
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);
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);
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);
1471 std::atomic_ref<word>{metadata[where >> block_shift].pointers}
1472 .fetch_or(
word{1} << bit, std::memory_order_relaxed);
1488 void compact(std::span<offset> roots = {})
noexcept {
1490 std::atomic_signal_fence(std::memory_order_seq_cst);
1492 compact_impl(roots);
1494 std::atomic_signal_fence(std::memory_order_seq_cst);
1498 std::size_t prepare(std::size_t start,
bool keep_null)
noexcept {
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;
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;
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);
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));
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});
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());
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 {
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);
1568 done[page].store(
true, std::memory_order_release);
1572 floor = front.fetch_add(0, std::memory_order_acq_rel);
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;
1580 floor = front.load(std::memory_order_acquire);
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);
1588 }
else for (std::size_t page = 0; page < source_pages; ++page) move_page(page);
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));
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());
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));
1607 std::size_t rotated_origin() const noexcept {
1608 return (origin + capacity() - reserve_word_count) % capacity();
1610 word * at_origin(std::size_t start)
const noexcept {
1611 return reinterpret_cast<word *
>(storage.data()) + start;
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;
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);
1628 && cursor <= smaller - reserve_word_count) resize(smaller);
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;
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);
1648 detail::compactor
const compactor = detail::choose_compactor();
1649 detail::work_pool workers;
1650 detail::generation_reservation reservation;
1653 heap_options
const policy;
1654 std::size_t minors_left;
1657 std::uint32_t copies, previous, next;
1658 trace_function trace;
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);
1667 auto slot = free_root;
1668 if (slot) free_root = root_slots[slot - 1].next;
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());
1675 root_slots[slot - 1] = {at, 1, 0, roots_head, trace};
1676 if (roots_head) root_slots[roots_head - 1].previous = slot;
1680 native_inline
void retain_root(std::uint32_t slot)
noexcept {
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");
1687 native_inline
void release_root(std::uint32_t slot)
noexcept {
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};
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;
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");
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>;
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;
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));
1745 native_inline
void forget(
void const * address)
noexcept {
1747 if (old_slot(address, slot)) forget_slot(slot);
1749 native_noinline
void forget_slot(std::uint32_t slot)
noexcept {
1750 remembered.erase(slot);
1752 native_noinline
void remember_slot(std::uint32_t slot, trace_function trace)
noexcept {
1754 remembered.insert_or_assign(slot, trace);
1756 void require_mutator() const noexcept {
1757 if (collecting) detail::heap_failure(
"mutation during collection");
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);
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;
1770 void run_finalizer(std::shared_ptr<weak_entry>
const & entry)
noexcept {
1771 entry->previous_running = running_finalizer;
1772 running_finalizer = entry.get();
1774 heap_scope binding{*
this};
1775 entry->invoke(*
this, *entry);
1777 running_finalizer = entry->previous_running;
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);
1787 void finalize(std::shared_ptr<weak_entry>
const & entry)
noexcept {
1789 if (!entry->active)
return;
1790 entry->active =
false;
1791 std::erase(weak_entries, entry);
1792 run_finalizer(entry);
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);
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);
1808 arena.move_to(target, tables);
1809 arena.zero_gaps(target, 0);
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);
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);
1822 std::memcpy(&value, address, sizeof(value));
1823 value = forward(value, true);
1824 std::memcpy(address, &value, sizeof(value));
1825 if (value) ++entry; else entry = remembered.erase(entry);
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);
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);
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(); }
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;
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");
1868 static_cast<void>(owner.arena.allocate(prefix - 1));
1869 static_cast<void>(owner.nursery.allocate(prefix - 1));
1872 host & operator=(
host const &) =
delete;
1875 if (state != phase::idle) detail::heap_failure(
"unfinished host collection");
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);
1894 owner.require_mutator();
1895 return (which == generation::young ? owner.nursery.allocate(words) : owner.arena.allocate(words)) | (which == generation::young ? young_bit : 0);
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;
1912 owner.arena.clear_marks();
1913 for (
auto & item : owner.arena.metadata) item.pointers = 0;
1920 template<
class 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);
1935 if (state != phase::marking && state != phase::retry)
1936 detail::heap_failure(
"host liveness outside marking phase");
1937 return owner.marked(at);
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;
1952 old_words = owner.nursery.prepare(owner.arena.cursor,
false);
1953 if (old_words > owner.arena.capacity() - owner.arena.reserved()) {
1954 state = phase::retry;
1957 young_words = prefix;
1959 if (prefix > 1) owner.nursery.mark(1, prefix - 1);
1960 young_words = owner.nursery.prepare(young_bit,
true) - young_bit;
1962 state = phase::prepared;
1967 if (state != phase::prepared)
1968 detail::heap_failure(
"host forward outside prepared phase");
1969 return owner.forward(at, owner.minor_collection);
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);
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);
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);
2004 [[nodiscard]]
char const *
compactor_name() const noexcept {
return compactor.name; }
2009 if (!value.is_young())
return;
2011 if (!old_slot(&value, slot))
return;
2012 remember_slot(slot, &heap::template trace_record<T>);
2016 if (values.empty())
return;
2017 std::uint32_t first;
2018 if (!old_slot(values.data(), first))
return;
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>);
2027 if (!old_slot(&value, slot))
return;
2028 if (value.is_young()) remember_slot(slot, &weak_slot);
else forget_slot(slot);
2032 if (values.empty())
return;
2033 std::uint32_t first;
2034 if (!old_slot(values.data(), first))
return;
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);
2048 :
heap(configured{}, configure(config)) {}
2056 heap & operator=(
heap const &) =
delete;
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;
2065 assert(at ==
null || (at & offset_mask) < containing(at).cursor);
2073 require_record<T>();
2082 require_record<T>();
2091 template<traceable K, traceable V, traceable F>
2093 ptr<F> const & finalizer, std::type_identity_t<
void (*)(F *)
noexcept> runner)
noexcept;
2102 collect([](
visitor &,
offset)
noexcept { detail::heap_failure(
"untyped target in typed collection"); });
2126 template<
class Trace>
2128 requires std::is_nothrow_invocable_v<Trace &, visitor &, offset> {
2138 template<
class Trace>
2140 requires std::is_nothrow_invocable_v<Trace &, visitor &, offset> {
2141 run_collection(std::forward<Trace>(trace),
false,
false);
2146 template<
class Trace>
2148 requires std::is_nothrow_invocable_v<Trace &,
visitor &,
offset> {
2149 run_collection(std::forward<Trace>(trace),
true, promote);
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};
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};
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);
2174 template<
class Trace>
2175 void run_collection(Trace && trace,
bool minor,
bool promote)
noexcept {
2179 std::atomic_signal_fence(std::memory_order_seq_cst);
2181 minor_collection = minor;
2182 if (minor) nursery.clear_marks();
else clear_marks();
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});
2193 if (minor)
for (
auto const & [slot, callback] : remembered) {
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});
2198 auto seed = [&](trace_job job)
noexcept {
2199 if (job.at && !marked(job.at)) initial.push_back(job);
2201 auto seed_finalizer = [&](weak_entry
const & entry)
noexcept {
2202 seed(entry.key); seed(entry.finalizer);
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());
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;
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;
2220 entry->active =
false;
2221 pending_finalizers.push_back(entry);
2222 seed_finalizer(*entry);
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);
2238 template<
class T,
class... Args>
2240 require_record<T>();
2241 static_assert(
noexcept(T{std::forward<Args>(args)...}),
"record construction must be noexcept");
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});
2249 std::memcpy(nursery.data() + at,
static_cast<void const *
>(&value),
sizeof(T));
2250 return ptr<T>{young_bit | at};
2258 require_record<T>();
2259 assert(!collecting);
2260 return record_address<T>(at.
get());
2267 require_record<T>();
2269 && record_words<T> <= containing(at.
get()).cursor - at.
local_offset());
2270 return *std::launder(
reinterpret_cast<T
const *
>(decode(at.
get())));
2277 require_record<T>();
2280 auto * destination = record_address<T>(at.
get());
2281 if (destination != &value) *destination = value;
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);
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);
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 {
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);
2319 detail::active_heap = &arena;
2326export template<
class T>
2330 native_inline
explicit root(base value) noexcept : base(std::move(value)) {}
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>>
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(); }
2359export template<
class T>
2363 native_inline
explicit weak_root(base value) noexcept : base(std::move(value)) {}
2368 native_inline weak_root(
ptr<T> const & value) noexcept requires
traceable<T>
2369 : weak_root(heap::current()->weak_root(value)) {}
2372 : weak_root(value.
get()) {}
2374 template<
class U>
requires (!std::same_as<T, U>) && std::convertible_to<
ptr<U>,
ptr<T>>
2387 native_inline
void swap(weak_root & other)
noexcept {
2388 std::swap(
static_cast<base &
>(*
this),
static_cast<base &
>(other));
2391 friend native_inline
void swap(weak_root & a, weak_root & b)
noexcept { a.swap(b); }
2398export template<
class V>
2401 std::shared_ptr<heap::weak_entry> entry;
2402 explicit weak(std::shared_ptr<heap::weak_entry> value) noexcept : entry(std::move(value)) {}
2404 weak()
noexcept =
default;
2406 [[nodiscard]]
bool expired() const noexcept {
return !entry || !entry->active; }
2422template<traceable K, traceable V, traceable F>
2424 ptr<F> const & finalizer, std::type_identity_t<
void (*)(F *)
noexcept> runner)
noexcept {
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);
2436 weak_entries.push_back(entry);
2442export template<traceable K, traceable V, traceable F>
2444 ptr<F> const & finalizer, std::type_identity_t<
void (*)(F *)
noexcept> runner)
noexcept {
2449export template<traceable T,
class... Args>
2450[[nodiscard]] native_inline
ptr<T> mk(Args &&... args)
noexcept {
2455export template<
class T, std::
size_t N>
2457 for (std::size_t i = 0; i != N; ++i) target[i].
unsafe_assign(source[i]);
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 {
2467export template<
class T, std::
size_t N>
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]);
2473export template<
class T, std::
size_t N>
2475 std::array<
weak_ptr<T>, N>
const & source)
noexcept {
2488constexpr void weak_ptr<T>::update_barrier() noexcept {
2503 auto const changed = position ^ other.position;
2504 position = other.position;
2505 if (changed & 0x80000000u) update_barrier();
2510 if (
is_young()) { update_barrier(); other.update_barrier(); }
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();
2522 if !
consteval {
if (
auto * h =
heap::current()) h->forget(
this); }
2526 auto const changed = position ^ other.position;
2527 std::swap(position, other.position);
2528 if (changed & 0x80000000u) { update_barrier(); other.update_barrier(); }
2532 if (!position)
return {};
2537constexpr void ptr<T>::update_barrier() noexcept {
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); }
2553 assert(!other || !
heap::current() ||
static_cast<void const *
>(
static_cast<T
const *
>(other.operator->()))
2554 ==
static_cast<void const *
>(other.operator->()));
2558constexpr ptr<T> & ptr<T>::operator=(
ptr const & other)
noexcept {
2559 auto const changed = position ^ other.position;
2560 position = other.position;
2562 if (changed & heap::young_bit) update_barrier();
2566constexpr ptr<T>::ptr(
ptr && other) noexcept : position(std::exchange(other.position, 0)) {
2567 if (
is_young()) { update_barrier(); other.update_barrier(); }
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();
2579 auto const changed = position ^ other.position;
2580 std::swap(position, other.position);
2581 if (changed & heap::young_bit) { update_barrier(); other.update_barrier(); }
2585 if !
consteval {
if (
auto * h =
heap::current()) h->forget(
this); }
2596 if (!position)
return;
2598 assert(arena &&
local_offset() < arena->containing(position).used());
2599 __builtin_prefetch(&arena->containing(position).blocks()[
local_offset() >> heap::block_shift].live, 0, 3);
2603 if (!position)
return;
2605 assert(arena &&
local_offset() < arena->containing(position).used());
2606 __builtin_prefetch(arena->decode(position), 0, 3);
Borrow a heap whose roots, barriers and reference policy belong to a host runtime.
bool marked(offset at) const noexcept
Exact collected-generation liveness; old objects are live during minors.
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...
generation
Select the arena for raw allocation or publication.
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.
host(heap &storage, std::size_t prefix) noexcept
Reserve an identical guard in both fresh, fixed-size generations.
~host() noexcept
Release the capability after finishing its last collection.
void begin(bool minor) noexcept
Start a VM-owned marking epoch; the VM supplies barriers and weak policy.
void finish() noexcept
Move with the original SIMD kernel and pack only after both moves finish.
bool prepare(bool promote=false) noexcept
Freeze both forwarding tables after resolving all reference policies.
offset forward(offset at) const noexcept
Repair a root while original objects and both forwarding tables remain intact.
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.
A four-byte handle to a collector-updated external root slot.Copies share a registration; moves leave...
root_handle(root_handle &&other) noexcept
Transfer a registration, leaving the source detached.
offset get() const noexcept
Resolve the slot through the current heap; returned offsets expire on collection.
root_handle & operator=(root_handle &&other) noexcept
Transfer a registration in the same heap.
~root_handle() noexcept
Release this reference; the last release recycles the slot.
root_handle & operator=(root_handle const &other) noexcept
Replace this handle with a shared registration in the same heap.
root_handle() noexcept=default
Construct a detached null handle.
offset operator*() const noexcept
Read the current target offset.
void pointer(weak_ptr< T > const &value) noexcept
Declare a weak slot without following or claiming its target.
void pointer(ptr< T > const &value) noexcept
Declare one pointer slot without following or claiming its target.
void target(offset at) noexcept
Enqueue an external/wide target without declaring a narrow source slot.
offset field(offset cell, unsigned slot=0) noexcept
Declare a managed field and enqueue its nonnull target for tracing.
T const * claim_target(T const *value) noexcept
Claim the complete object from its allocation-level hook.
T const * claim_target(ptr< T > const &value) noexcept
Claim a typed target without declaring the pointer's source slot.
void operator()(Ts const &... values) noexcept
Visit parts in order, preserving their addresses; an empty pack does nothing.
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...
void operator()(T const &value) noexcept
Walk an embedded value without claiming cells again. Parts share the current allocation's pointer mas...
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...
void operator()(weak_ptr< T > const &value) noexcept
Declare a weak field for forwarding; never enqueue its target.
ptr< T > operator()(ptr< T > const &value) noexcept
Declare a typed field and enqueue its nonnull target.
void poll() noexcept
Service overdue stochastic donations without leaving this walker. Long cooperative hooks should poll ...
heap heap_type
Heap accepted by this visitor.
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.
Bind an existing heap, restoring the previous binding on scope exit.The heap must outlive the scope....
~heap_scope() noexcept
Restore the binding saved at construction.
heap_scope(heap &arena) noexcept
Use the supplied heap for implicit operations on this thread.
A circular, double-mapped arena with bounded parallel compaction.Compressed four-byte offsets address...
void remember(std::span< weak_ptr< T > > values) noexcept
Register weak slots after bulk unsafe assignment, dropping stale entries.
void remember(ptr< T > const &value) noexcept
Explicitly register a pointer after a caller-managed unsafe write.
generation const & young() const noexcept
Inspect generation one's arena.
char const * compactor_name() const noexcept
Runtime-selected compaction implementation, including SIMD ISA admission.
void remember(weak_ptr< T > const &value) noexcept
Register a weak old-to-young slot for rewriting, never for marking.
void remember(std::span< ptr< T > > values) noexcept
Register a contiguous group after bulk unsafe assignment.
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.
std::size_t remembered_size() const noexcept
Number of distinct old source slots retained in the current GC epoch.
static heap * current() noexcept
Read the current thread's dynamically bound heap. Implicit operations require an active heap_scope....
jam::weak_ptr< T > weak_ptr
The weak field type for this heap.
generation const & old() const noexcept
Inspect generation zero's arena.
root_handle root(offset at) noexcept
Register a record or null in the current heap.
~heap() noexcept
Release the arena; every attached root must already be destroyed.
A typed heap offset, with no root registration or ownership.
constexpr ptr & unsafe_assign(offset bits) noexcept
Install encoded bits without registration; the caller owns the barrier.
void prefetch() const noexcept
Prefetch the cache line containing the target's first byte for reading. Null does nothing,...
constexpr void swap(ptr &other) noexcept
Exchange targets and update the remembered entries for both slots.
constexpr ~ptr() noexcept
Remove a dying old pointer slot from the remembered set.
T & operator*() const noexcept
Borrow the target in the current heap.
T element_type
Target record type.
constexpr offset local_offset() const noexcept
Cell index within the target generation.
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.
constexpr offset get() const noexcept
Read the cell offset; unregistered copies expire on collection.
std::uint32_t offset
Eight-byte cell index, stored in exactly four bytes.
void prefetch_marks() const noexcept
Prefetch the target's mark metadata without claiming or reading it. Null does nothing,...
T * operator->() const noexcept
Borrow the target in the current heap; expires on growth or collection.
constexpr bool is_young() const noexcept
Whether the target belongs to generation one.
constexpr ptr & unsafe_assign(ptr const &other) noexcept
Assign without a write barrier; the caller must register old-to-young edges.
A copyable external root, updated during collection.Four-byte slot index; no heap or registration add...
root() noexcept=default
Construct a detached null root.
root(ptr< U > const &value) noexcept
Register a derived pointer through its base allocation-tracing hook.
ptr< T > get() const noexcept
Obtain a pointer valid until the next collection.
T * operator->() const noexcept
Borrow the target in its current heap scope.
T & operator*() const noexcept
Borrow the target in its current heap scope.
constexpr rep count() const noexcept
Retrieve the count in this unit explicitly.
A four-byte weak heap field; tracing updates its slot without retaining its target....
constexpr bool expired() const noexcept
Whether null or cleared by collection; this does not initiate collection.
T element_type
Target record type; may be incomplete.
constexpr bool is_young() const noexcept
Whether this field targets the young generation.
constexpr void swap(weak_ptr &other) noexcept
Exchange targets while preserving each slot's weak barrier.
constexpr ~weak_ptr() noexcept
Remove a dying old slot from the remembered set.
constexpr weak_ptr & unsafe_assign(weak_ptr const &other) noexcept
Assign without registration; the caller must remember old-to-young slots.
root< T > lock() const noexcept
Retain the current target as a strong root, or return an empty root.
constexpr weak_ptr & unsafe_assign(offset bits) noexcept
Assign encoded bits without registration; the caller owns the barrier.
std::uint32_t offset
The same generation-tagged cell index used by ptr.
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.
constexpr weak_ptr(ptr< U > const &value) noexcept
Observe a derived pointer through its supported base conversion.
An external weak slot, updated or cleared by collection without retaining its target....
void swap(weak_root &other) noexcept
Exchange slots in the same heap without allocating or retaining either target.
root< T > lock() const noexcept
Retain the current target, or return an empty root if expired. Requires the owning heap current; the ...
void reset() noexcept
Unregister the slot and leave it detached and expired.
weak_root(root< T > const &value) noexcept
Observe a strong root in the current heap without retaining its target.
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.
bool expired() const noexcept
Whether this slot is null; does not initiate collection.
weak_root(ptr< U > const &value) noexcept
Register a derived pointer through its base allocation-tracing hook.
An external conditional weak-value handle; registration survives dropped handles.Keep this handle out...
bool expired() const noexcept
Whether the registration has retired or its heap has been destroyed.
root< V > lock() const noexcept
Retain the current value, or return an empty root for a retired registration. Locking and using the r...
void finalize() const noexcept
Retire the registration and run its callback now, at most once. Requires the owning heap current....
T supports tracing after it is complete.Ordinary trace return types are unrestricted; collection uses...
A visitor for jam's compressed, alignment-preserving heap.
void collect_major() noexcept
Trace both generations and compact survivors into old; reset the countdown.
void collect(Trace &&trace) noexcept
Scheduled collection with a fallback tracer for untyped records.
jam::ptr< T > ptr
The compact pointer type for this heap.
jam::weak_root< T > weak_root(ptr< T > const &at) noexcept
Register an external weak slot, forwarded but never used to seed marking.
void collect_minor(bool promote=false) noexcept
Collect young without walking old; optionally promote survivors; preserve the countdown.
offset field(offset cell, unsigned slot=0) const noexcept
Read one managed reference field by value.
std::size_t page_words() const noexcept
Cells per native OS page (distinct from a rank block).
T load(ptr< T > const &at) const noexcept
Read a typed value snapshot; any pointers in it expire on collection.
std::size_t capacity() const noexcept
Capacity in eight-byte cells, including the reserve.
T * address(ptr< T > const &at) noexcept
Borrow a typed target; the address expires on growth or collection.
detail::heap_block block
Live bitmap, forwarding base and pointer-field bitmap.
ptr< T > mk(Args &&... args) noexcept
Allocate and copy a typed record, returning an unrooted reference.Construct T from the arguments....
offset bitmap
Allocation/live bitmap.
heap(heap_options config={})
Construct a heap and one persistent pool sized for both collection phases.
static constexpr std::size_t block_words
Eight-byte cells in one rank block: 32.
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].
consteval auto make_manifest(Members T::*... members)
Describe the pointer-bearing members of T, including embedded values.T may still be incomplete....
offset allocate(std::size_t words, std::size_t alignment_bytes=8) noexcept
Allocate a positive complete record measured in eight-byte cells.
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.
std::size_t reserved() const noexcept
Reserved cells for bounded parallel compaction.
static constexpr unsigned fields_per_word
Four-byte pointer fields per allocation cell.
void clear_marks() noexcept
Clear marks and alignment requirements; pointer declarations survive.
void pointer(offset where, unsigned slot=0) noexcept
Declare a managed reference field; may overlap frozen-heap markers.
std::span< block const > blocks() const noexcept
Borrow rank descriptors while markers and compaction are quiescent.
void collect_minor(Trace &&trace, bool promote=false) noexcept
Minor collection with a fallback tracer and optional promotion; preserve the countdown....
static constexpr offset null
Unreachable reference preserved by forwarding.
void collect_major(Trace &&trace) noexcept
Major collection with a fallback tracer; reset the countdown.Uses the same tracing and synchronizatio...
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.
std::uint32_t offset
Stored reference, in eight-byte units from data().
void store(ptr< T > const &at, T const &value) noexcept
Replace a typed record's bytes without changing its extent or type.
void set_field(offset cell, offset value, unsigned slot=0) noexcept
Write a managed reference field's bytes.
void collect() noexcept
Collect typed roots, running minor_collections minors between majors. The first minor_collections cal...
heap_options configuration() const noexcept
Read the immutable construction policy with typed page counts.
std::size_t start() const noexcept
Current view's offset in the circular backing, in cells.
std::size_t used() const noexcept
Allocated prefix in eight-byte cells, including the reserved null cell.
jam::root< T > root(ptr< T > const &at) noexcept
Register a typed external root with its type-specific tracing callback.
std::uint64_t word
Eight-byte allocation cell.
void compact(std::span< offset > roots={}) noexcept
Pack marked records, forwarding managed fields and external roots.
void collect_major() noexcept
Collect both generations of the current heap; reset its collection countdown.
void collect() noexcept
Collect the current heap; only registered roots remain meaningful outside it.
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.
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...
ptr< T > mk(Args &&... args) noexcept
Allocate a record in the current heap; register a root before collection.
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.
void collect_minor(bool promote=false) noexcept
Collect the current heap's young generation; optionally promote survivors.
Sizing policy for one generation. Sizes are platform pages.
units::pages maximum
Maximum ring size; reserves virtual addresses without committing backing.
units::pages reserve
Gap reserved for bounded parallel compaction.
units::pages capacity
Initial ring capacity, including the compaction reserve.
unsigned shrink_shift
Shrink below 1/2^shift occupancy; zero disables shrinking.
One generation's ordered double-mapped arena and collection metadata.
Two generations and one shared worker pool.
generation_options young
Generation one; ordinary typed allocation starts here.
std::size_t workers
Workers shared by marking and compaction, including the caller.
generation_options old
Generation zero, below young in virtual memory.
std::size_t minor_collections
Minor collections between scheduled majors; zero means always major.
A value with no outgoing managed references.Specializing tracer<T> by deriving from leaf<T> explicitl...
static constexpr void trace(Visitor &, T const &) noexcept
Visit no fields; allocation tracing still marks the complete extent.
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.
static constexpr decltype(auto) trace(Visitor &visit, ptr< T > const &at) noexcept
Forward an unclaimed target to its cooperative static hook.
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.
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.
static constexpr void trace(Visitor &visit, std::array< T, N > const &value)
Visit elements in order, preserving their original addresses.
static constexpr void trace(Visitor &visit, std::tuple< Ts... > const &value)
Tuple views retain their elements' original addresses.
static constexpr void trace(Visitor &visit, std::variant< Ts... > const &value)
A valueless variant has no outgoing managed fields.
static constexpr void trace(Visitor &visit, weak_ptr< T > const &value) noexcept(noexcept(visit(value)))
Preserve the original field address for pointer-mask declaration.
Per-value tracing customization; values without a hook or manifest are leaves.An allocation-only clai...
constexpr To ceil(space< R, P > value) noexcept
Least whole destination count not less than the source.
constexpr To floor(space< R, P > value) noexcept
Greatest whole destination count not exceeding the source.
space< std::size_t, std::ratio< JAM_PAGE_BYTES > > pages
Platform pages, fixed at build time.
space< std::size_t > bytes
Whole bytes.