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).
59 bool pending_free : 1;
60
61 /// Whether the assignment is a single-part variable reference.
62 bool variable_ref : 1;
63
64 /// Whether the variable reference refers to a stack slot. Otherwise, this
65 /// var_ref_custom_idx is used to identify the variable.
66 bool stack_variable : 1;
67
68 /// Whether to delay the free when the reference count reaches zero.
69 /// (This is liveness.last_full, copied here for faster access).
70 bool delay_free : 1;
71
72 // TODO: get the type of parts from Derived
73 union {
74 Part first_part;
75 Part parts[GNU_ZERO];
76 };
77 };
78 };
79
80 u32 size() const {
81 assert(!variable_ref && "variable-ref has no allocation size");
82 return part_count * max_part_size;
83 }
84};
85
86#undef GNU_ZERO
87#pragma GCC diagnostic pop
88
89class AssignmentAllocator {
90private:
91 // Free list 0 holds VASize = sizeof(ValueAssignment).
92 // Free list 1 holds VASize rounded to the next larger power of two.
93 // Free list 2 holds twice as much as free list 1. Etc.
94 static constexpr size_t SlabSize = 16 * 1024;
95
96 // TODO: define a sensible limit on the maximum number of parts and reduce the
97 // number of free lists. Currently the limit is a little under 2**31; limiting
98 // the number to e.g. 2**16 would allow halving the number of free lists,
99 // where most are unused in the vast majority of cases.
100 static constexpr u32 NumFreeLists = 29;
101 static constexpr u32 FirstPartOff = offsetof(ValueAssignment, parts);
102
103public:
104 static constexpr u32 NumPartsIncluded =
105 (sizeof(ValueAssignment) - FirstPartOff) / sizeof(ValueAssignment::Part);
106
107private:
108 /// Allocator for small assignments that get stored in free lists. Larger
109 /// assignments are allocated directly on the heap.
110 util::BumpAllocator<SlabSize> alloc;
111 /// Free lists for small ValueAssignments
112 std::array<ValueAssignment *, NumFreeLists> fixed_free_lists{};
113
114public:
115 AssignmentAllocator() = default;
116
117 ValueAssignment *allocate(u32 part_count) {
118 if (part_count > NumPartsIncluded) [[unlikely]] {
119 return allocate_slow(part_count);
120 }
121 if (auto *res = fixed_free_lists[0]) {
122 util::unpoison_memory_region(res, sizeof(ValueAssignment));
123 fixed_free_lists[0] = res->next_free_list_entry;
124 return res;
125 }
126 return new (alloc) ValueAssignment;
127 }
128
129 ValueAssignment *allocate_slow(u32 part_count, bool skip_free_list = false);
130
131 void deallocate(ValueAssignment *assignment) {
132 if (assignment->part_count > NumPartsIncluded) [[unlikely]] {
133 deallocate_slow(assignment);
134 return;
135 }
136 assignment->next_free_list_entry = fixed_free_lists[0];
137 fixed_free_lists[0] = assignment;
138 util::poison_memory_region(assignment, sizeof(ValueAssignment));
139 }
140
141 void deallocate_slow(ValueAssignment *);
142
143 void reset() {
144 alloc.reset();
145 fixed_free_lists = {};
146 }
147};
148
149} // namespace tpde