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/DWARF.hpp"
7#include "tpde/base.hpp"
8#include "tpde/util/SmallVector.hpp"
9#include "tpde/util/VectorWriter.hpp"
10#include <cstring>
11#include <span>
12
13namespace tpde {
14
15enum class Label : u32 {
16};
17
18enum class LabelFixupKind : u8 {
19 ArchKindBegin,
20
21 X64_JMP_OR_MEM_DISP = ArchKindBegin,
22 X64_JUMP_TABLE,
23
24 AARCH64_BR = ArchKindBegin,
25 AARCH64_COND_BR,
26 AARCH64_TEST_BR,
27 AARCH64_JUMP_TABLE,
28};
29
30/// Architecture-independent base for helper class to write function text.
31class FunctionWriterBase {
32protected:
33 struct TargetCIEInfo {
34 /// The initial instructions for the CIE.
35 std::span<const u8> instrs;
36 /// The return address register for the CIE.
37 u8 return_addr_register;
38 /// Code alignment factor for the CIE, ULEB128, must be one byte.
39 u8 code_alignment_factor;
40 /// Data alignment factor for the CIE, SLEB128, must be one byte.
41 u8 data_alignment_factor;
42 };
43
44private:
45 Assembler *assembler;
46 const TargetCIEInfo &cie_info;
47
48protected:
49 DataSection *section = nullptr;
50 u8 *data_begin = nullptr;
51 u8 *data_cur = nullptr;
52 u8 *data_reserve_end = nullptr;
53
54 u32 func_begin; ///< Begin offset of the current function.
55 u32 reloc_begin; ///< Begin of relocations for current function.
56
57 u32 label_skew; ///< Offset to subtract from all label offsets.
58 /// Label offsets into section, ~0u indicates unplaced label.
59 util::SmallVector<u32> label_offsets;
60
61protected:
62 struct LabelFixup {
63 Label label;
64 u32 off;
65 LabelFixupKind kind;
66 };
67
68 /// Fixups for labels placed after their first use, processed at function end.
69 util::SmallVector<LabelFixup> label_fixups;
70
71 /// Growth size for more_space; adjusted exponentially after every grow.
72 u32 growth_size = 0x10000;
73
74private:
75 DataSection *eh_frame_section;
76
77public:
78 util::VectorWriter eh_writer;
79
80private:
81 /// The current personality function (if any)
82 SymRef cur_personality_func_addr;
83 u32 eh_cur_cie_off = 0u;
84 u32 fde_start;
85
86 struct ExceptCallSiteInfo {
87 /// Start offset *in section* (not inside function)
88 u64 start;
89 u64 len;
90 Label landing_pad;
91 u32 action_entry;
92 };
93
94 /// Call Sites for current function
95 util::SmallVector<ExceptCallSiteInfo, 0> except_call_site_table;
96
97 /// Temporary storage for encoding call sites
98 util::SmallVector<u8, 0> except_encoded_call_sites;
99 /// Action Table for current function
100 util::SmallVector<u8, 0> except_action_table;
101 /// The type_info table (contains the symbols which contain the pointers to
102 /// the type_info)
103 util::SmallVector<SymRef, 0> except_type_info_table;
104 /// Table for exception specs
105 util::SmallVector<u8, 0> except_spec_table;
106
107protected:
108 FunctionWriterBase(const TargetCIEInfo &cie_info) noexcept
109 : cie_info(cie_info) {}
110
111 ~FunctionWriterBase() {
112 assert(data_cur == data_reserve_end &&
113 "must flush section writer before destructing");
114 }
115
116public:
117 /// Get the SecRef of the current section.
118 SecRef get_sec_ref() const noexcept { return get_section().get_ref(); }
119
120 /// Get the current section.
121 DataSection &get_section() const noexcept {
122 assert(section != nullptr);
123 return *section;
124 }
125
126 /// Switch section writer to new section; must be flushed.
127 void switch_section(DataSection &new_section) noexcept {
128 assert(data_cur == data_reserve_end &&
129 "must flush section writer before switching sections");
130 section = &new_section;
131 data_begin = section->data.data();
132 data_cur = data_begin + section->data.size();
133 data_reserve_end = data_cur;
134 }
135
136 void begin_module(Assembler &assembler) noexcept;
137
138 void end_module() noexcept;
139
140 void begin_func() noexcept;
141
142 /// Get the current offset into the section.
143 size_t offset() const noexcept { return data_cur - data_begin; }
144
145 /// Get the current allocated size of the section.
146 size_t allocated_size() const noexcept {
147 return data_reserve_end - data_begin;
148 }
149
150 /// Pointer to beginning of section data.
151 u8 *begin_ptr() noexcept { return data_begin; }
152
153 /// Modifiable pointer to current writing position of the section. Must not
154 /// be moved beyond the allocated region.
155 u8 *&cur_ptr() noexcept { return data_cur; }
156
157 void more_space(size_t size) noexcept;
158
159 /// Remove bytes and adjust labels/relocations accordingly. The covered region
160 /// must be before the first label, i.e., this function can only be used to
161 /// cut out bytes from the function prologue.
162 void remove_prologue_bytes(u32 start, u32 size) noexcept;
163
164 void flush() noexcept {
165 if (data_cur != data_reserve_end) {
166 section->data.resize(offset());
167 data_reserve_end = data_cur;
168#ifndef NDEBUG
169 section->locked = false;
170#endif
171 }
172 }
173
174 /// \name Labels
175 /// @{
176
177 /// Create a new unplaced label.
178 Label label_create() noexcept {
179 const Label label = Label(label_offsets.size());
180 label_offsets.push_back(~0u);
181 return label;
182 }
183
184 bool label_is_pending(Label label) const noexcept {
185 return label_offsets[u32(label)] == ~0u;
186 }
187
188 u32 label_offset(Label label) const noexcept {
189 assert(!label_is_pending(label));
190 return label_offsets[u32(label)] - label_skew;
191 }
192
193 /// Place unplaced label at the specified offset inside the section.
194 void label_place(Label label, u32 off) noexcept {
195 assert(label_skew == 0 && "label_place called after prologue truncation");
196 assert(label_is_pending(label));
197 label_offsets[u32(label)] = off;
198 }
199
200 /// Reference label at given offset inside the code section.
201 void label_ref(Label label, u32 off, LabelFixupKind kind) noexcept {
202 // We also permit this to be called even if label is already placed to
203 // simplify code at the call site. It might be preferable, however, to
204 // immediately write the final value.
205 label_fixups.emplace_back(LabelFixup{label, off, kind});
206 }
207
208 /// @}
209
210 /// \name DWARF CFI
211 /// @{
212
213 static constexpr u32 write_eh_inst(u8 *dst, u8 opcode, u64 arg) noexcept {
214 if (opcode & dwarf::DWARF_CFI_PRIMARY_OPCODE_MASK) {
215 assert((arg & dwarf::DWARF_CFI_PRIMARY_OPCODE_MASK) == 0);
216 *dst = opcode | arg;
217 return 1;
218 }
219 *dst++ = opcode;
220 return 1 + util::uleb_write(dst, arg);
221 }
222
223 static constexpr u32
224 write_eh_inst(u8 *dst, u8 opcode, u64 arg1, u64 arg2) noexcept {
225 u8 *base = dst;
226 dst += write_eh_inst(dst, opcode, arg1);
227 dst += util::uleb_write(dst, arg2);
228 return dst - base;
229 }
230
231 void eh_align_frame() noexcept;
232 void eh_write_inst(u8 opcode) noexcept { this->eh_writer.write<u8>(opcode); }
233 void eh_write_inst(u8 opcode, u64 arg) noexcept;
234 void eh_write_inst(u8 opcode, u64 first_arg, u64 second_arg) noexcept;
235 /// Write CFA_advance_loc; size must be scaled by code alignment factor.
236 void eh_advance_raw(u64 size_units) noexcept;
237 /// Write CFA_advance_loc with code alignment factor 1.
238 void eh_advance(u64 size) noexcept { eh_advance_raw(size); }
239
240private:
241 void eh_init_cie(SymRef personality_func_addr = SymRef()) noexcept;
242
243public:
244 void eh_begin_fde(SymRef personality_func_addr = SymRef()) noexcept;
245 void eh_end_fde() noexcept;
246
247 /// @}
248
249 /// \name Itanium Exception ABI
250 /// @{
251
252 void except_encode_func() noexcept;
253
254 /// add an entry to the call-site table
255 /// must be called in strictly increasing order wrt text_off
256 void except_add_call_site(u32 text_off,
257 u32 len,
258 Label landing_pad,
259 bool is_cleanup) noexcept;
260
261 /// Add a cleanup action to the action table
262 /// *MUST* be the last one
264
265 /// add an action to the action table
266 /// An invalid SymRef signals a catch(...)
267 void except_add_action(bool first_action, SymRef type_sym) noexcept;
268
269 void except_add_empty_spec_action(bool first_action) noexcept;
270
271 u32 except_type_idx_for_sym(SymRef sym) noexcept;
272
273 /// @}
274};
275
276/// Helper class to write function text.
277template <typename Derived>
278class FunctionWriter : public FunctionWriterBase {
279protected:
280 FunctionWriter(const TargetCIEInfo &cie_info) noexcept
281 : FunctionWriterBase(cie_info) {}
282
283 Derived *derived() noexcept { return static_cast<Derived *>(this); }
284
285public:
286 void begin_func(u32 align, u32 expected_size) noexcept {
287 growth_size = expected_size;
288 ensure_space(align + expected_size);
289 derived()->align(align);
290 FunctionWriterBase::begin_func();
291 }
292
293 void finish_func() noexcept { derived()->handle_fixups(); }
294
295 /// \name Text Writing
296 /// @{
297
298 /// Ensure that at least size bytes are available.
299 void ensure_space(size_t size) noexcept {
300 assert(data_reserve_end >= data_cur);
301 if (size_t(data_reserve_end - data_cur) < size) [[unlikely]] {
302 derived()->more_space(size);
303 }
304 }
305
306 template <std::integral T>
307 void write_unchecked(T t) noexcept {
308 assert(size_t(data_reserve_end - data_cur) >= sizeof(T));
309 std::memcpy(data_cur, &t, sizeof(T));
310 data_cur += sizeof(T);
311 }
312
313 template <std::integral T>
314 void write(T t) noexcept {
315 ensure_space(sizeof(T));
316 write_unchecked<T>(t);
317 }
318
319 void align(size_t align) noexcept {
320 assert(align > 0 && (align & (align - 1)) == 0);
321 ensure_space(align);
322 // permit optimization when align is a constant.
323 std::memset(cur_ptr(), 0, align);
324 data_cur = data_begin + util::align_up(offset(), align);
325 section->align = std::max(section->align, u32(align));
326 }
327
328 /// @}
329};
330
331} // namespace tpde
Assembler base class.
Architecture-independent base for helper class to write function text.
DataSection & get_section() const noexcept
Get the current section.
void except_add_cleanup_action() noexcept
Add a cleanup action to the action table MUST be the last one.
Label label_create() noexcept
Create a new unplaced label.
util::SmallVector< LabelFixup > label_fixups
Fixups for labels placed after their first use, processed at function end.
util::SmallVector< u32 > label_offsets
Label offsets into section, ~0u indicates unplaced label.
void switch_section(DataSection &new_section) noexcept
Switch section writer to new section; must be flushed.
void except_add_call_site(u32 text_off, u32 len, Label landing_pad, bool is_cleanup) noexcept
add an entry to the call-site table must be called in strictly increasing order wrt text_off
size_t allocated_size() const noexcept
Get the current allocated size of the section.
u8 *& cur_ptr() noexcept
Modifiable pointer to current writing position of the section.
u8 * begin_ptr() noexcept
Pointer to beginning of section data.
void eh_advance(u64 size) noexcept
Write CFA_advance_loc with code alignment factor 1.
u32 func_begin
Begin offset of the current function.
void eh_advance_raw(u64 size_units) noexcept
Write CFA_advance_loc; size must be scaled by code alignment factor.
void except_add_action(bool first_action, SymRef type_sym) noexcept
add an action to the action table An invalid SymRef signals a catch(...)
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.
u32 reloc_begin
Begin of relocations for current function.
u32 label_skew
Offset to subtract from all label offsets.
void label_place(Label label, u32 off) noexcept
Place unplaced label at the specified offset inside the section.
void remove_prologue_bytes(u32 start, u32 size) noexcept
Remove bytes and adjust labels/relocations accordingly.
u32 growth_size
Growth size for more_space; adjusted exponentially after every grow.
SecRef get_sec_ref() const noexcept
Get the SecRef of the current section.
void ensure_space(size_t size) noexcept
Ensure that at least size bytes are available.