TPDE
Loading...
Searching...
No Matches
FunctionWriterA64.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/FunctionWriter.hpp"
6#include <disarm64.h>
7
8namespace tpde::a64 {
9
10/// Helper class to write function text for AArch64.
11class FunctionWriterA64 : public FunctionWriter<FunctionWriterA64> {
12 friend class FunctionWriter<FunctionWriterA64>;
13
14 util::SmallVector<u32, 16> veneers;
15 u32 unresolved_cond_brs, unresolved_test_brs;
16
17public:
18 void begin_func(u32 expected_size) noexcept {
19 veneers.clear();
20 // Must clear unresolved count here, begin_func will call more_space.
21 unresolved_cond_brs = unresolved_test_brs = 0;
22 FunctionWriter::begin_func(expected_size);
23 }
24
25 void more_space(u32 size) noexcept;
26
27 bool try_write_inst(u32 inst) noexcept {
28 if (inst == 0) {
29 return false;
30 }
31 write(inst);
32 return true;
33 }
34
35 void write_inst(u32 inst) noexcept {
36 assert(inst != 0);
37 write(inst);
38 }
39
40 void write_inst_unchecked(u32 inst) noexcept {
41 assert(inst != 0);
42 write_unchecked(inst);
43 }
44
45 void label_ref(Label label, u32 off, LabelFixupKind kind) noexcept {
46 FunctionWriter::label_ref(label, off, kind);
47 if (kind == LabelFixupKind::AARCH64_COND_BR) {
48 unresolved_cond_brs++;
49 } else if (kind == LabelFixupKind::AARCH64_TEST_BR) {
50 unresolved_test_brs++;
51 }
52 }
53
54private:
55 void handle_fixups() noexcept;
56};
57
58inline void FunctionWriterA64::more_space(u32 size) noexcept {
59 if (allocated_size() >= (128 * 1024 * 1024)) {
60 // we do not support multiple text sections currently
61 TPDE_FATAL("AArch64 doesn't support sections larger than 128 MiB");
62 }
63
64 // If the section has no unresolved conditional branch, veneer_info is null.
65 // In that case, we don't need to do anything regarding veneers.
66 u32 unresolved_count = unresolved_test_brs + unresolved_cond_brs;
67 u32 veneer_size = sizeof(u32) * unresolved_count;
68 FunctionWriter::more_space(size + veneer_size + 4);
69 if (veneer_size == 0) {
70 return;
71 }
72
73 // TBZ has 14 bits, CBZ has 19 bits; but the first bit is the sign bit
74 u32 max_dist = unresolved_test_brs ? 4 << (14 - 1) : 4 << (19 - 1);
75 max_dist -= veneer_size; // must be able to reach last veneer
76 // TODO: get a better approximation of the first unresolved condbr after the
77 // last veneer.
78 u32 first_condbr = veneers.empty() ? 0 : veneers.back();
79 // If all condbrs can only jump inside the now-reserved memory, do nothing.
80 if (first_condbr + max_dist > allocated_size()) {
81 return;
82 }
83
84 u32 cur_off = offset();
85 veneers.push_back(cur_off + 4);
86 unresolved_test_brs = unresolved_cond_brs = 0;
87
88 *reinterpret_cast<u32 *>(data_begin + cur_off) = de64_B(veneer_size / 4 + 1);
89 std::memset(data_begin + cur_off + 4, 0, veneer_size);
90 cur_ptr() += veneer_size + 4;
91}
92
93inline void FunctionWriterA64::handle_fixups() noexcept {
94 for (const LabelFixup &fixup : label_fixups) {
95 u32 label_off = label_offset(fixup.label);
96 u32 *dst_ptr = reinterpret_cast<u32 *>(begin_ptr() + fixup.off);
97
98 auto fix_condbr = [&](unsigned nbits) {
99 i64 diff = i64(label_off) - i64(fixup.off);
100 assert(diff >= 0 && diff < 128 * 1024 * 1024);
101 // lowest two bits are ignored, highest bit is sign bit
102 if (diff >= (4 << (nbits - 1))) {
103 auto veneer =
104 std::lower_bound(veneers.begin(), veneers.end(), fixup.off);
105 assert(veneer != veneers.end());
106
107 // Create intermediate branch at v.begin
108 auto *br = reinterpret_cast<u32 *>(begin_ptr() + *veneer);
109 assert(*br == 0 && "overwriting instructions with veneer branch");
110 *br = de64_B((label_off - *veneer) / 4);
111 diff = *veneer - fixup.off;
112 *veneer += 4;
113 }
114 u32 off_mask = ((1 << nbits) - 1) << 5;
115 *dst_ptr = (*dst_ptr & ~off_mask) | ((diff / 4) << 5);
116 };
117
118 switch (fixup.kind) {
119 case LabelFixupKind::AARCH64_BR: {
120 // diff from entry to label (should be positive tho)
121 i64 diff = i64(label_off) - i64(fixup.off);
122 assert(diff >= 0 && diff < 128 * 1024 * 1024);
123 *dst_ptr = de64_B(diff / 4);
124 break;
125 }
126 case LabelFixupKind::AARCH64_COND_BR:
127 if (veneers.empty() || veneers.back() < fixup.off) {
128 assert(unresolved_cond_brs > 0);
129 unresolved_cond_brs -= 1;
130 }
131 fix_condbr(19); // CBZ/CBNZ has 19 bits.
132 break;
133 case LabelFixupKind::AARCH64_TEST_BR:
134 if (veneers.empty() || veneers.back() < fixup.off) {
135 assert(unresolved_test_brs > 0);
136 unresolved_test_brs -= 1;
137 }
138 fix_condbr(14); // TBZ/TBNZ has 14 bits.
139 break;
140 case LabelFixupKind::AARCH64_JUMP_TABLE: {
141 auto table_off = *dst_ptr;
142 *dst_ptr = (i32)label_off - (i32)table_off;
143 break;
144 }
145 default: TPDE_UNREACHABLE("unexpected label fixup kind");
146 }
147 }
148}
149
150} // namespace tpde::a64
void label_ref(Label label, u32 off, LabelFixupKind kind) noexcept
Reference label at given offset inside the code section.
util::SmallVector< LabelFixup > label_fixups
Helper class to write function text for AArch64.