TPDE
Loading...
Searching...
No Matches
FunctionWriter.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/Assembler.hpp"
6#include "tpde/base.hpp"
7#include "tpde/util/SmallVector.hpp"
8#include <cstring>
9
10namespace tpde {
11
12enum class Label : u32 {
13};
14
15enum class LabelFixupKind : u8 {
16 ArchKindBegin,
17
18 X64_JMP_OR_MEM_DISP = ArchKindBegin,
19 X64_JUMP_TABLE,
20
21 AARCH64_BR = ArchKindBegin,
22 AARCH64_COND_BR,
23 AARCH64_TEST_BR,
24 AARCH64_JUMP_TABLE,
25};
26
27/// Helper class to write function text.
28template <typename Derived>
29class FunctionWriter {
30protected:
31 DataSection *section = nullptr;
32 u8 *data_begin = nullptr;
33 u8 *data_cur = nullptr;
34 u8 *data_reserve_end = nullptr;
35
36public:
37 /// Label offsets into section, ~0u indicates unplaced label.
38 util::SmallVector<u32> label_offsets;
39
40protected:
41 struct LabelFixup {
42 Label label;
43 u32 off;
44 LabelFixupKind kind;
45 };
46
47 /// Fixups for labels placed after their first use, processed at function end.
48 util::SmallVector<LabelFixup> label_fixups;
49
50 /// Growth size for more_space; adjusted exponentially after every grow.
51 u32 growth_size = 0x10000;
52
53public:
54 FunctionWriter() noexcept = default;
55
56 ~FunctionWriter() {
57 assert(data_cur == data_reserve_end &&
58 "must flush section writer before destructing");
59 }
60
61protected:
62 Derived *derived() noexcept { return static_cast<Derived *>(this); }
63
64public:
65 /// Get the SecRef of the current section.
66 SecRef get_sec_ref() const noexcept { return get_section().get_ref(); }
67
68 /// Get the current section.
69 DataSection &get_section() const noexcept {
70 assert(section != nullptr);
71 return *section;
72 }
73
74 /// Switch section writer to new section; must be flushed.
75 void switch_section(DataSection &new_section) noexcept {
76 assert(data_cur == data_reserve_end &&
77 "must flush section writer before switching sections");
78 section = &new_section;
79 data_begin = section->data.data();
80 data_cur = data_begin + section->data.size();
81 data_reserve_end = data_cur;
82 }
83
84 void begin_func(u32 expected_size) noexcept {
85 label_offsets.clear();
86 label_fixups.clear();
87 growth_size = expected_size;
88 ensure_space(expected_size);
89 }
90
91 void finish_func() noexcept { derived()->handle_fixups(); }
92
93 /// \name Text Writing
94 /// @{
95
96 /// Get the current offset into the section.
97 size_t offset() const noexcept { return data_cur - data_begin; }
98
99 /// Get the current allocated size of the section.
100 size_t allocated_size() const noexcept {
101 return data_reserve_end - data_begin;
102 }
103
104 /// Pointer to beginning of section data.
105 u8 *begin_ptr() noexcept { return data_begin; }
106
107 /// Modifiable pointer to current writing position of the section. Must not
108 /// be moved beyond the allocated region.
109 u8 *&cur_ptr() noexcept { return data_cur; }
110
111 void ensure_space(size_t size) noexcept {
112 assert(data_reserve_end >= data_cur);
113 if (size_t(data_reserve_end - data_cur) < size) [[unlikely]] {
114 derived()->more_space(size);
115 }
116 }
117
118 void more_space(size_t size) noexcept;
119
120 template <std::integral T>
121 void write_unchecked(T t) noexcept {
122 assert(size_t(data_reserve_end - data_cur) >= sizeof(T));
123 std::memcpy(data_cur, &t, sizeof(T));
124 data_cur += sizeof(T);
125 }
126
127 template <std::integral T>
128 void write(T t) noexcept {
129 ensure_space(sizeof(T));
130 write_unchecked<T>(t);
131 }
132
133 void flush() noexcept {
134 if (data_cur != data_reserve_end) {
135 section->data.resize(offset());
136 data_reserve_end = data_cur;
137#ifndef NDEBUG
138 section->locked = false;
139#endif
140 }
141 }
142
143 void align(size_t align) noexcept {
144 assert(align > 0 && (align & (align - 1)) == 0);
145 ensure_space(align);
146 // permit optimization when align is a constant.
147 std::memset(cur_ptr(), 0, align);
148 data_cur = data_begin + util::align_up(offset(), align);
149 section->align = std::max(section->align, u32(align));
150 }
151
152 /// @}
153
154 /// \name Labels
155 /// @{
156
157 /// Create a new unplaced label.
158 Label label_create() noexcept {
159 const Label label = Label(label_offsets.size());
160 label_offsets.push_back(~0u);
161 return label;
162 }
163
164 bool label_is_pending(Label label) const noexcept {
165 return label_offsets[u32(label)] == ~0u;
166 }
167
168 u32 label_offset(Label label) const noexcept {
169 assert(!label_is_pending(label));
170 return label_offsets[u32(label)];
171 }
172
173 /// Place unplaced label at the specified offset inside the section.
174 void label_place(Label label, u32 off) noexcept {
175 assert(label_is_pending(label));
176 label_offsets[u32(label)] = off;
177 }
178
179 /// Reference label at given offset inside the code section.
180 void label_ref(Label label, u32 off, LabelFixupKind kind) noexcept {
181 assert(label_is_pending(label));
182 label_fixups.emplace_back(LabelFixup{label, off, kind});
183 }
184
185 /// @}
186};
187
188template <typename Derived>
189void FunctionWriter<Derived>::more_space(size_t size) noexcept {
190 size_t cur_size = section->data.size();
191 size_t new_size;
192 if (cur_size + size <= section->data.capacity()) {
193 new_size = section->data.capacity();
194 } else {
195 new_size = cur_size + (size <= growth_size ? growth_size : size);
196
197 // Grow by factor 1.5
198 growth_size = growth_size + (growth_size >> 1);
199 // Max 16 MiB per grow.
200 growth_size = growth_size < 0x1000000 ? growth_size : 0x1000000;
201 }
202
203 const size_t off = offset();
204 section->data.resize_uninitialized(new_size);
205#ifndef NDEBUG
206 thread_local uint8_t rand = 1;
207 std::memset(section->data.data() + off, rand += 2, new_size - off);
208 section->locked = true;
209#endif
210
211 data_begin = section->data.data();
212 data_cur = data_begin + off;
213 data_reserve_end = data_begin + section->data.size();
214}
215
216} // namespace tpde
u32 growth_size
Growth size for more_space; adjusted exponentially after every grow.
void label_ref(Label label, u32 off, LabelFixupKind kind) noexcept
Reference label at given offset inside the code section.
size_t offset() const noexcept
Get the current offset into the section.
util::SmallVector< LabelFixup > label_fixups
Fixups for labels placed after their first use, processed at function end.
size_t allocated_size() const noexcept
Get the current allocated size of the section.
DataSection & get_section() const noexcept
Get the current section.
SecRef get_sec_ref() const noexcept
Get the SecRef of the current section.
u8 * begin_ptr() noexcept
Pointer to beginning of section data.
u8 *& cur_ptr() noexcept
Label label_create() noexcept
Create a new unplaced label.
void switch_section(DataSection &new_section) noexcept
Switch section writer to new section; must be flushed.
util::SmallVector< u32 > label_offsets
Label offsets into section, ~0u indicates unplaced label.
void label_place(Label label, u32 off) noexcept
Place unplaced label at the specified offset inside the section.