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