/ externals / biscuit / include / biscuit / label.hpp
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