https://github.com/barsolo2000 created 
https://github.com/llvm/llvm-project/pull/195887

Add support for parsing `<union>` and `<vector>` type elements from GDB remote 
target description XML, matching GDB's behavior. 

Before:
```
ft0 = 0xffffffff3fc00000
```

After:
```
ft0 = 0xffffffff3fc00000
    = (float = 1.5, double = NaN)
```

Also supports dot-notation field access and SB API children:
```
(lldb) register read ft0.float
ft0.float = 1.5
```

>From f7c40d87095e271ee4b0be0ca5c08fb44645fb91 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <[email protected]>
Date: Tue, 5 May 2026 10:36:52 -0700
Subject: [PATCH] [LLDB] Add union and vector type support for GDB remote
 register descriptions

---
 lldb/include/lldb/Core/DumpRegisterInfo.h     |   4 +-
 .../include/lldb/Target/DynamicRegisterInfo.h |   1 +
 lldb/include/lldb/Target/RegisterFlags.h      |  56 ++++
 .../include/lldb/Target/RegisterTypeBuilder.h |   7 +
 lldb/include/lldb/Target/Target.h             |   5 +
 lldb/include/lldb/lldb-private-types.h        |   2 +
 .../source/Commands/CommandObjectRegister.cpp |  62 +++-
 lldb/source/Core/DumpRegisterInfo.cpp         |  16 +-
 lldb/source/Core/DumpRegisterValue.cpp        |  46 ++-
 .../Process/gdb-remote/ProcessGDBRemote.cpp   | 309 ++++++++++++++++--
 .../Process/gdb-remote/ProcessGDBRemote.h     |   4 +
 .../RegisterTypeBuilderClang.cpp              |  62 ++++
 .../RegisterTypeBuilderClang.h                |   5 +
 lldb/source/Target/DynamicRegisterInfo.cpp    |   3 +-
 lldb/source/Target/RegisterFlags.cpp          |  33 +-
 lldb/source/Target/Target.cpp                 |  11 +
 .../ValueObject/ValueObjectRegister.cpp       |   5 +-
 .../gdb_remote_client/TestXMLRegisterFlags.py | 238 +++++++++++++-
 lldb/unittests/Core/DumpRegisterInfoTest.cpp  |  62 +++-
 lldb/unittests/Target/RegisterFlagsTest.cpp   |  52 ++-
 20 files changed, 920 insertions(+), 63 deletions(-)

diff --git a/lldb/include/lldb/Core/DumpRegisterInfo.h 
b/lldb/include/lldb/Core/DumpRegisterInfo.h
index bceabcacd836e..d5fb1202910ae 100644
--- a/lldb/include/lldb/Core/DumpRegisterInfo.h
+++ b/lldb/include/lldb/Core/DumpRegisterInfo.h
@@ -19,6 +19,7 @@ class Stream;
 class RegisterContext;
 struct RegisterInfo;
 class RegisterFlags;
+class RegisterUnion;
 
 void DumpRegisterInfo(Stream &strm, RegisterContext &ctx,
                       const RegisterInfo &info, uint32_t terminal_width);
@@ -29,7 +30,8 @@ void DoDumpRegisterInfo(
     const std::vector<const char *> &invalidates,
     const std::vector<const char *> &read_from,
     const std::vector<std::pair<const char *, uint32_t>> &in_sets,
-    const RegisterFlags *flags_type, uint32_t terminal_width);
+    const RegisterFlags *flags_type, const RegisterUnion *union_type,
+    uint32_t terminal_width);
 
 } // namespace lldb_private
 
diff --git a/lldb/include/lldb/Target/DynamicRegisterInfo.h 
b/lldb/include/lldb/Target/DynamicRegisterInfo.h
index 43bba5038e537..95428ee4e04ce 100644
--- a/lldb/include/lldb/Target/DynamicRegisterInfo.h
+++ b/lldb/include/lldb/Target/DynamicRegisterInfo.h
@@ -42,6 +42,7 @@ class DynamicRegisterInfo {
     uint32_t value_reg_offset = 0;
     // Non-null if there is an XML provided type.
     const RegisterFlags *flags_type = nullptr;
+    const RegisterUnion *union_type = nullptr;
   };
 
   DynamicRegisterInfo() = default;
diff --git a/lldb/include/lldb/Target/RegisterFlags.h 
b/lldb/include/lldb/Target/RegisterFlags.h
index 1250fd0330958..697209ed19728 100644
--- a/lldb/include/lldb/Target/RegisterFlags.h
+++ b/lldb/include/lldb/Target/RegisterFlags.h
@@ -9,6 +9,8 @@
 #ifndef LLDB_TARGET_REGISTERFLAGS_H
 #define LLDB_TARGET_REGISTERFLAGS_H
 
+#include "lldb/lldb-enumerations.h"
+
 #include <stdint.h>
 #include <string>
 #include <vector>
@@ -193,6 +195,60 @@ class RegisterFlags {
   std::vector<Field> m_fields;
 };
 
+/// Represents a union type from a GDB remote target description XML. Each
+/// field is an alternative interpretation of the same register data (e.g.,
+/// viewing an FPU register as both ieee_single and ieee_double).
+class RegisterUnion {
+public:
+  class Field {
+  public:
+    /// Construct a union field. For scalar fields, \p vector_count should be 
0.
+    /// For vector fields, \p byte_size is the element size and \p vector_count
+    /// is the number of elements.
+    Field(std::string name, lldb::Encoding encoding, lldb::Format format,
+          uint32_t byte_size, uint32_t vector_count = 0);
+
+    const std::string &GetName() const { return m_name; }
+    lldb::Encoding GetEncoding() const { return m_encoding; }
+    lldb::Format GetFormat() const { return m_format; }
+    uint32_t GetByteSize() const { return m_byte_size; }
+    uint32_t GetVectorCount() const { return m_vector_count; }
+    bool IsVector() const { return m_vector_count > 0; }
+    /// For scalar fields, returns byte_size. For vector fields, returns
+    /// element byte_size * vector_count.
+    uint32_t GetTotalByteSize() const {
+      if (m_vector_count > 0) {
+        uint64_t total = static_cast<uint64_t>(m_byte_size) * m_vector_count;
+        assert(total <= UINT32_MAX && "Vector type size overflow");
+        return static_cast<uint32_t>(total);
+      }
+      return m_byte_size;
+    }
+    void DumpToLog(Log *log) const;
+
+  private:
+    std::string m_name;
+    lldb::Encoding m_encoding;
+    lldb::Format m_format;
+    uint32_t m_byte_size;
+    uint32_t m_vector_count;
+  };
+
+  /// \p fields must not be empty.
+  RegisterUnion(std::string id, std::vector<Field> fields);
+
+  const std::vector<Field> &GetFields() const { return m_fields; }
+  const std::string &GetID() const { return m_id; }
+  /// Returns the size of the largest field in bytes.
+  uint32_t GetSize() const { return m_size; }
+  void DumpToLog(Log *log) const;
+
+private:
+  const std::string m_id;
+  uint32_t m_size;
+  std::vector<Field> m_fields;
+};
+
 } // namespace lldb_private
 
 #endif // LLDB_TARGET_REGISTERFLAGS_H
