label.hpp
1 #pragma once 2 3 #include <cstddef> 4 #include <optional> 5 #include <set> 6 #include <biscuit/assert.hpp> 7 8 namespace biscuit { 9 10 /** 11 * A label is a representation of an address that can be used with branch and jump instructions. 12 * 13 * Labels do not need to be bound to a location immediately. A label can be created 14 * to provide branches with a tentative, undecided location that is then bound 15 * at a later point in time. 16 * 17 * @note Any label that is created, is used with a branch instruction, 18 * but is *not* bound to a location (via Bind() in the assembler) 19 * will result in an assertion being invoked when the label instance's 20 * destructor is executed. 21 * 22 * @note A label may only be bound to one location. Any attempt to rebind 23 * a label that is already bound will result in an assertion being 24 * invoked. 25 * 26 * @par 27 * An example of binding a label: 28 * 29 * @code{.cpp} 30 * Assembler as{...}; 31 * Label label; 32 * 33 * as.BNE(x2, x3, &label); // Use the label 34 * as.ADD(x7, x8, x9); 35 * as.XOR(x7, x10, x12); 36 * as.Bind(&label); // Bind the label to a location 37 * @endcode 38 */ 39 class Label { 40 public: 41 using Location = std::optional<ptrdiff_t>; 42 using LocationOffset = Location::value_type; 43 44 /** 45 * Default constructor. 46 * 47 * This constructor results in a label being constructed that is not 48 * bound to a particular location yet. 49 */ 50 explicit Label() = default; 51 52 /// Destructor 53 ~Label() noexcept { 54 // It's a logic bug if something references a label and hasn't been handled. 55 // 56 // This is usually indicative of a scenario where a label is referenced but 57 // hasn't been bound to a location. 58 // 59 BISCUIT_ASSERT(IsResolved()); 60 } 61 62 // We disable copying of labels, as this doesn't really make sense to do. 63 // It also presents a problem. When labels are being resolved, if we have 64 // two labels pointing to the same place, resolving the links to this address 65 // are going to clobber each other N times for however many copies of the label 66 // exist. 67 // 68 // This isn't a particularly major problem, since the resolving will still result 69 // in the same end result, but it does make it annoying to think about label interactions 70 // moving forward. Thus, I choose to simply not think about it at all! 71 // 72 Label(const Label&) = delete; 73 Label& operator=(const Label&) = delete; 74 75 // Moving labels on the other hand is totally fine, this is just pushing data around 76 // to another label while invalidating the label having it's data "stolen". 77 Label(Label&&) noexcept = default; 78 Label& operator=(Label&&) noexcept = default; 79 80 /** 81 * Determines whether or not this label instance has a location assigned to it. 82 * 83 * A label is considered bound if it has an assigned location. 84 */ 85 [[nodiscard]] bool IsBound() const noexcept { 86 return m_location.has_value(); 87 } 88 89 /** 90 * Determines whether or not this label is resolved. 91 * 92 * A label is considered resolved when all referencing offsets have been handled. 93 */ 94 [[nodiscard]] bool IsResolved() const noexcept { 95 return m_offsets.empty(); 96 } 97 98 /** 99 * Determines whether or not this label is unresolved. 100 * 101 * A label is considered unresolved if it still has any unhandled referencing offsets. 102 */ 103 [[nodiscard]] bool IsUnresolved() const noexcept { 104 return !IsResolved(); 105 } 106 107 /** 108 * Retrieves the location for this label. 109 * 110 * @note If the returned location is empty, then this label has not been assigned 111 * a location yet. 112 */ 113 [[nodiscard]] Location GetLocation() const noexcept { 114 return m_location; 115 } 116 117 private: 118 // A label instance is inherently bound to the assembler it's 119 // used with, as the offsets within the label set depend on 120 // said assemblers code buffer. 121 friend class Assembler; 122 123 /** 124 * Binds a label to the given location. 125 * 126 * @param offset The instruction offset to bind this label to. 127 * 128 * @pre The label must not have already been bound to a previous location. 129 * Attempting to rebind a label is typically, in almost all scenarios, 130 * the source of bugs. 131 * Attempting to rebind an already bound label will result in an assertion 132 * being triggered. 133 */ 134 void Bind(LocationOffset offset) noexcept { 135 BISCUIT_ASSERT(!IsBound()); 136 m_location = offset; 137 } 138 139 /** 140 * Marks the given address as dependent on this label. 141 * 142 * This is used in scenarios where a label exists, but has not yet been 143 * bound to a location yet. It's important to track these addresses, 144 * as we'll need to patch the dependent branch instructions with the 145 * proper offset once the label is finally bound by the assembler. 146 * 147 * During label binding, the offset will be calculated and inserted 148 * into dependent instructions. 149 */ 150 void AddOffset(LocationOffset offset) { 151 // If a label is already bound to a location, then offset tracking 152 // isn't necessary. Tripping this assert means we have a bug somewhere. 153 BISCUIT_ASSERT(!IsBound()); 154 BISCUIT_ASSERT(IsNewOffset(offset)); 155 156 m_offsets.insert(offset); 157 } 158 159 // Clears all the underlying offsets for this label. 160 void ClearOffsets() noexcept { 161 m_offsets.clear(); 162 } 163 164 // Determines whether or not this address has already been added before. 165 [[nodiscard]] bool IsNewOffset(LocationOffset offset) const noexcept { 166 return m_offsets.find(offset) == m_offsets.cend(); 167 } 168 169 std::set<LocationOffset> m_offsets; 170 Location m_location; 171 }; 172 173 } // namespace biscuit