Skip to content

Commit

Permalink
assembler: Add PC-relative literal loads
Browse files Browse the repository at this point in the history
  • Loading branch information
OFFTKP committed Nov 9, 2024
1 parent 1f5546d commit 871b780
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 0 deletions.
1 change: 1 addition & 0 deletions examples/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(cpuinfo)
add_subdirectory(literal)
3 changes: 3 additions & 0 deletions examples/literal/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
add_executable(literal literal.cpp)
target_link_libraries(literal biscuit)
set_property(TARGET literal PROPERTY CXX_STANDARD 20)
73 changes: 73 additions & 0 deletions examples/literal/literal.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include <biscuit/assert.hpp>
#include <biscuit/assembler.hpp>

#include <iostream>

using namespace biscuit;

constexpr const static uint64_t literal1_value = 0x1234567890ABCDEF;
constexpr const static uint64_t literal2_value = 0x1122334455667788;
constexpr const static uint64_t literal3_value = 0xFEDCBA0987654321;
constexpr const static uint64_t literal4_value = 0xAABBCCDDEEFF0011;

void print_literals(uint64_t literal1, uint64_t literal2, uint64_t literal3, uint64_t literal4) {
std::cout << "Literal 1: " << std::hex << literal1 << std::endl;
std::cout << "Literal 2: " << std::hex << literal2 << std::endl;
std::cout << "Literal 3: " << std::hex << literal3 << std::endl;
std::cout << "Literal 4: " << std::hex << literal4 << std::endl;

BISCUIT_ASSERT(literal1 == literal1_value);
BISCUIT_ASSERT(literal2 == literal2_value);
BISCUIT_ASSERT(literal3 == literal3_value);
BISCUIT_ASSERT(literal4 == literal4_value);
}

