TPDE
Loading...
Searching...
No Matches
ValueAssignment.hpp
1// SPDX-FileCopyrightText: 2025 Contributors to TPDE <https://tpde.org>
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3#pragma once
4
5#include "tpde/ValLocalIdx.hpp"
6#include "tpde/base.hpp"
7#include "tpde/util/AddressSanitizer.hpp"
8#include "tpde/util/BumpAllocator.hpp"
9
10#include <array>
11#include <cassert>
12#include <cstddef>
13
14namespace tpde {
15
16// disable Wpedantic here since these are compiler extensions
17#pragma GCC diagnostic push
18#pragma GCC diagnostic ignored "-Wpedantic"
19
20// GCC < 15 does not support the flexible array member in the union
21// see https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53548
22#if !defined(__clang__) && defined(__GNUC__) && __GNUC__ < 15
23 #define GNU_ZERO 0
24#else
25 #define GNU_ZERO
26#endif
27
28// TODO(ts): add option to use fixed size assignments if there is no need
29// for arbitrarily many parts since that comes with a 1-3% cost in
30// compile-time
31struct ValueAssignment {
32 using Part = u16;
33
34 union {
35 /// Offset from the frame pointer.
36 i32 frame_off;
37 /// For variable-references, frame_off is unused so it can be used
38 /// to store an index into a custom structure in case there is
39 /// special handling for variable references
40 u32 var_ref_custom_idx;
41 };
42
43 u32 part_count;
44
45 // we want tight packing and with this construct we get a base size of
46 // 16 bytes for values with only one part (the majority)
47 union {
48 ValueAssignment *next_free_list_entry;
49
50 struct {
51 union {
52 ValLocalIdx next_delayed_free_entry;
53 u32 references_left;
54 };
55 u8 max_part_size;
56
57 /// Whether the assignment is in a delayed free list and the reference
58 /// count is therefore invalid (zero). Set/used in debug builds only to
59 /// catch use-after-frees.
60 bool pending_free : 1;
61
62 /// Whether the assignment is a single-part variable reference.
63 bool variable_ref : 1;
64
65 /// Whether the variable reference refers to a stack slot. Otherwise, this
66 /// var_ref_custom_idx is used to identify the variable.
67 bool stack_variable : 1;
68
69 /// Whether to delay the free when the reference count reaches zero.
70 /// (This is liveness.last_full, copied here for faster access).
71 bool delay_free : 1;
72
73 // TODO: get the type of parts from Derived
74 union {
75 Part first_part;
76 Part parts[GNU_ZERO];
77 };
78 };
79 };
80
81 u32 size() const noexcept {
82 assert(!variable_ref && "variable-ref has no allocation size");
83 return part_count * max_part_size;
84 }
85};
86
87#undef GNU_ZERO
88#pragma GCC diagnostic pop
89
90class AssignmentAllocator {
91private:
92 // Free list 0 holds VASize = sizeof(ValueAssignment).
93 // Free list 1 holds VASize rounded to the next larger power of two.
94 // Free list 2 holds twice as much as free list 1. Etc.
95 static constexpr size_t SlabSize = 16 * 1024;
96
97 static constexpr u32 NumFreeLists = 2;
98 static constexpr u32 FirstPartOff = offsetof(ValueAssignment, parts);
99
100public:
101 static constexpr u32 NumPartsIncluded =
102 (sizeof(ValueAssignment) - FirstPartOff) / sizeof(ValueAssignment::Part);
103
104private:
105 /// Allocator for small assignments that get stored in free lists. Larger
106 /// assignments are allocated directly on the heap.
107 util::BumpAllocator<SlabSize> alloc;
108 /// Free lists for small ValueAssignments
109 std::array<ValueAssignment *, NumFreeLists> fixed_free_lists{};
110
111public:
112 AssignmentAllocator() noexcept = default;
113
114 ValueAssignment *allocate(u32 part_count) noexcept {
115 if (part_count > NumPartsIncluded) [[unlikely]] {
116 return allocate_slow(part_count);
117 }
118 if (auto *res = fixed_free_lists[0]) {
119 util::unpoison_memory_region(res, sizeof(ValueAssignment));
120 fixed_free_lists[0] = res->next_free_list_entry;
121 return res;
122 }
123 return new (alloc) ValueAssignment;
124 }
125
126 ValueAssignment *allocate_slow(u32 part_count,
127 bool skip_free_list = false) noexcept;
128
129 void deallocate(ValueAssignment *assignment) noexcept {
130 if (assignment->part_count > NumPartsIncluded) [[unlikely]] {
131 deallocate_slow(assignment);
132 return;
133 }
134 assignment->next_free_list_entry = fixed_free_lists[0];
135 fixed_free_lists[0] = assignment;
136 util::poison_memory_region(assignment, sizeof(ValueAssignment));
137 }
138
139 void deallocate_slow(ValueAssignment *) noexcept;
140
141 void reset() noexcept {
142 alloc.reset();
143 fixed_free_lists = {};
144 }
145};
146
147} // namespace tpde