e4a733d5b2
git-subtree-dir: externals/biscuit git-subtree-split: 1d53c9e6cfe56544982200a5bac15a5de064936b
173 lines
5.6 KiB
C++
173 lines
5.6 KiB
C++
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <biscuit/assert.hpp>
|
|
|
|
namespace biscuit {
|
|
|
|
/**
|
|
* A label is a representation of an address that can be used with branch and jump instructions.
|
|
*
|
|
* Labels do not need to be bound to a location immediately. A label can be created
|
|
* to provide branches with a tentative, undecided location that is then bound
|
|
* at a later point in time.
|
|
*
|
|
* @note Any label that is created, is used with a branch instruction,
|
|
* but is *not* bound to a location (via Bind() in the assembler)
|
|
* will result in an assertion being invoked when the label instance's
|
|
* destructor is executed.
|
|
*
|
|
* @note A label may only be bound to one location. Any attempt to rebind
|
|
* a label that is already bound will result in an assertion being
|
|
* invoked.
|
|
*
|
|
* @par
|
|
* An example of binding a label:
|
|
*
|
|
* @code{.cpp}
|
|
* Assembler as{...};
|
|
* Label label;
|
|
*
|
|
* as.BNE(x2, x3, &label); // Use the label
|
|
* as.ADD(x7, x8, x9);
|
|
* as.XOR(x7, x10, x12);
|
|
* as.Bind(&label); // Bind the label to a location
|
|
* @endcode
|
|
*/
|
|
class Label {
|
|
public:
|
|
using Location = std::optional<ptrdiff_t>;
|
|
using LocationOffset = Location::value_type;
|
|
|
|
/**
|
|
* Default constructor.
|
|
*
|
|
* This constructor results in a label being constructed that is not
|
|
* bound to a particular location yet.
|
|
*/
|
|
explicit Label() = default;
|
|
|
|
/// Destructor
|
|
~Label() noexcept {
|
|
// It's a logic bug if something references a label and hasn't been handled.
|
|
//
|
|
// This is usually indicative of a scenario where a label is referenced but
|
|
// hasn't been bound to a location.
|
|
//
|
|
BISCUIT_ASSERT(IsResolved());
|
|
}
|
|
|
|
// We disable copying of labels, as this doesn't really make sense to do.
|
|
// It also presents a problem. When labels are being resolved, if we have
|
|
// two labels pointing to the same place, resolving the links to this address
|
|
// are going to clobber each other N times for however many copies of the label
|
|
// exist.
|
|
//
|
|
// This isn't a particularly major problem, since the resolving will still result
|
|
// in the same end result, but it does make it annoying to think about label interactions
|
|
// moving forward. Thus, I choose to simply not think about it at all!
|
|
//
|
|
Label(const Label&) = delete;
|
|
Label& operator=(const Label&) = delete;
|
|
|
|
// Moving labels on the other hand is totally fine, this is just pushing data around
|
|
// to another label while invalidating the label having it's data "stolen".
|
|
Label(Label&&) noexcept = default;
|
|
Label& operator=(Label&&) noexcept = default;
|
|
|
|
/**
|
|
* Determines whether or not this label instance has a location assigned to it.
|
|
*
|
|
* A label is considered bound if it has an assigned location.
|
|
*/
|
|
[[nodiscard]] bool IsBound() const noexcept {
|
|
return m_location.has_value();
|
|
}
|
|
|
|
/**
|
|
* Determines whether or not this label is resolved.
|
|
*
|
|
* A label is considered resolved when all referencing offsets have been handled.
|
|
*/
|
|
[[nodiscard]] bool IsResolved() const noexcept {
|
|
return m_offsets.empty();
|
|
}
|
|
|
|
/**
|
|
* Determines whether or not this label is unresolved.
|
|
*
|
|
* A label is considered unresolved if it still has any unhandled referencing offsets.
|
|
*/
|
|
[[nodiscard]] bool IsUnresolved() const noexcept {
|
|
return !IsResolved();
|
|
}
|
|
|
|
/**
|
|
* Retrieves the location for this label.
|
|
*
|
|
* @note If the returned location is empty, then this label has not been assigned
|
|
* a location yet.
|
|
*/
|
|
[[nodiscard]] Location GetLocation() const noexcept {
|
|
return m_location;
|
|
}
|
|
|
|
private:
|
|
// A label instance is inherently bound to the assembler it's
|
|
// used with, as the offsets within the label set depend on
|
|
// said assemblers code buffer.
|
|
friend class Assembler;
|
|
|
|
/**
|
|
* Binds a label to the given location.
|
|
*
|
|
* @param offset The instruction offset to bind this label to.
|
|
*
|
|
* @pre The label must not have already been bound to a previous location.
|
|
* Attempting to rebind a label is typically, in almost all scenarios,
|
|
* the source of bugs.
|
|
* Attempting to rebind an already bound label will result in an assertion
|
|
* being triggered.
|
|
*/
|
|
void Bind(LocationOffset offset) noexcept {
|
|
BISCUIT_ASSERT(!IsBound());
|
|
m_location = offset;
|
|
}
|
|
|
|
/**
|
|
* Marks the given address as dependent on this label.
|
|
*
|
|
* This is used in scenarios where a label exists, but has not yet been
|
|
* bound to a location yet. It's important to track these addresses,
|
|
* as we'll need to patch the dependent branch instructions with the
|
|
* proper offset once the label is finally bound by the assembler.
|
|
*
|
|
* During label binding, the offset will be calculated and inserted
|
|
* into dependent instructions.
|
|
*/
|
|
void AddOffset(LocationOffset offset) {
|
|
// If a label is already bound to a location, then offset tracking
|
|
// isn't necessary. Tripping this assert means we have a bug somewhere.
|
|
BISCUIT_ASSERT(!IsBound());
|
|
BISCUIT_ASSERT(IsNewOffset(offset));
|
|
|
|
m_offsets.insert(offset);
|
|
}
|
|
|
|
// Clears all the underlying offsets for this label.
|
|
void ClearOffsets() noexcept {
|
|
m_offsets.clear();
|
|
}
|
|
|
|
// Determines whether or not this address has already been added before.
|
|
[[nodiscard]] bool IsNewOffset(LocationOffset offset) const noexcept {
|
|
return m_offsets.find(offset) == m_offsets.cend();
|
|
}
|
|
|
|
std::set<LocationOffset> m_offsets;
|
|
Location m_location;
|
|
};
|
|
|
|
} // namespace biscuit
|