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 noexcept {
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 static constexpr u32 NumFreeLists = 2;
97 static constexpr u32 FirstPartOff = offsetof(ValueAssignment, parts);
98
99public:
100 static constexpr u32 NumPartsIncluded =
101 (sizeof(ValueAssignment) - FirstPartOff) / sizeof(ValueAssignment::Part);
102
103private:
104 /// Allocator for small assignments that get stored in free lists. Larger
105 /// assignments are allocated directly on the heap.
106 util::BumpAllocator<SlabSize> alloc;
107 /// Free lists for small ValueAssignments
108 std::array<ValueAssignment *, NumFreeLists> fixed_free_lists{};
109
110public:
111 AssignmentAllocator() noexcept = default;
112
113 ValueAssignment *allocate(u32 part_count) noexcept {
114 if (part_count > NumPartsIncluded) [[unlikely]] {
115 return allocate_slow(part_count);
116 }
117 if (auto *res = fixed_free_lists[0]) {
118 util::unpoison_memory_region(res, sizeof(ValueAssignment));
119 fixed_free_lists[0] = res->next_free_list_entry;
120 return res;
121 }
122 return new (alloc) ValueAssignment;
123 }
124
125 ValueAssignment *allocate_slow(u32 part_count,
126 bool skip_free_list = false) noexcept;
127
128 void deallocate(ValueAssignment *assignment) noexcept {
129 if (assignment->part_count > NumPartsIncluded) [[unlikely]] {
130 deallocate_slow(assignment);
131 return;
132 }
133 assignment->next_free_list_entry = fixed_free_lists[0];
134 fixed_free_lists[0] = assignment;
135 util::poison_memory_region(assignment, sizeof(ValueAssignment));
136 }
137
138 void deallocate_slow(ValueAssignment *) noexcept;
139
140 void reset() noexcept {
141 alloc.reset();
142 fixed_free_lists = {};
143 }
144};
145
146} // namespace tpde