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
130 ~FunctionWriterBase() {
131 assert(data_cur == data_reserve_end &&
132 "must flush section writer before destructing");
133 }
134
135public:
136 /// Get the SecRef of the current section.
137 SecRef get_sec_ref() const { return get_section().get_ref(); }
138
139 /// Get the current section.
140 DataSection &get_section() const {
141 assert(section != nullptr);
142 return *section;
143 }
144
145 /// Switch section writer to new section; must be flushed.
146 void switch_section(DataSection &new_section) {
147 assert(data_cur == data_reserve_end &&
148 "must flush section writer before switching sections");
149 section = &new_section;
150 data_begin = section->data.data();
151 data_cur = data_begin + section->data.size();
152 data_reserve_end = data_cur;
153 }
154
155 void begin_module(Assembler &assembler);
156
157 void end_module();
158
159 void begin_func();
160
161 /// Get the current offset into the section.
162 size_t offset() const { return data_cur - data_begin; }
163
164 /// Get the current allocated size of the section.
165 size_t allocated_size() const { return data_reserve_end - data_begin; }
166
167 /// Pointer to beginning of section data.
168 u8 *begin_ptr() { return data_begin; }
169
170 /// Modifiable pointer to current writing position of the section. Must not
171 /// be moved beyond the allocated region.
172 u8 *&cur_ptr() { return data_cur; }
173
174protected:
175 void more_space(size_t size);
176
177public:
178 /// Record relocation at the given offset.
179 void reloc(SymRef sym, u32 type, u64 off, i64 addend = 0) {
180 assembler->reloc_sec(get_sec_ref(), sym, type, off, addend);
181 }
182
183 /// Remove bytes and adjust labels/relocations accordingly. The covered region
184 /// must be before the first label, i.e., this function can only be used to
185 /// cut out bytes from the function prologue.
186 void remove_prologue_bytes(u32 start, u32 size);
187
188 void flush() {
189 if (data_cur != data_reserve_end) {
190 section->data.resize(offset());
191 data_reserve_end = data_cur;
192#ifndef NDEBUG
193 section->locked = false;
194#endif
195 }
196 }
197
198 /// \name Labels
199 /// @{
200
201 /// Create a new unplaced label.
202 Label label_create() {
203 const Label label = Label(label_offsets.size());
204 label_offsets.push_back(~0u);
205 return label;
206 }
207
208 bool label_is_pending(Label label) const {
209 return label_offsets[u32(label)] == ~0u;
210 }
211
212 u32 label_offset(Label label) const {
213 assert(!label_is_pending(label));
214 return label_offsets[u32(label)] - label_skew;
215 }
216
217 /// Place unplaced label at the specified offset inside the section.
218 void label_place(Label label, u32 off) {
219 assert(label_skew == 0 && "label_place called after prologue truncation");
220 assert(label_is_pending(label));
221 label_offsets[u32(label)] = off;
222 }
223
224 /// Reference label at given offset inside the code section.
225 void label_ref(Label label, u32 off, LabelFixupKind kind) {
226 // We also permit this to be called even if label is already placed to
227 // simplify code at the call site. It might be preferable, however, to
228 // immediately write the final value.
229 label_fixups.emplace_back(LabelFixup{label, off, kind});
230 }
231
232 /// @}
233
234protected:
235 /// Allocate a jump table for the current location.
236 JumpTable &alloc_jump_table(u32 size, Reg idx, Reg tmp);
237
238 SecRef get_jump_table_section() {
239 // TODO: move jump tables to separate read-only data section if function is
240 // not in the default text section (e.g., part of a section group).
241 return assembler->get_default_section(SectionKind::ReadOnly);
242 }
243
244 /// \name DWARF CFI
245 /// @{
246
247public:
248 static constexpr u32 write_eh_inst(u8 *dst, u8 opcode, u64 arg) {
249 if (opcode & dwarf::DWARF_CFI_PRIMARY_OPCODE_MASK) {
250 assert((arg & dwarf::DWARF_CFI_PRIMARY_OPCODE_MASK) == 0);
251 *dst = opcode | arg;
252 return 1;
253 }
254 *dst++ = opcode;
255 return 1 + util::uleb_write(dst, arg);
256 }
257
258 static constexpr u32 write_eh_inst(u8 *dst, u8 opcode, u64 arg1, u64 arg2) {
259 u8 *base = dst;
260 dst += write_eh_inst(dst, opcode, arg1);
261 dst += util::uleb_write(dst, arg2);
262 return dst - base;
263 }
264
265 void eh_align_frame();
266 void eh_write_inst(u8 opcode) { this->eh_writer.write<u8>(opcode); }
267 void eh_write_inst(u8 opcode, u64 arg);
268 void eh_write_inst(u8 opcode, u64 first_arg, u64 second_arg);
269 /// Write CFA_advance_loc; size must be scaled by code alignment factor.
270 void eh_advance_raw(u64 size_units);
271 /// Write CFA_advance_loc with code alignment factor 1.
272 void eh_advance(u64 size) { eh_advance_raw(size); }
273
274private:
275 void eh_init_cie(SymRef personality_func_addr = SymRef());
276
277public:
278 void eh_begin_fde(SymRef personality_func_addr = SymRef());
279 void eh_end_fde();
280
281 /// @}
282
283 /// \name Itanium Exception ABI
284 /// @{
285
286 void except_encode_func();
287
288 /// add an entry to the call-site table
289 /// must be called in strictly increasing order wrt text_off
290 void except_add_call_site(u32 text_off,
291 u32 len,
292 Label landing_pad,
293 bool is_cleanup);
294
295 /// Add a cleanup action to the action table
296 /// *MUST* be the last one
298
299 /// add an action to the action table
300 /// An invalid SymRef signals a catch(...)
301 void except_add_action(bool first_action, SymRef type_sym);
302
303 void except_add_empty_spec_action(bool first_action);
304
305 u32 except_type_idx_for_sym(SymRef sym);
306
307 /// @}
308};
309
310/// Helper class to write function text.
311template <typename Derived>
312class FunctionWriter : public FunctionWriterBase {
313protected:
314 FunctionWriter(const TargetCIEInfo &cie_info)
315 : FunctionWriterBase(cie_info) {}
316
317 Derived *derived() { return static_cast<Derived *>(this); }
318
319public:
320 void begin_func(u32 align, u32 expected_size) {
321 growth_size = expected_size;
322 ensure_space(align + expected_size);
323 derived()->align(align);
324 FunctionWriterBase::begin_func();
325 }
326
327 void finish_func() { derived()->handle_fixups(); }
328
329 /// \name Text Writing
330 /// @{
331
332 /// Ensure that at least size bytes are available.
333 void ensure_space(size_t size) {
334 assert(data_reserve_end >= data_cur);
335 if (size_t(data_reserve_end - data_cur) < size) [[unlikely]] {
336 derived()->more_space(size);
337 }
338 }
339
340 template <std::integral T>
341 void write_unchecked(T t) {
342 assert(size_t(data_reserve_end - data_cur) >= sizeof(T));
343 std::memcpy(data_cur, &t, sizeof(T));
344 data_cur += sizeof(T);
345 }
346
347 template <std::integral T>
348 void write(T t) {
349 ensure_space(sizeof(T));
350 write_unchecked<T>(t);
351 }
352
353 void align(size_t align) {
354 assert(align > 0 && (align & (align - 1)) == 0);
355 ensure_space(align);
356 // permit optimization when align is a constant.
357 std::memset(cur_ptr(), 0, align);
358 data_cur = data_begin + util::align_up(offset(), align);
359 section->align = std::max(section->align, u32(align));
360 }
361
362 /// @}
363};
364
365} // 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.