Skip to content

[lldb] Add register field enum class #90063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 65 additions & 7 deletions lldb/include/lldb/Target/RegisterFlags.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,42 @@
#include <string>
#include <vector>

#include "llvm/ADT/StringSet.h"

namespace lldb_private {

class Stream;
class Log;

class FieldEnum {
public:
struct Enumerator {
uint64_t m_value;
// Short name for the value. Shown in tables and when printing the field's
// value. For example "RZ".
std::string m_name;

Enumerator(uint64_t value, std::string name)
: m_value(value), m_name(std::move(name)) {}

void ToXML(Stream &strm) const;
};

typedef std::vector<Enumerator> Enumerators;

FieldEnum(std::string id, const Enumerators &enumerators);

const Enumerators &GetEnumerators() const { return m_enumerators; }

const std::string &GetID() const { return m_id; }

void ToXML(Stream &strm, unsigned size) const;

private:
std::string m_id;
Enumerators m_enumerators;
};

class RegisterFlags {
public:
class Field {
Expand All @@ -26,17 +57,27 @@ class RegisterFlags {
/// significant bit. The start bit must be <= the end bit.
Field(std::string name, unsigned start, unsigned end);

/// Construct a field that also has some known enum values.
Field(std::string name, unsigned start, unsigned end,
const FieldEnum *enum_type);

/// Construct a field that occupies a single bit.
Field(std::string name, unsigned bit_position)
: m_name(std::move(name)), m_start(bit_position), m_end(bit_position) {}
Field(std::string name, unsigned bit_position);

/// Get size of the field in bits. Will always be at least 1.
unsigned GetSizeInBits() const { return m_end - m_start + 1; }
unsigned GetSizeInBits() const;

/// Identical to GetSizeInBits, but for the GDB client to use.
static unsigned GetSizeInBits(unsigned start, unsigned end);

/// A mask that covers all bits of the field.
uint64_t GetMask() const {
return (((uint64_t)1 << (GetSizeInBits())) - 1) << m_start;
}
uint64_t GetMask() const;

/// The maximum unsigned value that could be contained in this field.
uint64_t GetMaxValue() const;

/// Identical to GetMaxValue but for the GDB client to use.
static uint64_t GetMaxValue(unsigned start, unsigned end);

/// Extract value of the field from a whole register value.
uint64_t GetValue(uint64_t register_value) const {
Expand All @@ -46,6 +87,7 @@ class RegisterFlags {
const std::string &GetName() const { return m_name; }
unsigned GetStart() const { return m_start; }
unsigned GetEnd() const { return m_end; }
const FieldEnum *GetEnum() const { return m_enum_type; }
bool Overlaps(const Field &other) const;
void log(Log *log) const;

Expand All @@ -69,12 +111,15 @@ class RegisterFlags {

private:
std::string m_name;

/// Start/end bit positions. Where start N, end N means a single bit
/// field at position N. We expect that start <= end. Bit positions begin
/// at 0.
/// Start is the LSB, end is the MSB.
unsigned m_start;
unsigned m_end;

const FieldEnum *m_enum_type;
};

/// This assumes that:
Expand All @@ -89,6 +134,10 @@ class RegisterFlags {
/// when runtime field detection is needed.
void SetFields(const std::vector<Field> &fields);

/// Make a string where each line contains the name of a field that has
/// enum values, and lists what those values are.
std::string DumpEnums(uint32_t max_width) const;

// Reverse the order of the fields, keeping their values the same.
// For example a field from bit 31 to 30 with value 0b10 will become bits
// 1 to 0, with the same 0b10 value.
Expand Down Expand Up @@ -118,9 +167,18 @@ class RegisterFlags {
/// be split into many tables as needed.
std::string AsTable(uint32_t max_width) const;

// Output XML that describes this set of flags.
/// Output XML that describes this set of flags.
/// EnumsToXML should have been called before this.
void ToXML(Stream &strm) const;

/// Enum types must be defined before use, and
/// GDBRemoteCommunicationServerLLGS view of the register types is based only
/// on the registers. So this method emits any enum types that the upcoming
/// set of fields may need. "seen" is a set of Enum IDs that we have already
/// printed, that is updated with any printed by this call. This prevents us
/// printing the same enum multiple times.
void EnumsToXML(Stream &strm, llvm::StringSet<> &seen) const;

private:
const std::string m_id;
/// Size in bytes
Expand Down
198 changes: 196 additions & 2 deletions lldb/source/Target/RegisterFlags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,41 @@

#include "llvm/ADT/StringExtras.h"

#include <limits>
#include <numeric>
#include <optional>

using namespace lldb_private;

RegisterFlags::Field::Field(std::string name, unsigned start, unsigned end)
: m_name(std::move(name)), m_start(start), m_end(end) {
: m_name(std::move(name)), m_start(start), m_end(end),
m_enum_type(nullptr) {
assert(m_start <= m_end && "Start bit must be <= end bit.");
}

RegisterFlags::Field::Field(std::string name, unsigned bit_position)
: m_name(std::move(name)), m_start(bit_position), m_end(bit_position),
m_enum_type(nullptr) {}

RegisterFlags::Field::Field(std::string name, unsigned start, unsigned end,
const FieldEnum *enum_type)
: m_name(std::move(name)), m_start(start), m_end(end),
m_enum_type(enum_type) {
if (m_enum_type) {
// Check that all values fit into this field. The XML parser will also
// do this check so at runtime nothing should fail this check.
// We can also make enums in C++ at compile time, which might fail this
// check, so we catch them before it makes it into a release.
uint64_t max_value = GetMaxValue();
UNUSED_IF_ASSERT_DISABLED(max_value);
for (const auto &enumerator : m_enum_type->GetEnumerators()) {
UNUSED_IF_ASSERT_DISABLED(enumerator);
assert(enumerator.m_value <= max_value &&
"Enumerator value exceeds maximum value for this field");
}
}
}

void RegisterFlags::Field::log(Log *log) const {
LLDB_LOG(log, " Name: \"{0}\" Start: {1} End: {2}", m_name.c_str(), m_start,
m_end);
Expand Down Expand Up @@ -53,6 +78,35 @@ unsigned RegisterFlags::Field::PaddingDistance(const Field &other) const {
return lhs_start - rhs_end - 1;
}

unsigned RegisterFlags::Field::GetSizeInBits(unsigned start, unsigned end) {
return end - start + 1;
}

unsigned RegisterFlags::Field::GetSizeInBits() const {
return GetSizeInBits(m_start, m_end);
}

uint64_t RegisterFlags::Field::GetMaxValue(unsigned start, unsigned end) {
uint64_t max = std::numeric_limits<uint64_t>::max();
unsigned bits = GetSizeInBits(start, end);
// If the field is >= 64 bits the shift below would be undefined.
// We assume the GDB client has discarded any field that would fail this
// assert, it's only to check information we define directly in C++.
assert(bits <= 64 && "Cannot handle field with size > 64 bits");
if (bits < 64) {
max = ((uint64_t)1 << bits) - 1;
}
return max;
}

uint64_t RegisterFlags::Field::GetMaxValue() const {
return GetMaxValue(m_start, m_end);
}

uint64_t RegisterFlags::Field::GetMask() const {
return GetMaxValue() << m_start;
}

void RegisterFlags::SetFields(const std::vector<Field> &fields) {
// We expect that the XML processor will discard anything describing flags but
// with no fields.
Expand Down Expand Up @@ -190,6 +244,132 @@ std::string RegisterFlags::AsTable(uint32_t max_width) const {
return table;
}

// Print enums as:
// value = name, value2 = name2
// Subject to the limits of the terminal width.
static void DumpEnumerators(StreamString &strm, size_t indent,
size_t current_width, uint32_t max_width,
const FieldEnum::Enumerators &enumerators) {
for (auto it = enumerators.cbegin(); it != enumerators.cend(); ++it) {
StreamString enumerator_strm;
// The first enumerator of a line doesn't need to be separated.
if (current_width != indent)
enumerator_strm << ' ';

enumerator_strm.Printf("%" PRIu64 " = %s", it->m_value, it->m_name.c_str());

// Don't put "," after the last enumerator.
if (std::next(it) != enumerators.cend())
enumerator_strm << ",";

llvm::StringRef enumerator_string = enumerator_strm.GetString();
// If printing the next enumerator would take us over the width, start
// a new line. However, if we're printing the first enumerator of this
// line, don't start a new one. Resulting in there being at least one per
// line.
//
// This means for very small widths we get:
// A: 0 = foo,
// 1 = bar
// Instead of:
// A:
// 0 = foo,
// 1 = bar
if ((current_width + enumerator_string.size() > max_width) &&
current_width != indent) {
current_width = indent;
strm << '\n' << std::string(indent, ' ');
// We're going to a new line so we don't need a space before the
// name of the enumerator.
enumerator_string = enumerator_string.drop_front();
}

current_width += enumerator_string.size();
strm << enumerator_string;
}
}

std::string RegisterFlags::DumpEnums(uint32_t max_width) const {
StreamString strm;
bool printed_enumerators_once = false;

for (const auto &field : m_fields) {
const FieldEnum *enum_type = field.GetEnum();
if (!enum_type)
continue;

const FieldEnum::Enumerators &enumerators = enum_type->GetEnumerators();
if (enumerators.empty())
continue;

// Break between enumerators of different fields.
if (printed_enumerators_once)
strm << "\n\n";
else
printed_enumerators_once = true;

std::string name_string = field.GetName() + ": ";
size_t indent = name_string.size();
size_t current_width = indent;

strm << name_string;

DumpEnumerators(strm, indent, current_width, max_width, enumerators);
}

return strm.GetString().str();
}

void RegisterFlags::EnumsToXML(Stream &strm, llvm::StringSet<> &seen) const {
for (const Field &field : m_fields)
if (const FieldEnum *enum_type = field.GetEnum()) {
const std::string &id = enum_type->GetID();
if (!seen.contains(id)) {
enum_type->ToXML(strm, GetSize());
seen.insert(id);
}
}
}

void FieldEnum::ToXML(Stream &strm, unsigned size) const {
// Example XML:
// <enum id="foo" size="4">
// <evalue name="bar" value="1"/>
// </enum>
// Note that "size" is only emitted for GDB compatibility, LLDB does not need
// it.

strm.Indent();
strm << "<enum id=\"" << GetID() << "\" ";
// This is the size of the underlying enum type if this were a C type.
// In other words, the size of the register in bytes.
strm.Printf("size=\"%d\"", size);

const Enumerators &enumerators = GetEnumerators();
if (enumerators.empty()) {
strm << "/>\n";
return;
}

strm << ">\n";
strm.IndentMore();
for (const auto &enumerator : enumerators) {
strm.Indent();
enumerator.ToXML(strm);
strm.PutChar('\n');
}
strm.IndentLess();
strm.Indent("</enum>\n");
}

void FieldEnum::Enumerator::ToXML(Stream &strm) const {
std::string escaped_name;
llvm::raw_string_ostream escape_strm(escaped_name);
llvm::printHTMLEscaped(m_name, escape_strm);
strm.Printf("<evalue name=\"%s\" value=\"%" PRIu64 "\"/>",
escaped_name.c_str(), m_value);
}

void RegisterFlags::ToXML(Stream &strm) const {
// Example XML:
// <flags id="cpsr_flags" size="4">
Expand All @@ -214,7 +394,9 @@ void RegisterFlags::ToXML(Stream &strm) const {
}

void RegisterFlags::Field::ToXML(Stream &strm) const {
// Example XML:
// Example XML with an enum:
// <field name="correct" start="0" end="0" type="some_enum">
// Without:
// <field name="correct" start="0" end="0"/>
strm.Indent();
strm << "<field name=\"";
Expand All @@ -225,5 +407,17 @@ void RegisterFlags::Field::ToXML(Stream &strm) const {
strm << escaped_name << "\" ";

strm.Printf("start=\"%d\" end=\"%d\"", GetStart(), GetEnd());

if (const FieldEnum *enum_type = GetEnum())
strm << " type=\"" << enum_type->GetID() << "\"";

strm << "/>";
}

FieldEnum::FieldEnum(std::string id, const Enumerators &enumerators)
: m_id(id), m_enumerators(enumerators) {
for (const auto &enumerator : m_enumerators) {
UNUSED_IF_ASSERT_DISABLED(enumerator);
assert(enumerator.m_name.size() && "Enumerator name cannot be empty");
}
}
Loading
Loading