diff --git a/lldb/include/lldb/Target/RegisterTypeBuilder.h 
b/lldb/include/lldb/Target/RegisterTypeBuilder.h
index 7239e1d4bd126..8c178bbe14b7c 100644
--- a/lldb/include/lldb/Target/RegisterTypeBuilder.h
+++ b/lldb/include/lldb/Target/RegisterTypeBuilder.h
@@ -14,6 +14,8 @@
 
 namespace lldb_private {
 
+class RegisterUnion;
+
 class RegisterTypeBuilder : public PluginInterface {
 public:
   ~RegisterTypeBuilder() override = default;
@@ -22,6 +24,11 @@ class RegisterTypeBuilder : public PluginInterface {
                                        const lldb_private::RegisterFlags 
&flags,
                                        uint32_t byte_size) = 0;
 
+  virtual CompilerType
+  GetRegisterUnionType(const std::string &name,
+                       const lldb_private::RegisterUnion &union_type,
+                       uint32_t byte_size) = 0;
+
 protected:
   RegisterTypeBuilder() = default;
 
diff --git a/lldb/include/lldb/Target/Target.h 
b/lldb/include/lldb/Target/Target.h
index 87b5c4f9591f1..722a89dbb46cb 100644
--- a/lldb/include/lldb/Target/Target.h
+++ b/lldb/include/lldb/Target/Target.h
@@ -1487,6 +1487,11 @@ class Target : public 
std::enable_shared_from_this<Target>,
                                const lldb_private::RegisterFlags &flags,
                                uint32_t byte_size);
 
+  CompilerType
+  GetRegisterUnionType(const std::string &name,
+                       const lldb_private::RegisterUnion &union_type,
+                       uint32_t byte_size);
+
   /// Sends a breakpoint notification event.
   void NotifyBreakpointChanged(Breakpoint &bp,
                                lldb::BreakpointEventType event_kind);
diff --git a/lldb/include/lldb/lldb-private-types.h 
b/lldb/include/lldb/lldb-private-types.h
index a60034314b77e..e730969051867 100644
--- a/lldb/include/lldb/lldb-private-types.h
+++ b/lldb/include/lldb/lldb-private-types.h
@@ -24,6 +24,7 @@ namespace lldb_private {
 class Platform;
 class ExecutionContext;
 class RegisterFlags;
+class RegisterUnion;
 
 typedef llvm::SmallString<256> PathSmallString;
 
@@ -68,6 +69,7 @@ struct RegisterInfo {
   /// this is mutable. The data pointed to is still const, so you must swap a
   /// whole set of flags for another.
   mutable const RegisterFlags *flags_type;
+  mutable const RegisterUnion *union_type;
 
   llvm::ArrayRef<uint8_t> data(const uint8_t *context_base) const {
     return llvm::ArrayRef<uint8_t>(context_base + byte_offset, byte_size);
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp 
b/lldb/source/Commands/CommandObjectRegister.cpp
index 29d1cd6dc13e4..c0d67b6ff4a36 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -22,11 +22,15 @@
 #include "lldb/Target/ExecutionContext.h"
 #include "lldb/Target/Process.h"
 #include "lldb/Target/RegisterContext.h"
+#include "lldb/Target/RegisterFlags.h"
 #include "lldb/Target/SectionLoadList.h"
+#include "lldb/Target/Target.h"
 #include "lldb/Target/Thread.h"
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/DataExtractor.h"
+#include "lldb/Utility/Endian.h"
 #include "lldb/Utility/RegisterValue.h"
+#include "lldb/ValueObject/ValueObjectConstResult.h"
 #include "llvm/Support/Errno.h"
 
 using namespace lldb;
@@ -204,18 +208,64 @@ class CommandObjectRegisterRead : public 
CommandObjectParsed {
           auto arg_str = entry.ref();
           arg_str.consume_front("$");
 
-          if (const RegisterInfo *reg_info =
-                  reg_ctx->GetRegisterInfoByName(arg_str)) {
-            // If they have asked for a specific format don't obscure that by
-            // printing flags afterwards.
+          // Try the full name first. If not found and the name contains a
+          // dot, try splitting into "register.field" for union member access.
+          llvm::StringRef reg_name_part = arg_str;
+          llvm::StringRef field_name;
+          const RegisterInfo *reg_info =
+              reg_ctx->GetRegisterInfoByName(arg_str);
+          if (!reg_info) {
+            if (auto dot_pos = arg_str.find('.');
+                dot_pos != llvm::StringRef::npos) {
+              reg_name_part = arg_str.substr(0, dot_pos);
+              field_name = arg_str.substr(dot_pos + 1);
+              reg_info = reg_ctx->GetRegisterInfoByName(reg_name_part);
+            }
+          }
+
+          if (!reg_info) {
+            result.AppendErrorWithFormat("Invalid register name '%s'",
+                                         arg_str.str().c_str());
+          } else if (field_name.empty()) {
             bool print_flags =
                 !m_format_options.GetFormatValue().OptionWasSet();
             if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *reg_info,
                               print_flags))
               strm.Printf("%-12s = error: unavailable\n", reg_info->name);
+          } else if (!reg_info->union_type) {
+            result.AppendErrorWithFormat(
+                "Register '%s' does not have a union type",
+                reg_name_part.str().c_str());
+          } else if (reg_info->byte_size != 4 && reg_info->byte_size != 8) {
+            result.AppendErrorWithFormat(
+                "Union field access not supported for %u-byte registers",
+                reg_info->byte_size);
           } else {
-            result.AppendErrorWithFormat("Invalid register name '%s'",
-                                         arg_str.str().c_str());
+            RegisterValue reg_value;
+            if (!reg_ctx->ReadRegister(reg_info, reg_value)) {
+              strm.Printf("%-12s = error: unavailable\n", reg_info->name);
+            } else if (auto target_sp = m_exe_ctx.GetTargetSP()) {
+              CompilerType union_ct = target_sp->GetRegisterUnionType(
+                  reg_info->name, *reg_info->union_type, reg_info->byte_size);
+              uint64_t raw = reg_info->byte_size == 4 ? reg_value.GetAsUInt32()
+                                                      : 
reg_value.GetAsUInt64();
+              DataExtractor de{&raw, reg_info->byte_size,
+                               endian::InlHostByteOrder(), 8};
+              auto vobj_sp = ValueObjectConstResult::Create(
+                  m_exe_ctx.GetBestExecutionContextScope(), union_ct,
+                  ConstString(reg_info->name), de);
+              if (auto child = vobj_sp->GetChildMemberWithName(field_name)) {
+                const char *value_str = child->GetValueAsCString();
+                strm.Indent();
+                strm.Printf("%s.%s = %s\n", reg_info->name,
+                            field_name.str().c_str(),
+                            value_str ? value_str : "<unavailable>");
+              } else {
+                result.AppendErrorWithFormat("No field '%s' in register '%s'",
+                                             field_name.str().c_str(),
+                                             reg_info->name);
+              }
+            }
           }
         }
       }
diff --git a/lldb/source/Core/DumpRegisterInfo.cpp 
b/lldb/source/Core/DumpRegisterInfo.cpp
index eccc6784cd497..deaa647d089fa 100644
--- a/lldb/source/Core/DumpRegisterInfo.cpp
+++ b/lldb/source/Core/DumpRegisterInfo.cpp
@@ -63,7 +63,7 @@ void lldb_private::DumpRegisterInfo(Stream &strm, 
RegisterContext &ctx,
 
   DoDumpRegisterInfo(strm, info.name, info.alt_name, info.byte_size,
                      invalidates, read_from, in_sets, info.flags_type,
-                     terminal_width);
+                     info.union_type, terminal_width);
 }
 
 template <typename ElementType>
@@ -89,7 +89,7 @@ void lldb_private::DoDumpRegisterInfo(
     const std::vector<const char *> &invalidates,
     const std::vector<const char *> &read_from,
     const std::vector<SetInfo> &in_sets, const RegisterFlags *flags_type,
-    uint32_t terminal_width) {
+    const RegisterUnion *union_type, uint32_t terminal_width) {
   strm << "       Name: " << name;
   if (alt_name)
     strm << " (" << alt_name << ")";
@@ -118,4 +118,16 @@ void lldb_private::DoDumpRegisterInfo(
     if (enumerators.size())
       strm << "\n\n" << enumerators;
   }
+
+  if (union_type) {
+    strm << "\n\n  Union members:";
+    for (const auto &field : union_type->GetFields()) {
+      if (field.IsVector())
+        strm.Printf("\n    %s (%u x %u bytes)", field.GetName().c_str(),
+                    field.GetVectorCount(), field.GetByteSize());
+      else
+        strm.Printf("\n    %s (%u bytes)", field.GetName().c_str(),
+                    field.GetByteSize());
+    }
+  }
 }
diff --git a/lldb/source/Core/DumpRegisterValue.cpp 
b/lldb/source/Core/DumpRegisterValue.cpp
index aff4d2c621d7e..20f8b82464ead 100644
--- a/lldb/source/Core/DumpRegisterValue.cpp
+++ b/lldb/source/Core/DumpRegisterValue.cpp
@@ -22,18 +22,16 @@
 using namespace lldb;
 
 template <typename T>
-static void dump_type_value(lldb_private::CompilerType &fields_type, T value,
+static void dump_type_value(lldb_private::CompilerType &type, T value,
                             lldb_private::ExecutionContextScope *exe_scope,
                             const lldb_private::RegisterInfo &reg_info,
-                            lldb_private::Stream &strm) {
+                            lldb_private::Stream &strm, bool is_flags) {
   lldb::ByteOrder target_order = exe_scope->CalculateProcess()->GetByteOrder();
 
-  // For the bitfield types we generate, it is expected that the fields are
-  // in what is usually a big endian order. Most significant field first.
-  // This is also clang's internal ordering and the order we want to print
-  // them. On a big endian host this all matches up, for a little endian
-  // host we have to swap the order of the fields before display.
-  if (target_order == lldb::ByteOrder::eByteOrderLittle) {
+  // For bitfield (flags) types, fields are in MSB-first order which matches
+  // big endian. On little endian we reverse the field order before display.
+  // For union types, members are full types so no reordering is needed.
+  if (is_flags && target_order == lldb::ByteOrder::eByteOrderLittle) {
     value = reg_info.flags_type->ReverseFieldOrder(value);
   }
 
@@ -45,14 +43,16 @@ static void dump_type_value(lldb_private::CompilerType 
&fields_type, T value,
       &value, sizeof(T), lldb_private::endian::InlHostByteOrder(), 8};
 
   lldb::ValueObjectSP vobj_sp = lldb_private::ValueObjectConstResult::Create(
-      exe_scope, fields_type, lldb_private::ConstString(), data_extractor);
+      exe_scope, type, lldb_private::ConstString(), data_extractor);
   lldb_private::DumpValueObjectOptions dump_options;
   lldb_private::DumpValueObjectOptions::ChildPrintingDecider decider =
       [](lldb_private::ConstString varname) {
         // Unnamed bit-fields are padding that we don't want to show.
         return varname.GetLength();
       };
-  dump_options.SetChildPrintingDecider(decider).SetHideRootType(true);
+  dump_options.SetChildPrintingDecider(decider)
+      .SetHideRootType(true)
+      .SetShowSummary(false);
 
   if (llvm::Error error = vobj_sp->Dump(strm, dump_options))
     strm << "error: " << toString(std::move(error));
@@ -121,22 +121,34 @@ void lldb_private::DumpRegisterValue(const RegisterValue 
&reg_val, Stream &s,
                     0,                    // item_bit_offset
                     exe_scope);
 
-  if (!print_flags || !reg_info.flags_type || !exe_scope || !target_sp ||
+  if (!print_flags || !exe_scope || !target_sp ||
       (reg_info.byte_size != 4 && reg_info.byte_size != 8))
     return;
 
-  CompilerType fields_type = target_sp->GetRegisterType(
-      reg_info.name, *reg_info.flags_type, reg_info.byte_size);
+  CompilerType type_to_dump;
+  bool is_flags = false;
+
+  if (reg_info.flags_type) {
+    type_to_dump = target_sp->GetRegisterType(
+        reg_info.name, *reg_info.flags_type, reg_info.byte_size);
+    is_flags = true;
+  } else if (reg_info.union_type) {
+    type_to_dump = target_sp->GetRegisterUnionType(
+        reg_info.name, *reg_info.union_type, reg_info.byte_size);
+  }
+
+  if (!type_to_dump)
+    return;
 
   // Use a new stream so we can remove a trailing newline later.
   StreamString fields_stream;
 
   if (reg_info.byte_size == 4) {
-    dump_type_value(fields_type, reg_val.GetAsUInt32(), exe_scope, reg_info,
-                    fields_stream);
+    dump_type_value(type_to_dump, reg_val.GetAsUInt32(), exe_scope, reg_info,
+                    fields_stream, is_flags);
   } else {
-    dump_type_value(fields_type, reg_val.GetAsUInt64(), exe_scope, reg_info,
-                    fields_stream);
+    dump_type_value(type_to_dump, reg_val.GetAsUInt64(), exe_scope, reg_info,
+                    fields_stream, is_flags);
   }
 
   // Registers are indented like:
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp 
b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
index adf108919b36e..e372ef9493f6c 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.cpp
@@ -4919,11 +4919,246 @@ void ParseFlags(
       });
 }
 
+struct GDBTypeInfo {
+  lldb::Encoding encoding;
+  lldb::Format format;
+  uint32_t byte_size;
+};
+
+static std::optional<GDBTypeInfo>
+ResolveGDBTypeName(llvm::StringRef type_name) {
+  if (type_name == "ieee_half")
+    return GDBTypeInfo{eEncodingIEEE754, eFormatFloat, 2};
+  if (type_name == "ieee_single" || type_name == "float")
+    return GDBTypeInfo{eEncodingIEEE754, eFormatFloat, 4};
+  if (type_name == "ieee_double")
+    return GDBTypeInfo{eEncodingIEEE754, eFormatFloat, 8};
+  if (type_name == "int8")
+    return GDBTypeInfo{eEncodingSint, eFormatDecimal, 1};
+  if (type_name == "int16")
+    return GDBTypeInfo{eEncodingSint, eFormatDecimal, 2};
+  if (type_name == "int32" || type_name == "int")
+    return GDBTypeInfo{eEncodingSint, eFormatDecimal, 4};
+  if (type_name == "int64")
+    return GDBTypeInfo{eEncodingSint, eFormatDecimal, 8};
+  if (type_name == "uint8")
+    return GDBTypeInfo{eEncodingUint, eFormatHex, 1};
+  if (type_name == "uint16")
+    return GDBTypeInfo{eEncodingUint, eFormatHex, 2};
+  if (type_name == "uint32")
+    return GDBTypeInfo{eEncodingUint, eFormatHex, 4};
+  if (type_name == "uint64")
+    return GDBTypeInfo{eEncodingUint, eFormatHex, 8};
+  if (type_name == "uint128")
+    return GDBTypeInfo{eEncodingUint, eFormatHex, 16};
+  if (type_name == "data_ptr" || type_name == "code_ptr")
+    return GDBTypeInfo{eEncodingUint, eFormatAddressInfo, 0};
+  return std::nullopt;
+}
+
+struct GDBRegisteredType {
+  lldb::Encoding encoding;
+  lldb::Format format;
+  uint32_t element_byte_size;
+  uint32_t vector_count;
+  uint32_t GetTotalByteSize() const {
+    return vector_count > 0 ? element_byte_size * vector_count
+                            : element_byte_size;
+  }
+};
+
+static void ParseVectors(XMLNode feature_node,
+                         llvm::StringMap<GDBRegisteredType> &type_registry) {
+  Log *log(GetLog(GDBRLog::Process));
+
+  feature_node.ForEachChildElementWithName(
+      "vector", [&log, &type_registry](const XMLNode &vector_node) -> bool {
+        std::string id;
+        std::string element_type;
+        uint32_t count = 0;
+
+        vector_node.ForEachAttribute(
+            [&id, &element_type, &count](const llvm::StringRef &attr_name,
+                                         const llvm::StringRef &attr_value) {
+              if (attr_name == "id")
+                id = attr_value;
+              else if (attr_name == "type")
+                element_type = attr_value;
+              else if (attr_name == "count")
+                llvm::to_integer(attr_value, count);
+              return true;
+            });
+
+        if (id.empty() || element_type.empty() || count == 0) {
+          LLDB_LOG(log, "ProcessGDBRemote::ParseVectors Ignoring vector with "
+                        "missing id, type, or count");
+          return true;
+        }
+
+        auto reg_it = type_registry.find(element_type);
+        if (reg_it != type_registry.end()) {
+          auto &elem = reg_it->second;
+          type_registry.insert_or_assign(
+              id, GDBRegisteredType{elem.encoding, elem.format,
+                                    elem.element_byte_size, count});
+        } else {
+          auto resolved = ResolveGDBTypeName(element_type);
+          if (resolved) {
+            type_registry.insert_or_assign(
+                id, GDBRegisteredType{resolved->encoding, resolved->format,
+                                      resolved->byte_size, count});
+          } else {
+            LLDB_LOG(log,
+                     "ProcessGDBRemote::ParseVectors Could not resolve "
+                     "element type \"{0}\" for vector \"{1}\"",
+                     element_type, id);
+          }
+        }
+
+        return true;
+      });
+}
+
+static std::vector<RegisterUnion::Field>
+ParseUnionFields(const XMLNode &union_node,
+                 const llvm::StringMap<GDBRegisteredType> &type_registry) {
+  Log *log(GetLog(GDBRLog::Process));
+  std::vector<RegisterUnion::Field> fields;
+  bool has_unresolved = false;
+
+  union_node.ForEachChildElementWithName(
+      "field",
+      [&fields, &has_unresolved, &log,
+       &type_registry](const XMLNode &field_node) -> bool {
+        std::optional<llvm::StringRef> name;
+        std::optional<llvm::StringRef> type;
+
+        field_node.ForEachAttribute(
+            [&name, &type, &log](const llvm::StringRef &attr_name,
+                                 const llvm::StringRef &attr_value) {
+              if (attr_name == "name") {
+                if (attr_value.size())
+                  name = attr_value;
+                else
+                  LLDB_LOG(log, "ProcessGDBRemote::ParseUnionFields "
+                                "Ignoring empty name in field");
+              } else if (attr_name == "type") {
+                if (attr_value.size())
+                  type = attr_value;
+                else
+                  LLDB_LOG(log, "ProcessGDBRemote::ParseUnionFields "
+                                "Ignoring empty type in field");
+              } else {
+                LLDB_LOG(log,
+                         "ProcessGDBRemote::ParseUnionFields "
+                         "Ignoring unknown attribute \"{0}\" in field",
+                         attr_name.data());
+              }
+              return true;
+            });
+
+        if (name && type) {
+          auto reg_it = type_registry.find(*type);
+          if (reg_it != type_registry.end()) {
+            auto &reg_type = reg_it->second;
+            fields.push_back(RegisterUnion::Field(
+                name->str(), reg_type.encoding, reg_type.format,
+                reg_type.element_byte_size, reg_type.vector_count));
+          } else {
+            auto resolved = ResolveGDBTypeName(*type);
+            if (resolved && resolved->byte_size > 0) {
+              fields.push_back(
+                  RegisterUnion::Field(name->str(), resolved->encoding,
+                                       resolved->format, resolved->byte_size));
+            } else {
+              LLDB_LOG(log,
+                       "ProcessGDBRemote::ParseUnionFields Could not resolve "
+                       "type \"{0}\" for field \"{1}\", discarding union",
+                       type->data(), name->data());
+              has_unresolved = true;
+            }
+          }
+        } else {
+          if (!name)
+            LLDB_LOG(log, "ProcessGDBRemote::ParseUnionFields "
+                          "Field missing required \"name\" attribute");
+          if (!type)
+            LLDB_LOG(log, "ProcessGDBRemote::ParseUnionFields "
+                          "Field missing required \"type\" attribute");
+          has_unresolved = true;
+        }
+        return true;
+      });
+
+  if (has_unresolved) {
+    LLDB_LOG(log, "ProcessGDBRemote::ParseUnionFields Discarding union because 
"
+                  "one or more fields have unresolvable types");
+    fields.clear();
+  }
+
+  return fields;
+}
+
+static void ParseUnions(
+    XMLNode feature_node,
+    llvm::StringMap<std::unique_ptr<RegisterUnion>> &registers_union_types,
+    const llvm::StringMap<GDBRegisteredType> &type_registry) {
+  Log *log(GetLog(GDBRLog::Process));
+
+  feature_node.ForEachChildElementWithName(
+      "union",
+      [&log, &registers_union_types,
+       &type_registry](const XMLNode &union_node) -> bool {
+        std::string id;
+
+        union_node.ForEachAttribute([&id](const llvm::StringRef &attr_name,
+                                          const llvm::StringRef &attr_value) {
+          if (attr_name == "id")
+            id = attr_value;
+          return true;
+        });
+
+        if (id.empty()) {
+          LLDB_LOG(log, "ProcessGDBRemote::ParseUnions "
+                        "Ignoring union without \"id\" attribute");
+          return true;
+        }
+
+        if (registers_union_types.contains(id)) {
+          LLDB_LOG(log,
+                   "ProcessGDBRemote::ParseUnions "
+                   "Definition of union \"{0}\" shadows previous definition, "
+                   "using original",
+                   id);
+          return true;
+        }
+
+        std::vector<RegisterUnion::Field> fields =
+            ParseUnionFields(union_node, type_registry);
+        if (!fields.empty()) {
+          LLDB_LOG(log,
+                   "ProcessGDBRemote::ParseUnions Found union type \"{0}\" "
+                   "with {1} fields",
+                   id, fields.size());
+          registers_union_types.insert_or_assign(
+              id, std::make_unique<RegisterUnion>(id, std::move(fields)));
+        } else {
+          LLDB_LOG(log,
+                   "ProcessGDBRemote::ParseUnions Ignoring union \"{0}\" "
+                   "because it contains no resolvable fields",
+                   id);
+        }
+
+        return true;
+      });
+}
+
 bool ParseRegisters(
     XMLNode feature_node, GdbServerTargetInfo &target_info,
     std::vector<DynamicRegisterInfo::Register> &registers,
     llvm::StringMap<std::unique_ptr<RegisterFlags>> &registers_flags_types,
-    llvm::StringMap<std::unique_ptr<FieldEnum>> &registers_enum_types) {
+    llvm::StringMap<std::unique_ptr<FieldEnum>> &registers_enum_types,
+    llvm::StringMap<std::unique_ptr<RegisterUnion>> &registers_union_types) {
   if (!feature_node)
     return false;
 
@@ -4938,9 +5173,19 @@ bool ParseRegisters(
   for (const auto &flags : registers_flags_types)
     flags.second->DumpToLog(log);
 
+  // Type registry for resolving <vector> and <union> field types within this
+  // feature. Note: types defined in one xi:include file cannot currently
+  // reference types from another xi:include file. In practice, <vector> and
+  // <union> elements are always in the same <feature>.
+  llvm::StringMap<GDBRegisteredType> type_registry;
+  ParseVectors(feature_node, type_registry);
+  ParseUnions(feature_node, registers_union_types, type_registry);
+  for (const auto &union_type : registers_union_types)
+    union_type.second->DumpToLog(log);
+
   feature_node.ForEachChildElementWithName(
       "reg",
-      [&target_info, &registers, &registers_flags_types,
+      [&target_info, &registers, &registers_flags_types, 
&registers_union_types,
        log](const XMLNode &reg_node) -> bool {
         std::string gdb_group;
         std::string gdb_type;
@@ -5018,27 +5263,51 @@ bool ParseRegisters(
         });
 
         if (!gdb_type.empty()) {
-          // gdb_type could reference some flags type defined in XML.
-          llvm::StringMap<std::unique_ptr<RegisterFlags>>::iterator it =
-              registers_flags_types.find(gdb_type);
-          if (it != registers_flags_types.end()) {
-            auto flags_type = it->second.get();
-            if (reg_info.byte_size == flags_type->GetSize())
-              reg_info.flags_type = flags_type;
-            else
+          // Check union types first.
+          auto union_it = registers_union_types.find(gdb_type);
+          if (union_it != registers_union_types.end()) {
+            auto *union_type = union_it->second.get();
+            if (reg_info.byte_size >= union_type->GetSize()) {
+              reg_info.union_type = union_type;
+              if (!(encoding_set || format_set)) {
+                reg_info.encoding = eEncodingUint;
+                reg_info.format = eFormatHex;
+              }
+            } else {
               LLDB_LOG(
                   log,
-                  "ProcessGDBRemote::ParseRegisters Size of register flags {0} 
"
-                  "({1} bytes) for register {2} does not match the register "
-                  "size ({3} bytes). Ignoring this set of flags.",
-                  flags_type->GetID().c_str(), flags_type->GetSize(),
+                  "ProcessGDBRemote::ParseRegisters Size of register union "
+                  "{0} ({1} bytes) for register {2} does not fit in the "
+                  "register size ({3} bytes). Ignoring this union type.",
+                  union_type->GetID().c_str(), union_type->GetSize(),
                   reg_info.name, reg_info.byte_size);
+            }
+          }
+
+          // Then check flags types (only if not a union).
+          if (!reg_info.union_type) {
+            // gdb_type could reference some flags type defined in XML.
+            llvm::StringMap<std::unique_ptr<RegisterFlags>>::iterator it =
+                registers_flags_types.find(gdb_type);
+            if (it != registers_flags_types.end()) {
+              auto flags_type = it->second.get();
+              if (reg_info.byte_size == flags_type->GetSize())
+                reg_info.flags_type = flags_type;
+              else
+                LLDB_LOG(
+                    log,
+                    "ProcessGDBRemote::ParseRegisters Size of register flags "
+                    "{0} "
+                    "({1} bytes) for register {2} does not match the register "
+                    "size ({3} bytes). Ignoring this set of flags.",
+                    flags_type->GetID().c_str(), flags_type->GetSize(),
+                    reg_info.name, reg_info.byte_size);
+            }
           }
 
-          // There's a slim chance that the gdb_type name is both a flags type
-          // and a simple type. Just in case, look for that too (setting both
-          // does no harm).
-          if (!gdb_type.empty() && !(encoding_set || format_set)) {
+          // Then check simple built-in type strings (only if not a union).
+          if (!gdb_type.empty() && !reg_info.union_type &&
+              !(encoding_set || format_set)) {
             if (llvm::StringRef(gdb_type).starts_with("int")) {
               reg_info.format = eFormatHex;
               reg_info.encoding = eEncodingUint;
@@ -5202,7 +5471,8 @@ bool 
ProcessGDBRemote::GetGDBServerRegisterInfoXMLAndProcess(
     if (arch_to_use.IsValid()) {
       for (auto &feature_node : feature_nodes) {
         ParseRegisters(feature_node, target_info, registers,
-                       m_registers_flags_types, m_registers_enum_types);
+                       m_registers_flags_types, m_registers_enum_types,
+                       m_registers_union_types);
       }
 
       for (const auto &include : target_info.includes) {
@@ -5280,6 +5550,7 @@ llvm::Error 
ProcessGDBRemote::GetGDBServerRegisterInfo(ArchSpec &arch_to_use) {
   // include read.
   m_registers_flags_types.clear();
   m_registers_enum_types.clear();
+  m_registers_union_types.clear();
   std::vector<DynamicRegisterInfo::Register> registers;
   if (GetGDBServerRegisterInfoXMLAndProcess(arch_to_use, "target.xml",
                                             registers) &&
diff --git a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h 
b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
index 7c2877fa71d49..5f07b241b166e 100644
--- a/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
+++ b/lldb/source/Plugins/Process/gdb-remote/ProcessGDBRemote.h
@@ -530,6 +530,10 @@ class ProcessGDBRemote : public Process,
   // directly because the map may reallocate. Pointers to these are contained
   // within instances of RegisterFlags.
   llvm::StringMap<std::unique_ptr<FieldEnum>> m_registers_enum_types;
+
+  // Union types defined in target XML. Same lifetime and pointer-stability
+  // requirements as m_registers_flags_types.
+  llvm::StringMap<std::unique_ptr<RegisterUnion>> m_registers_union_types;
 };
 
 } // namespace process_gdb_remote
diff --git 
a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp 
b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index 80d5289178ed0..d1bea1e0f6896 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -11,6 +11,8 @@
 #include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
 #include "RegisterTypeBuilderClang.h"
 #include "lldb/Core/PluginManager.h"
+#include "lldb/DataFormatters/DataVisualization.h"
+#include "lldb/DataFormatters/TypeSummary.h"
 #include "lldb/Target/RegisterFlags.h"
 #include "lldb/lldb-enumerations.h"
 
@@ -119,3 +121,63 @@ CompilerType RegisterTypeBuilderClang::GetRegisterType(
 
   return fields_type;
 }
+
+CompilerType RegisterTypeBuilderClang::GetRegisterUnionType(
+    const std::string &name, const lldb_private::RegisterUnion &union_type,
+    uint32_t byte_size) {
+  lldb::TypeSystemClangSP type_system =
+      ScratchTypeSystemClang::GetForTarget(m_target);
+  assert(type_system && "ScratchTypeSystemClang must be available");
+
+  std::string register_type_name = "__lldb_register_union_" + name;
+  // See if we have made this type before and can reuse it.
+  CompilerType existing_type =
+      type_system->GetTypeForIdentifier<clang::CXXRecordDecl>(
+          type_system->getASTContext(), register_type_name);
+
+  if (existing_type)
+    return existing_type;
+
+  CompilerType union_ct = type_system->CreateRecordType(
+      nullptr, OptionalClangModuleID(), register_type_name,
+      llvm::to_underlying(clang::TagTypeKind::Union), lldb::eLanguageTypeC);
+  type_system->StartTagDeclarationDefinition(union_ct);
+
+  for (const RegisterUnion::Field &field : union_type.GetFields()) {
+    CompilerType field_type;
+
+    if (field.IsVector()) {
+      // Create element type from encoding and element byte size.
+      CompilerType element_type =
+          type_system->GetBuiltinTypeForEncodingAndBitSize(
+              field.GetEncoding(), field.GetByteSize() * 8);
+      // Create a constant-size array type for the vector.
+      field_type = type_system->CreateArrayType(
+          element_type, field.GetVectorCount(), /*is_vector=*/false);
+    } else {
+      // Scalar field: use the full byte size.
+      field_type = type_system->GetBuiltinTypeForEncodingAndBitSize(
+          field.GetEncoding(), field.GetByteSize() * 8);
+    }
+
+    // 0 for bitfield size means a full-type member (not a bitfield).
+    type_system->AddFieldToRecordType(union_ct, field.GetName(), field_type,
+                                      /*bitfield_bit_size=*/0);
+  }
+
+  type_system->CompleteTagDeclarationDefinition(union_ct);
+
+  // Register an inline-children summary so GetSummary() returns
+  // "(field1 = val1, field2 = val2)" for union-typed registers.
+  TypeSummaryImpl::Flags summary_flags;
+  summary_flags.SetShowMembersOneLiner(true);
+  auto summary_sp = std::make_shared<StringSummaryFormat>(summary_flags, "");
+  lldb::TypeCategoryImplSP category_sp;
+  DataVisualization::Categories::GetCategory(ConstString("default"),
+                                             category_sp);
+  if (category_sp)
+    category_sp->AddTypeSummary(register_type_name, lldb::eFormatterMatchExact,
+                                summary_sp);
+
+  return union_ct;
+}
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h 
b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
index 611e2e60436ec..2285b493e0ba0 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
@@ -32,6 +32,11 @@ class RegisterTypeBuilderClang : public RegisterTypeBuilder {
                                const lldb_private::RegisterFlags &flags,
                                uint32_t byte_size) override;
 
+  CompilerType
+  GetRegisterUnionType(const std::string &name,
+                       const lldb_private::RegisterUnion &union_type,
+                       uint32_t byte_size) override;
+
 private:
   Target &m_target;
 };
diff --git a/lldb/source/Target/DynamicRegisterInfo.cpp 
b/lldb/source/Target/DynamicRegisterInfo.cpp
index c0116921e33fc..36df15e133286 100644
--- a/lldb/source/Target/DynamicRegisterInfo.cpp
+++ b/lldb/source/Target/DynamicRegisterInfo.cpp
@@ -424,7 +424,8 @@ size_t DynamicRegisterInfo::SetRegisterInfo(
         // value_regs and invalidate_regs are filled by Finalize()
         nullptr,
         nullptr,
-        reg.flags_type};
+        reg.flags_type,
+        reg.union_type};
 
     m_regs.push_back(reg_info);
 
diff --git a/lldb/source/Target/RegisterFlags.cpp 
b/lldb/source/Target/RegisterFlags.cpp
index 976e03870ad9e..ea1125a49ed90 100644
--- a/lldb/source/Target/RegisterFlags.cpp
+++ b/lldb/source/Target/RegisterFlags.cpp
@@ -426,4 +426,35 @@ FieldEnum::FieldEnum(std::string id, const Enumerators 
&enumerators)
     UNUSED_IF_ASSERT_DISABLED(enumerator);
     assert(enumerator.m_name.size() && "Enumerator name cannot be empty");
   }
-}
\ No newline at end of file
+}
+
+RegisterUnion::Field::Field(std::string name, lldb::Encoding encoding,
+                            lldb::Format format, uint32_t byte_size,
+                            uint32_t vector_count)
+    : m_name(std::move(name)), m_encoding(encoding), m_format(format),
+      m_byte_size(byte_size), m_vector_count(vector_count) {
+  assert(m_name.size() && "Union field name cannot be empty");
+  assert(m_byte_size > 0 && "Union field byte size cannot be zero");
+}
+
+void RegisterUnion::Field::DumpToLog(Log *log) const {
+  if (m_vector_count > 0)
+    LLDB_LOG(log, "  Name: \"{0}\" Vector: {1} x {2} bytes", m_name.c_str(),
+             m_vector_count, m_byte_size);
+  else
+    LLDB_LOG(log, "  Name: \"{0}\" Size: {1} bytes", m_name.c_str(),
+             m_byte_size);
+}
+
+RegisterUnion::RegisterUnion(std::string id, std::vector<Field> fields)
+    : m_id(std::move(id)), m_size(0), m_fields(std::move(fields)) {
+  assert(!m_fields.empty() && "Union must have at least one field");
+  for (const Field &field : m_fields)
+    m_size = std::max(m_size, field.GetTotalByteSize());
+}
+
+void RegisterUnion::DumpToLog(Log *log) const {
+  LLDB_LOG(log, "Union ID: \"{0}\" Fields: {1}", m_id.c_str(), 
m_fields.size());
+  for (const Field &field : m_fields)
+    field.DumpToLog(log);
+}
diff --git a/lldb/source/Target/Target.cpp b/lldb/source/Target/Target.cpp
index e2bae8fae1a26..c8c1cdfe9bfe9 100644
--- a/lldb/source/Target/Target.cpp
+++ b/lldb/source/Target/Target.cpp
@@ -2640,6 +2640,17 @@ CompilerType Target::GetRegisterType(const std::string 
&name,
   return m_register_type_builder_sp->GetRegisterType(name, flags, byte_size);
 }
 
+CompilerType
+Target::GetRegisterUnionType(const std::string &name,
+                             const lldb_private::RegisterUnion &union_type,
+                             uint32_t byte_size) {
+  if (!m_register_type_builder_sp)
+    m_register_type_builder_sp = PluginManager::GetRegisterTypeBuilder(*this);
+  assert(m_register_type_builder_sp);
+  return m_register_type_builder_sp->GetRegisterUnionType(name, union_type,
+                                                          byte_size);
+}
+
 std::vector<lldb::TypeSystemSP>
 Target::GetScratchTypeSystems(bool create_on_demand) {
   if (!m_valid)
diff --git a/lldb/source/ValueObject/ValueObjectRegister.cpp 
b/lldb/source/ValueObject/ValueObjectRegister.cpp
index 0d6e54b39ac1d..dbedc13e3e5b7 100644
--- a/lldb/source/ValueObject/ValueObjectRegister.cpp
+++ b/lldb/source/ValueObject/ValueObjectRegister.cpp
@@ -196,7 +196,10 @@ CompilerType ValueObjectRegister::GetCompilerTypeImpl() {
   if (!m_compiler_type.IsValid()) {
     ExecutionContext exe_ctx(GetExecutionContextRef());
     if (auto *target = exe_ctx.GetTargetPtr()) {
-      if (auto *exe_module = target->GetExecutableModulePointer()) {
+      if (m_reg_info.union_type) {
+        m_compiler_type = target->GetRegisterUnionType(
+            m_reg_info.name, *m_reg_info.union_type, m_reg_info.byte_size);
+      } else if (auto *exe_module = target->GetExecutableModulePointer()) {
         auto type_system_or_err =
             exe_module->GetTypeSystemForLanguage(eLanguageTypeC);
         if (auto err = type_system_or_err.takeError()) {
diff --git 
a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterFlags.py 
b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterFlags.py
index 1d0fd00ede3f0..5a4754bb1f8c2 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterFlags.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterFlags.py
@@ -1,4 +1,4 @@
-""" Check that register fields found in target XML are properly processed.
+"""Check that register fields found in target XML are properly processed.
 
 These tests make XML out of string substitution. This can lead to some strange
 failures. Check that the final XML is valid and each child is indented more 
than
@@ -1056,3 +1056,239 @@ def test_fields_same_name_different_enum(self):
         )
 
         self.expect("register read x0", patterns=[r"\(foo = foo_1, foo = 
foo_0\)$"])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_basic(self):
+        """Union with ieee_single and ieee_double fields."""
+        self.setup_register_test(
+            """\
+          <union id="fpu_type">
+            <field name="float" type="ieee_single"/>
+            <field name="double" type="ieee_double"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="fpu_type"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect(
+            "register read x0",
+            substrs=["(float = ", ", double = "],
+        )
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_no_id(self):
+        """Union without id attribute is ignored."""
+        self.setup_register_test(
+            """\
+          <union>
+            <field name="float" type="ieee_single"/>
+            <field name="double" type="ieee_double"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect("register read x0", matching=False, substrs=["(float = "])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_no_fields(self):
+        """Union with no field children is ignored."""
+        self.setup_register_test(
+            """\
+          <union id="empty_union">
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="empty_union"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect("register read x0", matching=False, substrs=["(float = "])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_unknown_field_type(self):
+        """Union with any unresolvable field type discards the entire union."""
+        self.setup_register_test(
+            """\
+          <union id="bad_union">
+            <field name="float" type="ieee_single"/>
+            <field name="custom" type="some_undefined_type"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="bad_union"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect("register read x0", matching=False, substrs=["(float = "])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_field_missing_name(self):
+        """Union field without name attribute causes union to be discarded."""
+        self.setup_register_test(
+            """\
+          <union id="bad_union2">
+            <field type="ieee_single"/>
+            <field name="double" type="ieee_double"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="bad_union2"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect("register read x0", matching=False, substrs=["(double = "])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_field_missing_type(self):
+        """Union field without type attribute causes union to be discarded."""
+        self.setup_register_test(
+            """\
+          <union id="bad_union3">
+            <field name="float"/>
+            <field name="double" type="ieee_double"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="bad_union3"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect("register read x0", matching=False, substrs=["(double = "])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_duplicate_id(self):
+        """Second union with same ID is ignored, first definition wins."""
+        self.setup_register_test(
+            """\
+          <union id="fpu_type">
+            <field name="float" type="ieee_single"/>
+            <field name="double" type="ieee_double"/>
+          </union>
+          <union id="fpu_type">
+            <field name="uint" type="uint64"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="fpu_type"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect("register read x0", substrs=["(float = ", ", double = "])
+        self.expect("register read x0", matching=False, substrs=["uint"])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_register_info(self):
+        """register info shows union field descriptions."""
+        self.setup_register_test(
+            """\
+          <union id="fpu_type">
+            <field name="float" type="ieee_single"/>
+            <field name="double" type="ieee_double"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="fpu_type"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect(
+            "register info x0",
+            substrs=["Union members:", "float (4 bytes)", "double (8 bytes)"],
+        )
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_format_disables_display(self):
+        """Explicit format flag suppresses union annotation."""
+        self.setup_register_test(
+            """\
+          <union id="fpu_type">
+            <field name="float" type="ieee_single"/>
+            <field name="double" type="ieee_double"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="fpu_type"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect("register read -f hex x0", matching=False, 
substrs=["(float = "])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_and_flags_separate_registers(self):
+        """One register uses flags, another uses union, both render 
correctly."""
+        self.setup_register_test(
+            """\
+          <flags id="cpsr_flags" size="4">
+            <field name="SP" start="0" end="0"/>
+          </flags>
+          <union id="fpu_type">
+            <field name="float" type="ieee_single"/>
+            <field name="double" type="ieee_double"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64" type="fpu_type"/>
+          <reg name="cpsr" regnum="33" bitsize="32" type="cpsr_flags"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        self.expect("register read x0", substrs=["(float = ", ", double = "])
+        self.expect("register read cpsr", substrs=["(SP = "])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_with_vector_fields(self):
+        """Union with vector fields resolved from <vector> elements."""
+        self.setup_register_test(
+            """\
+          <vector id="v4f" type="ieee_single" count="4"/>
+          <vector id="v2d" type="ieee_double" count="2"/>
+          <union id="vec128">
+            <field name="v4_float" type="v4f"/>
+            <field name="v2_double" type="v2d"/>
+            <field name="uint128" type="uint128"/>
+          </union>
+          <reg name="x0" regnum="0" bitsize="64"/>
+          <reg name="cpsr" regnum="33" bitsize="32"/>
+          <reg name="pc" bitsize="64"/>"""
+        )
+
+        # The union should be registered even though we don't have a 128-bit
+        # register to test display with. Just verify no crash on parsing.
+        self.expect("register read x0", substrs=["x0 = "])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_xi_include(self):
+        """Union defined in xi:included file works."""
+        self.setup_multidoc_test(
+            {
+                "target.xml": dedent(
+                    """\
+            <?xml version="1.0"?>
+              <target version="1.0">
+                <architecture>aarch64</architecture>
+                <xi:include href="fpu.xml"/>
+            </target>"""
+                ),
+                "fpu.xml": dedent(
+                    """\
+            <?xml version="1.0"?>
+              <feature name="org.gnu.gdb.aarch64.fpu">
+                <union id="fpu_type">
+                  <field name="float" type="ieee_single"/>
+                  <field name="double" type="ieee_double"/>
+                </union>
+                <reg name="x0" regnum="0" bitsize="64" type="fpu_type"/>
+                <reg name="cpsr" regnum="33" bitsize="32"/>
+                <reg name="pc" bitsize="64"/>
+            </feature>"""
+                ),
+            }
+        )
+
+        self.expect("register read x0", substrs=["(float = ", ", double = "])
diff --git a/lldb/unittests/Core/DumpRegisterInfoTest.cpp 
b/lldb/unittests/Core/DumpRegisterInfoTest.cpp
index 593170c2822ab..35f577a34f70d 100644
--- a/lldb/unittests/Core/DumpRegisterInfoTest.cpp
+++ b/lldb/unittests/Core/DumpRegisterInfoTest.cpp
@@ -15,28 +15,29 @@ using namespace lldb_private;
 
 TEST(DoDumpRegisterInfoTest, MinimumInfo) {
   StreamString strm;
-  DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {}, nullptr, 0);
+  DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {}, nullptr, nullptr, 0);
   ASSERT_EQ(strm.GetString(), "       Name: foo\n"
                               "       Size: 4 bytes (32 bits)");
 }
 
 TEST(DoDumpRegisterInfoTest, AltName) {
   StreamString strm;
-  DoDumpRegisterInfo(strm, "foo", "bar", 4, {}, {}, {}, nullptr, 0);
+  DoDumpRegisterInfo(strm, "foo", "bar", 4, {}, {}, {}, nullptr, nullptr, 0);
   ASSERT_EQ(strm.GetString(), "       Name: foo (bar)\n"
                               "       Size: 4 bytes (32 bits)");
 }
 
 TEST(DoDumpRegisterInfoTest, Invalidates) {
   StreamString strm;
-  DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2"}, {}, {}, nullptr, 0);
+  DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2"}, {}, {}, nullptr,
+                     nullptr, 0);
   ASSERT_EQ(strm.GetString(), "       Name: foo\n"
                               "       Size: 4 bytes (32 bits)\n"
                               "Invalidates: foo2");
 
   strm.Clear();
   DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2", "foo3", "foo4"}, {}, {},
-                     nullptr, 0);
+                     nullptr, nullptr, 0);
   ASSERT_EQ(strm.GetString(), "       Name: foo\n"
                               "       Size: 4 bytes (32 bits)\n"
                               "Invalidates: foo2, foo3, foo4");
@@ -44,14 +45,15 @@ TEST(DoDumpRegisterInfoTest, Invalidates) {
 
 TEST(DoDumpRegisterInfoTest, ReadFrom) {
   StreamString strm;
-  DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1"}, {}, nullptr, 0);
+  DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1"}, {}, nullptr,
+                     nullptr, 0);
   ASSERT_EQ(strm.GetString(), "       Name: foo\n"
                               "       Size: 4 bytes (32 bits)\n"
                               "  Read from: foo1");
 
   strm.Clear();
   DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {"foo1", "foo2", "foo3"}, {},
-                     nullptr, 0);
+                     nullptr, nullptr, 0);
   ASSERT_EQ(strm.GetString(), "       Name: foo\n"
                               "       Size: 4 bytes (32 bits)\n"
                               "  Read from: foo1, foo2, foo3");
@@ -60,14 +62,15 @@ TEST(DoDumpRegisterInfoTest, ReadFrom) {
 TEST(DoDumpRegisterInfoTest, InSets) {
   StreamString strm;
   DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {{"set1", 101}}, nullptr,
-                     0);
+                     nullptr, 0);
   ASSERT_EQ(strm.GetString(), "       Name: foo\n"
                               "       Size: 4 bytes (32 bits)\n"
                               "    In sets: set1 (index 101)");
 
   strm.Clear();
   DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {},
-                     {{"set1", 0}, {"set2", 1}, {"set3", 2}}, nullptr, 0);
+                     {{"set1", 0}, {"set2", 1}, {"set3", 2}}, nullptr, nullptr,
+                     0);
   ASSERT_EQ(strm.GetString(),
             "       Name: foo\n"
             "       Size: 4 bytes (32 bits)\n"
@@ -77,7 +80,8 @@ TEST(DoDumpRegisterInfoTest, InSets) {
 TEST(DoDumpRegisterInfoTest, MaxInfo) {
   StreamString strm;
   DoDumpRegisterInfo(strm, "foo", nullptr, 4, {"foo2", "foo3"},
-                     {"foo3", "foo4"}, {{"set1", 1}, {"set2", 2}}, nullptr, 0);
+                     {"foo3", "foo4"}, {{"set1", 1}, {"set2", 2}}, nullptr,
+                     nullptr, 0);
   ASSERT_EQ(strm.GetString(), "       Name: foo\n"
                               "       Size: 4 bytes (32 bits)\n"
                               "Invalidates: foo2, foo3\n"
@@ -94,7 +98,7 @@ TEST(DoDumpRegisterInfoTest, FieldsTable) {
       {RegisterFlags::Field("A", 24, 31), RegisterFlags::Field("B", 16, 23),
        RegisterFlags::Field("C", 8, 15), RegisterFlags::Field("D", 0, 7)});
 
-  DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {}, &flags, 100);
+  DoDumpRegisterInfo(strm, "foo", nullptr, 4, {}, {}, {}, &flags, nullptr, 
100);
   ASSERT_EQ(strm.GetString(), "       Name: foo\n"
                               "       Size: 4 bytes (32 bits)\n"
                               "\n"
@@ -115,7 +119,7 @@ TEST(DoDumpRegisterInfoTest, Enumerators) {
                        RegisterFlags::Field("B", 16, 23),
                        RegisterFlags::Field("C", 8, 15, &enum_two)});
 
-  DoDumpRegisterInfo(strm, "abc", nullptr, 4, {}, {}, {}, &flags, 100);
+  DoDumpRegisterInfo(strm, "abc", nullptr, 4, {}, {}, {}, &flags, nullptr, 
100);
   ASSERT_EQ(strm.GetString(),
             "       Name: abc\n"
             "       Size: 4 bytes (32 bits)\n"
@@ -128,3 +132,39 @@ TEST(DoDumpRegisterInfoTest, Enumerators) {
             "\n"
             "C: 1 = another_enumerator, 2 = another_enumerator_2");
 }
+
+TEST(DoDumpRegisterInfoTest, UnionFields) {
+  StreamString strm;
+  RegisterUnion union_type(
+      "fpu_type", {RegisterUnion::Field("float", lldb::eEncodingIEEE754,
+                                        lldb::eFormatFloat, 4),
+                   RegisterUnion::Field("double", lldb::eEncodingIEEE754,
+                                        lldb::eFormatFloat, 8)});
+
+  DoDumpRegisterInfo(strm, "ft0", nullptr, 8, {}, {}, {}, nullptr, &union_type,
+                     100);
+  ASSERT_EQ(strm.GetString(), "       Name: ft0\n"
+                              "       Size: 8 bytes (64 bits)\n"
+                              "\n"
+                              "  Union members:\n"
+                              "    float (4 bytes)\n"
+                              "    double (8 bytes)");
+}
+
+TEST(DoDumpRegisterInfoTest, UnionWithVectorFields) {
+  StreamString strm;
+  RegisterUnion union_type(
+      "vec_union", {RegisterUnion::Field("v4_float", lldb::eEncodingIEEE754,
+                                         lldb::eFormatFloat, 4, 4),
+                    RegisterUnion::Field("uint128", lldb::eEncodingUint,
+                                         lldb::eFormatHex, 16)});
+
+  DoDumpRegisterInfo(strm, "xmm0", nullptr, 16, {}, {}, {}, nullptr,
+                     &union_type, 100);
+  ASSERT_EQ(strm.GetString(), "       Name: xmm0\n"
+                              "       Size: 16 bytes (128 bits)\n"
+                              "\n"
+                              "  Union members:\n"
+                              "    v4_float (4 x 4 bytes)\n"
+                              "    uint128 (16 bytes)");
+}
diff --git a/lldb/unittests/Target/RegisterFlagsTest.cpp 
b/lldb/unittests/Target/RegisterFlagsTest.cpp
index ecffdd0fe44e6..54671af7a314b 100644
--- a/lldb/unittests/Target/RegisterFlagsTest.cpp
+++ b/lldb/unittests/Target/RegisterFlagsTest.cpp
@@ -130,11 +130,13 @@ TEST(RegisterFlagsTest, RegisterFlagsPadding) {
 TEST(RegisterFieldsTest, ReverseFieldOrder) {
   // Unchanged
   RegisterFlags rf("", 4, {make_field(0, 31)});
-  ASSERT_EQ(0x12345678ULL, (unsigned long 
long)rf.ReverseFieldOrder(0x12345678));
+  ASSERT_EQ(0x12345678ULL,
+            (unsigned long long)rf.ReverseFieldOrder(0x12345678));
 
   // Swap the two halves around.
   RegisterFlags rf2("", 4, {make_field(16, 31), make_field(0, 15)});
-  ASSERT_EQ(0x56781234ULL, (unsigned long 
long)rf2.ReverseFieldOrder(0x12345678));
+  ASSERT_EQ(0x56781234ULL,
+            (unsigned long long)rf2.ReverseFieldOrder(0x12345678));
 
   // Many small fields.
   RegisterFlags rf3(
@@ -480,4 +482,48 @@ TEST(RegisterFlagsTest, EnumsToXML) {
                               "<enum id=\"enum_b\" size=\"4\">\n"
                               "  <evalue name=\"one\" value=\"1\"/>\n"
                               "</enum>\n");
-}
\ No newline at end of file
+}
+
+TEST(RegisterUnionTest, ScalarField) {
+  RegisterUnion::Field f("float_view", eEncodingIEEE754, eFormatFloat, 4);
+  ASSERT_EQ(f.GetName(), "float_view");
+  ASSERT_EQ(f.GetEncoding(), eEncodingIEEE754);
+  ASSERT_EQ(f.GetFormat(), eFormatFloat);
+  ASSERT_EQ(f.GetByteSize(), 4u);
+  ASSERT_EQ(f.GetVectorCount(), 0u);
+  ASSERT_FALSE(f.IsVector());
+  ASSERT_EQ(f.GetTotalByteSize(), 4u);
+}
+
+TEST(RegisterUnionTest, VectorField) {
+  RegisterUnion::Field f("v4_float", eEncodingIEEE754, eFormatFloat, 4, 4);
+  ASSERT_EQ(f.GetName(), "v4_float");
+  ASSERT_TRUE(f.IsVector());
+  ASSERT_EQ(f.GetByteSize(), 4u);
+  ASSERT_EQ(f.GetVectorCount(), 4u);
+  ASSERT_EQ(f.GetTotalByteSize(), 16u);
+}
+
+TEST(RegisterUnionTest, Construction) {
+  RegisterUnion u(
+      "test_union",
+      {RegisterUnion::Field("f32", eEncodingIEEE754, eFormatFloat, 4),
+       RegisterUnion::Field("f64", eEncodingIEEE754, eFormatFloat, 8)});
+  ASSERT_EQ(u.GetID(), "test_union");
+  ASSERT_EQ(u.GetFields().size(), 2u);
+  ASSERT_EQ(u.GetFields()[0].GetName(), "f32");
+  ASSERT_EQ(u.GetFields()[1].GetName(), "f64");
+}
+
+TEST(RegisterUnionTest, MixedScalarAndVector) {
+  RegisterUnion u(
+      "mixed",
+      {RegisterUnion::Field("f", eEncodingIEEE754, eFormatFloat, 4),
+       RegisterUnion::Field("v4f", eEncodingIEEE754, eFormatFloat, 4, 4),
+       RegisterUnion::Field("i", eEncodingUint, eFormatHex, 8)});
+  ASSERT_EQ(u.GetFields().size(), 3u);
+  ASSERT_FALSE(u.GetFields()[0].IsVector());
+  ASSERT_TRUE(u.GetFields()[1].IsVector());
+  ASSERT_EQ(u.GetFields()[1].GetTotalByteSize(), 16u);
+  ASSERT_FALSE(u.GetFields()[2].IsVector());
+}

_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to