int main() {
Assembler as(0x5000);

Literal64 literal1(literal1_value);
Literal64 literal2(literal2_value);
Literal64 literal3(literal3_value);
Literal64 literal4(literal4_value);

// Literal placed before the code, more than 0x1000 bytes away
as.Place(&literal1);

as.AdvanceBuffer(as.GetCodeBuffer().GetCursorOffset() + 0x1000);

// Literal placed before the code, less than 0x1000 bytes away
as.Place(&literal2);

void (*code)() = reinterpret_cast<void(*)()>(as.GetCursorPointer());
as.ADDI(sp, sp, -8);
as.SD(ra, 0, sp);

as.LD(a0, &literal1);

as.LD(a1, &literal2);

as.LD(a2, &literal3);

as.LD(a3, &literal4);

as.LI(t0, (uint64_t)print_literals);
as.JALR(t0);

as.LD(ra, 0, sp);
as.ADDI(sp, sp, 8);
as.RET();

// Literal placed after the code, less than 0x1000 bytes away
as.Place(&literal3);

as.AdvanceBuffer(as.GetCodeBuffer().GetCursorOffset() + 0x1000);

// Literal placed after the code, more than 0x1000 bytes away
as.Place(&literal4);

as.GetCodeBuffer().SetExecutable();

code();

return 0;
}
22 changes: 22 additions & 0 deletions include/biscuit/assembler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <biscuit/csr.hpp>
#include <biscuit/isa.hpp>
#include <biscuit/label.hpp>
#include <biscuit/literal.hpp>
#include <biscuit/registers.hpp>
#include <biscuit/vector.hpp>
#include <cstddef>
Expand Down Expand Up @@ -151,6 +152,13 @@ class Assembler {
*/
void Bind(Label* label);

/**
* Places a literal at the current offset within the code buffer.
*
* @param literal A non-null valid literal to place.
*/
void Place(Literal64* literal);

// RV32I Instructions

void ADD(GPR rd, GPR lhs, GPR rhs) noexcept;
Expand Down Expand Up @@ -269,6 +277,7 @@ class Assembler {
void ADDIW(GPR rd, GPR rs, int32_t imm) noexcept;
void ADDW(GPR rd, GPR lhs, GPR rhs) noexcept;
void LD(GPR rd, int32_t imm, GPR rs) noexcept;
void LD(GPR rd, Literal64* literal) noexcept;
void LWU(GPR rd, int32_t imm, GPR rs) noexcept;
void SD(GPR rs2, int32_t imm, GPR rs1) noexcept;

Expand Down Expand Up @@ -1523,6 +1532,19 @@ class Assembler {
// requires them.
void ResolveLabelOffsets(Label* label);

// Places a literal at the given offset.
template<typename T>
void PlaceAtOffset(Literal<T>* literal, Literal<T>::LocationOffset offset);

// Links the given literal and returns the offset to it.
template<typename T>
ptrdiff_t LinkAndGetOffset(Literal<T>* literal);

// Resolves all literal offsets and patches any necessary
// offsets into the load instructions that require them.
template<typename T>
void ResolveLiteralOffsets(Literal<T>* literal);

CodeBuffer m_buffer;
ArchFeature m_features = ArchFeature::RV64;
};
Expand Down
168 changes: 168 additions & 0 deletions include/biscuit/literal.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#pragma once

#include <cstddef>
#include <cstdint>
#include <optional>
#include <set>
#include <biscuit/assert.hpp>

namespace biscuit {

/**
* A Literal is a representation of a constant value that can be loaded into a register.
* This is useful for avoiding multiple instructions for loading big constants.
*
* Literals, like Labels, don't need to be placed immediately. They can be created
* and used with loads that require a Literal, and placed in the buffer at a later point.
*
* @note Any literal that is created, is used with a load instruction,
* but is *not* placed to a location (via Place() in the assembler)
* will result in an assertion being invoked when the literal instance's
* destructor is executed.
*
* @note A literal may only be placed to one location. Any attempt to place
* a literal that is already placed will result in an assertion being
* invoked.
*
* @par
* An example of placing a literal:
* @code{.cpp}
* Assembler as{...};
* Literal64 literal(0x1234567890ABCDEF);
*
* as.LD(x2, &literal); // Load the literal (emits a AUIPC+LD sequence)
* as.JR(x2); // Execution continues elsewhere
* as.Place(&literal); // Place the literal at this location in the buffer
* @endcode
*/
template<class T>
class Literal {
public:
using Location = std::optional<ptrdiff_t>;
using LocationOffset = Location::value_type;

/**
* This constructor results in a literal being constructed that is not
* placed at a particular location yet.
*
* @param value The value that this literal represents.
*/
explicit Literal(T value) : m_value{value} {}

/// Destructor
~Literal() noexcept {
// It's a logic bug if something references a literal and hasn't been handled.
//
// This is usually indicative of a scenario where a literal is referenced but
// hasn't been placed at a location.
//
BISCUIT_ASSERT(IsResolved());
}

// Copying disabled for the same reasons as Labels.
Literal(const Literal&) = delete;
Literal& operator=(const Literal&) = delete;

Literal(Literal&&) noexcept = default;
Literal& operator=(Literal&&) noexcept = default;

/**
* Determines whether or not this literal instance has a location assigned to it.
*
* A literal is considered placed if it has an assigned location.
*/
[[nodiscard]] bool IsPlaced() const noexcept {
return m_location.has_value();
}

/**
* Determines whether or not this literal is resolved.
*
* A literal is considered resolved when all referencing offsets have been handled.
*/
[[nodiscard]] bool IsResolved() const noexcept {
return m_offsets.empty();
}

/**
* Determines whether or not this literal is unresolved.
*
* A literal is considered unresolved if it still has any unhandled referencing offsets.
*/
[[nodiscard]] bool IsUnresolved() const noexcept {
return !IsResolved();
}

/**
* Retrieves the location for this literal.
*
* @note If the returned location is empty, then this literal has not been assigned
* a location yet.
*/
[[nodiscard]] Location GetLocation() const noexcept {
return m_location;
}

private:
// A literal instance is inherently bound to the assembler it's
// used with, as the offsets within the literal set depend on
// said assemblers code buffer.
friend class Assembler;

/**
* Places a literal to the given location.
*
* @param offset The offset to place this literal at.
*
* @returns The literal value so it can be copied to memory by the assembler.
*
* @pre The literal must not have already been placed at a previous location.
* Attempting to place a literal multiple times is typically, in almost all scenarios,
* the source of bugs.
* Attempting to place an already placed literal will result in an assertion
* being triggered.
*/
[[nodiscard]] const T& Place(LocationOffset offset) noexcept {
BISCUIT_ASSERT(!IsPlaced());
m_location = offset;
return m_value;
}

/**
* Marks the given address as dependent on this literal.
*
* This is used in scenarios where a literal exists, but has not yet been
* placed at a location yet. It's important to track these addresses,
* as we'll need to patch the dependent load instructions with the
* proper offset once the literal is finally placed by the assembler.
*
* During literal placement, the offset will be calculated and inserted
* into dependent instructions.
*/
void AddOffset(LocationOffset offset) {
// If a literal is already placed at a location, then offset tracking
// isn't necessary. Tripping this assert means we have a bug somewhere.
BISCUIT_ASSERT(!IsPlaced());
BISCUIT_ASSERT(IsNewOffset(offset));

m_offsets.insert(offset);
}

// Clears all the underlying offsets for this literal.
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;
const T m_value;
};

using Literal64 = Literal<uint64_t>;

} // namespace biscuit
Loading

0 comments on commit 871b780

Please sign in to comment.