[Lldb-commits] [lldb] [LLDB] Expose and display GDB remote register union values (PR #224147)

Bar Soloveychik via lldb-commits lldb-commits at lists.llvm.org
Sun Oct 18 02:41:36 PDT 2026


https://github.com/barsolo2000 updated https://github.com/llvm/llvm-project/pull/224147

>From eaeb64f081f77701d809f90440784cb3bcce32b5 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 8 Sep 2026 08:43:31 -0700
Subject: [PATCH 01/11] [lldb][7/7] Expose and display GDB remote register
 union values

---
 .../source/Commands/CommandObjectRegister.cpp | 137 +++++++-
 lldb/source/Core/DumpRegisterValue.cpp        |   3 +-
 lldb/source/Core/Value.cpp                    |   7 +-
 .../RegisterTypeBuilder/CMakeLists.txt        |   1 +
 .../RegisterTypeBuilderClang.cpp              | 177 ++++++++--
 .../RegisterTypeBuilderClang.h                |  18 +-
 .../ValueObject/ValueObjectRegister.cpp       |  15 +-
 .../gdb_remote_client/TestXMLRegisterUnion.py | 327 ++++++++++++++++++
 .../Target/RegisterTypeBuilderClangTest.cpp   | 160 +++++++++
 9 files changed, 792 insertions(+), 53 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 192cef6a2a53b6..02717c82262777 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -10,6 +10,7 @@
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/DumpRegisterInfo.h"
 #include "lldb/Core/DumpRegisterValue.h"
+#include "lldb/DataFormatters/DumpValueObjectOptions.h"
 #include "lldb/Host/OptionParser.h"
 #include "lldb/Interpreter/CommandInterpreter.h"
 #include "lldb/Interpreter/CommandOptionArgumentTable.h"
@@ -23,11 +24,16 @@
 #include "lldb/Target/Process.h"
 #include "lldb/Target/RegisterContext.h"
 #include "lldb/Target/SectionLoadList.h"
+#include "lldb/Target/StackFrame.h"
 #include "lldb/Target/Thread.h"
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/DataExtractor.h"
+#include "lldb/Utility/RegisterType.h"
 #include "lldb/Utility/RegisterValue.h"
+#include "lldb/Utility/StreamString.h"
+#include "lldb/ValueObject/ValueObjectRegister.h"
 #include "llvm/Support/Errno.h"
+#include "llvm/Support/Error.h"
 
 using namespace lldb;
 using namespace lldb_private;
@@ -41,6 +47,68 @@ static size_t GetNameSize(const RegisterInfo *reg_info, bool use_primary_name) {
   return reg_name ? strlen(reg_name) : 0;
 }
 
+static std::pair<const RegisterInfo *, llvm::StringRef>
+FindRegisterWithExpressionPath(RegisterContext &reg_ctx, llvm::StringRef name) {
+  if (const RegisterInfo *reg_info = reg_ctx.GetRegisterInfoByName(name))
+    return {reg_info, {}};
+
+  size_t search_end = name.size();
+  while (search_end) {
+    size_t split = name.take_front(search_end).find_last_of(".[");
+    if (split == llvm::StringRef::npos)
+      break;
+    if (const RegisterInfo *reg_info =
+            reg_ctx.GetRegisterInfoByName(name.take_front(split)))
+      return {reg_info, name.drop_front(split)};
+    search_end = split;
+  }
+  return {nullptr, {}};
+}
+
+static ValueObjectSP
+GetRegisterValueForExpressionPath(ValueObjectSP value,
+                                  llvm::StringRef expression_path) {
+  while (!expression_path.empty()) {
+    if (expression_path.consume_front(".")) {
+      size_t separator = expression_path.find_first_of(".[");
+      llvm::StringRef member = separator == llvm::StringRef::npos
+                                   ? expression_path
+                                   : expression_path.take_front(separator);
+      if (member.empty())
+        return {};
+      value = value->GetChildMemberWithName(member);
+      expression_path = separator == llvm::StringRef::npos
+                            ? llvm::StringRef()
+                            : expression_path.drop_front(separator);
+    } else if (expression_path.consume_front("[")) {
+      size_t closing_bracket = expression_path.find(']');
+      if (closing_bracket == llvm::StringRef::npos)
+        return {};
+
+      uint32_t index = 0;
+      if (expression_path.take_front(closing_bracket).getAsInteger(10, index))
+        return {};
+
+      llvm::Expected<uint32_t> num_children = value->GetNumChildren();
+      if (!num_children) {
+        llvm::consumeError(num_children.takeError());
+        return {};
+      }
+      if (index >= *num_children)
+        return {};
+
+      value = value->GetChildAtIndex(index);
+      expression_path = expression_path.drop_front(closing_bracket + 1);
+    } else {
+      return {};
+    }
+
+    if (!value)
+      return {};
+  }
+  return value;
+}
+
 static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
                                          const RegisterSet &reg_set,
                                          bool use_primary_name,
@@ -78,8 +146,9 @@ static size_t ComputeLongestRegisterName(Args &command,
     llvm::StringRef arg_str = entry.ref();
     arg_str.consume_front("$");
 
-    if (const RegisterInfo *reg_info =
-            reg_ctx->GetRegisterInfoByName(arg_str)) {
+    const RegisterInfo *reg_info =
+        FindRegisterWithExpressionPath(*reg_ctx, arg_str).first;
+    if (reg_info) {
       name_right_align_at = std::max(name_right_align_at,
                                      GetNameSize(reg_info, use_primary_name));
     }
@@ -161,6 +230,61 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
     return true;
   }
 
+  bool DumpRegisterField(const ExecutionContext &exe_ctx, Stream &strm,
+                         const RegisterInfo &reg_info,
+                         llvm::StringRef expression_path,
+                         size_t reg_name_right_align_at,
+                         CommandReturnObject &result) {
+    if (!llvm::isa_and_present<RegisterTypeUnion, RegisterTypeVector>(
+            reg_info.register_type)) {
+      result.AppendErrorWithFormat(
+          "Register '%s' does not have a structured type", reg_info.name);
+      return false;
+    }
+
+    StackFrame *frame = exe_ctx.GetFramePtr();
+    if (!frame)
+      return false;
+    lldb::RegisterContextSP reg_ctx_sp = frame->GetRegisterContextSP();
+    ValueObjectSP register_value =
+        ValueObjectRegister::Create(frame, reg_ctx_sp, &reg_info);
+    ValueObjectSP child =
+        GetRegisterValueForExpressionPath(register_value, expression_path);
+    if (!child) {
+      llvm::StringRef error_path = expression_path;
+      error_path.consume_front(".");
+      result.AppendErrorWithFormat("No field path '%s' in register '%s'",
+                                   error_path.str().c_str(), reg_info.name);
+      return false;
+    }
+
+    DumpValueObjectOptions options(*child);
+    options.SetHideRootType(true)
+        .SetHideRootName(true)
+        .SetHideName(true)
+        .SetAllowOnelinerMode(true);
+    if (m_format_options.GetFormatValue().OptionWasSet())
+      options.SetFormat(m_format_options.GetFormatValue().GetCurrentValue());
+
+    StreamString value_stream;
+    if (llvm::Error error = child->Dump(value_stream, options)) {
+      result.AppendError(toString(std::move(error)));
+      return false;
+    }
+
+    llvm::StringRef value = value_stream.GetString();
+    value.consume_back("\n");
+    const char *register_name =
+        m_command_options.alternate_name && reg_info.alt_name
+            ? reg_info.alt_name
+            : reg_info.name;
+    strm.Indent();
+    strm.Printf("%*s%s = ", static_cast<int>(reg_name_right_align_at),
+                register_name, expression_path.str().c_str());
+    strm << value << '\n';
+    return true;
+  }
+
   bool DumpRegisterSet(const ExecutionContext &exe_ctx, Stream &strm,
                        RegisterContext *reg_ctx, size_t set_idx,
                        bool primitive_only = false) {
@@ -264,14 +388,19 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
           auto arg_str = entry.ref();
           arg_str.consume_front("$");
 
-          if (const RegisterInfo *reg_info =
-                  reg_ctx->GetRegisterInfoByName(arg_str)) {
+          auto [reg_info, expression_path] =
+              FindRegisterWithExpressionPath(*reg_ctx, arg_str);
+
+          if (reg_info && expression_path.empty()) {
             // If they have asked for a specific format don't obscure that by
             // printing a structured value afterwards.
             bool print_type = !m_format_options.GetFormatValue().OptionWasSet();
             if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *reg_info, print_type,
                               reg_name_right_align_at))
               strm.Printf("%-12s = error: unavailable\n", reg_info->name);
+          } else if (reg_info) {
+            DumpRegisterField(m_exe_ctx, strm, *reg_info, expression_path,
+                              reg_name_right_align_at, result);
           } else {
             result.AppendErrorWithFormat("Invalid register name '%s'",
                                          arg_str.str().c_str());
diff --git a/lldb/source/Core/DumpRegisterValue.cpp b/lldb/source/Core/DumpRegisterValue.cpp
index dff81f9bdbe900..84b1e03dd4576c 100644
--- a/lldb/source/Core/DumpRegisterValue.cpp
+++ b/lldb/source/Core/DumpRegisterValue.cpp
@@ -143,7 +143,8 @@ void lldb_private::DumpRegisterValue(const RegisterValue &reg_val, Stream &s,
   const RegisterTypeFlags *flags_type =
       llvm::dyn_cast_if_present<RegisterTypeFlags>(reg_info.register_type);
   if (!flags_type &&
-      !llvm::isa_and_present<RegisterTypeVector>(reg_info.register_type))
+      !llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
+          reg_info.register_type))
     return;
   if (flags_type && reg_info.byte_size != 4 && reg_info.byte_size != 8)
     return;
diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 0a7610e62ed4ab..11d313ccd9e9b3 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -26,6 +26,7 @@
 #include "lldb/Utility/FileSpec.h"
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
+#include "lldb/Utility/RegisterType.h"
 #include "lldb/Utility/State.h"
 #include "lldb/Utility/Stream.h"
 #include "lldb/lldb-defines.h"
@@ -493,10 +494,10 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
     if (exe_ctx) {
       if (Target *target = exe_ctx->GetTargetPtr()) {
         ByteOrder byte_order = target->GetArchitecture().GetByteOrder();
-        // ValueObjectRegister stores typed vectors in target byte order and
-        // all other register buffers in host byte order.
+        // ValueObjectRegister stores structured register types in target byte
+        // order and all other register buffers in host byte order.
         if (m_context_type == ContextType::RegisterInfo &&
-            !llvm::isa_and_present<RegisterTypeVector>(
+            !llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
                 GetRegisterInfo()->register_type))
           byte_order = endian::InlHostByteOrder();
         data.SetByteOrder(byte_order);
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt b/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
index 7411b24c86cbdb..c70e61ea0aa225 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
+++ b/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
@@ -7,5 +7,6 @@ add_lldb_library(lldbPluginRegisterTypeBuilderClang PLUGIN
     Support
   LINK_LIBS
     lldbCore
+    lldbDataFormatters
     lldbTarget
   )
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index 12cf2bccee5039..ef0b1748f1c011 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -10,10 +10,14 @@
 
 #include "RegisterTypeBuilderClang.h"
 #include "lldb/Core/PluginManager.h"
+#include "lldb/DataFormatters/DataVisualization.h"
+#include "lldb/DataFormatters/TypeSummary.h"
 #include "lldb/Utility/RegisterType.h"
 #include "lldb/lldb-enumerations.h"
 #include "llvm/ADT/bit.h"
 
+#include <algorithm>
+
 using namespace lldb_private;
 
 LLDB_PLUGIN_DEFINE(RegisterTypeBuilderClang)
@@ -151,36 +155,15 @@ RegisterTypeBuilderClang::BuildVectorType(const RegisterTypeVector *vector_type,
   if (auto type = GetExistingCompilerType(vector_type, expected_byte_size))
     return *type;
 
-  std::optional<uint64_t> element_size =
-      vector_type->GetElementType()->GetByteSize();
-  if (!element_size) {
-    if (expected_byte_size % vector_type->GetCount())
-      return {};
-    element_size = expected_byte_size / vector_type->GetCount();
-  }
-  if (*element_size > UINT32_MAX ||
-      expected_byte_size % vector_type->GetCount() ||
-      *element_size != expected_byte_size / vector_type->GetCount())
+  std::optional<uint32_t> element_size =
+      GetTargetByteSize(vector_type->GetElementType(), type_system);
+  if (!element_size || *element_size > UINT32_MAX / vector_type->GetCount() ||
+      *element_size * vector_type->GetCount() != expected_byte_size)
     return {};
 
-  CompilerType element_type;
   const RegisterType *element_register_type = vector_type->GetElementType();
-  switch (element_register_type->getKind()) {
-  case RegisterType::eRegisterTypeKindBuiltin:
-    element_type =
-        BuildBuiltinType(llvm::cast<RegisterTypeBuiltin>(element_register_type),
-                         *element_size, type_system);
-    break;
-  case RegisterType::eRegisterTypeKindVector:
-    element_type =
-        BuildVectorType(llvm::cast<RegisterTypeVector>(element_register_type),
-                        *element_size, type_system);
-    break;
-  case RegisterType::eRegisterTypeKindEnum:
-  case RegisterType::eRegisterTypeKindFlags:
-  case RegisterType::eRegisterTypeKindUnion:
-    return {};
-  }
+  CompilerType element_type =
+      BuildType(element_register_type, *element_size, type_system);
   if (!element_type.IsValid())
     return {};
 
@@ -216,6 +199,129 @@ RegisterTypeBuilderClang::BuildVectorType(const RegisterTypeVector *vector_type,
   return compiler_type;
 }
 
+CompilerType
+RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
+                                         uint32_t expected_byte_size,
+                                         lldb::TypeSystemClangSP type_system) {
+  if (!expected_byte_size)
+    return {};
+  if (auto type = GetExistingCompilerType(union_type, expected_byte_size))
+    return *type;
+
+  std::vector<std::pair<llvm::StringRef, CompilerType>> fields;
+  fields.reserve(union_type->GetFields().size());
+  for (const RegisterTypeUnion::Field &field : union_type->GetFields()) {
+    std::optional<uint32_t> field_size =
+        GetTargetByteSize(field.GetType(), type_system);
+    if (!field_size || *field_size > expected_byte_size)
+      return {};
+
+    CompilerType field_type =
+        BuildType(field.GetType(), *field_size, type_system);
+    if (!field_type.IsValid())
+      return {};
+    fields.emplace_back(field.GetName(), field_type);
+  }
+
+  std::string type_name = "__lldb_register_union_" +
+                          std::to_string(union_type->GetUID()) + "_" +
+                          std::to_string(expected_byte_size);
+  CompilerType compiler_type = type_system->CreateRecordType(
+      nullptr, OptionalClangModuleID(), type_name,
+      llvm::to_underlying(clang::TagTypeKind::Union), lldb::eLanguageTypeC);
+  type_system->StartTagDeclarationDefinition(compiler_type);
+  for (const auto &[field_name, field_type] : fields)
+    type_system->AddFieldToRecordType(compiler_type, field_name, field_type,
+                                      /*bitfield_bit_size=*/0);
+  type_system->CompleteTagDeclarationDefinition(compiler_type);
+  type_system->SetIsPacked(compiler_type);
+
+  if (llvm::expectedToOptional(compiler_type.GetByteSize(nullptr)) !=
+      expected_byte_size)
+    return {};
+
+  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(type_name, lldb::eFormatterMatchExact,
+                                summary_sp);
+
+  m_type_cache.try_emplace(
+      std::make_pair(union_type->GetUID(), expected_byte_size), compiler_type);
+  return compiler_type;
+}
+
+CompilerType
+RegisterTypeBuilderClang::BuildType(const RegisterType *register_type,
+                                    uint32_t expected_byte_size,
+                                    lldb::TypeSystemClangSP type_system) {
+  switch (register_type->getKind()) {
+  case RegisterType::eRegisterTypeKindBuiltin:
+    return BuildBuiltinType(llvm::cast<RegisterTypeBuiltin>(register_type),
+                            expected_byte_size, type_system);
+  case RegisterType::eRegisterTypeKindVector:
+    return BuildVectorType(llvm::cast<RegisterTypeVector>(register_type),
+                           expected_byte_size, type_system);
+  case RegisterType::eRegisterTypeKindUnion:
+    return BuildUnionType(llvm::cast<RegisterTypeUnion>(register_type),
+                          expected_byte_size, type_system);
+  case RegisterType::eRegisterTypeKindEnum:
+  case RegisterType::eRegisterTypeKindFlags:
+    return {};
+  }
+}
+
+std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
+    const RegisterType *register_type, lldb::TypeSystemClangSP type_system) {
+  if (std::optional<uint64_t> fixed_size = register_type->GetByteSize()) {
+    if (*fixed_size <= UINT32_MAX)
+      return static_cast<uint32_t>(*fixed_size);
+    return std::nullopt;
+  }
+
+  switch (register_type->getKind()) {
+  case RegisterType::eRegisterTypeKindBuiltin: {
+    const auto *builtin_type = llvm::cast<RegisterTypeBuiltin>(register_type);
+    if (builtin_type->GetID() != "data_ptr" &&
+        builtin_type->GetID() != "code_ptr")
+      return std::nullopt;
+    std::optional<uint64_t> pointer_size = llvm::expectedToOptional(
+        type_system->GetType(type_system->getASTContext().VoidPtrTy)
+            .GetByteSize(nullptr));
+    if (!pointer_size || *pointer_size > UINT32_MAX)
+      return std::nullopt;
+    return static_cast<uint32_t>(*pointer_size);
+  }
+  case RegisterType::eRegisterTypeKindVector: {
+    const auto *vector_type = llvm::cast<RegisterTypeVector>(register_type);
+    std::optional<uint32_t> element_size =
+        GetTargetByteSize(vector_type->GetElementType(), type_system);
+    if (!element_size || *element_size > UINT32_MAX / vector_type->GetCount())
+      return std::nullopt;
+    return *element_size * vector_type->GetCount();
+  }
+  case RegisterType::eRegisterTypeKindUnion: {
+    uint32_t byte_size = 0;
+    for (const RegisterTypeUnion::Field &field :
+         llvm::cast<RegisterTypeUnion>(register_type)->GetFields()) {
+      std::optional<uint32_t> field_size =
+          GetTargetByteSize(field.GetType(), type_system);
+      if (!field_size)
+        return std::nullopt;
+      byte_size = std::max(byte_size, *field_size);
+    }
+    return byte_size;
+  }
+  case RegisterType::eRegisterTypeKindEnum:
+  case RegisterType::eRegisterTypeKindFlags:
+    return std::nullopt;
+  }
+}
+
 CompilerType
 RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo &reg_info) {
   lldb::TypeSystemClangSP type_system =
@@ -236,9 +342,7 @@ RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo &reg_info) {
 
   switch (reg_info.register_type->getKind()) {
   case RegisterType::eRegisterTypeKindBuiltin:
-    return BuildBuiltinType(
-        llvm::cast<RegisterTypeBuiltin>(reg_info.register_type),
-        reg_info.byte_size, type_system);
+    return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
   case RegisterType::eRegisterTypeKindFlags:
     return BuildFlagsType(
         llvm::dyn_cast<RegisterTypeFlags>(reg_info.register_type),
@@ -248,11 +352,14 @@ RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo &reg_info) {
         llvm::dyn_cast<RegisterTypeEnum>(reg_info.register_type),
         reg_info.byte_size, type_system);
   case RegisterType::eRegisterTypeKindVector:
-    return BuildVectorType(
-        llvm::cast<RegisterTypeVector>(reg_info.register_type),
-        reg_info.byte_size, type_system);
-  case RegisterType::eRegisterTypeKindUnion:
-    return {};
+    return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
+  case RegisterType::eRegisterTypeKindUnion: {
+    std::optional<uint32_t> byte_size =
+        GetTargetByteSize(reg_info.register_type, type_system);
+    if (!byte_size || *byte_size > reg_info.byte_size)
+      return {};
+    return BuildType(reg_info.register_type, *byte_size, type_system);
+  }
   }
 }
 
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
index ab069b56422251..a8c005d77252f3 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
@@ -49,15 +49,27 @@ class RegisterTypeBuilderClang : public RegisterTypeBuilder {
                                uint32_t expected_byte_size,
                                lldb::TypeSystemClangSP type_system);
 
+  CompilerType BuildUnionType(const RegisterTypeUnion *union_type,
+                              uint32_t expected_byte_size,
+                              lldb::TypeSystemClangSP type_system);
+
+  CompilerType BuildType(const RegisterType *register_type,
+                         uint32_t expected_byte_size,
+                         lldb::TypeSystemClangSP type_system);
+
+  std::optional<uint32_t>
+  GetTargetByteSize(const RegisterType *register_type,
+                    lldb::TypeSystemClangSP type_system);
+
   Target &m_target;
 
   // A cache of previously created types. We do not cache by element ID because
   // IDs are not unique across xml <feature> elements and this class does not
   // know anything about features.
   //
-  // The key contains the process-wide UID of the type and the size of the
-  // register we made it for. Some types (enums for example) use the register
-  // size in their type and must be rebuilt for a different size.
+  // The key contains the process-wide UID of the type and the byte size used
+  // to build it. Some types (enums for example) use the register size, while a
+  // union can use its natural size inside a larger register or nested type.
   //
   // 8 is chosen because types are only made when needed, and most lldb commands
   // do not need them.
diff --git a/lldb/source/ValueObject/ValueObjectRegister.cpp b/lldb/source/ValueObject/ValueObjectRegister.cpp
index 23f4f3deb5c87a..71be7ab993681d 100644
--- a/lldb/source/ValueObject/ValueObjectRegister.cpp
+++ b/lldb/source/ValueObject/ValueObjectRegister.cpp
@@ -229,8 +229,8 @@ CompilerType ValueObjectRegister::GetCompilerTypeImpl() {
   if (!target)
     return {};
 
-  if (llvm::isa_and_present<RegisterTypeBuiltin, RegisterTypeVector>(
-          m_reg_info.register_type)) {
+  if (llvm::isa_and_present<RegisterTypeBuiltin, RegisterTypeVector,
+                            RegisterTypeUnion>(m_reg_info.register_type)) {
     m_compiler_type = target->GetRegisterType(m_reg_info);
     if (m_compiler_type.IsValid())
       return m_compiler_type;
@@ -284,12 +284,13 @@ bool ValueObjectRegister::UpdateValue() {
     RegisterValue m_old_reg_value(m_reg_value);
     if (m_reg_ctx_sp->ReadRegister(&m_reg_info, m_reg_value)) {
       Target *target = exe_ctx.GetTargetPtr();
-      const bool has_vector_type =
-          llvm::isa_and_present<RegisterTypeVector>(m_reg_info.register_type);
-      // Scalar registers remain in host byte order, while CompilerType vector
-      // children need target-order bytes to interpret their layout.
+      const bool has_structured_type =
+          llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
+              m_reg_info.register_type);
+      // Scalar registers remain in host byte order, while CompilerType
+      // children need target-order bytes to interpret structured layouts.
       const bool got_data =
-          has_vector_type && target
+          has_structured_type && target
               ? m_reg_value.GetData(m_data, m_reg_info.byte_size,
                                     target->GetArchitecture().GetByteOrder())
               : m_reg_value.GetData(m_data);
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index 3303d57d4504a8..19826ba8ef2912 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -274,3 +274,330 @@ def test_union_ids_are_scoped_to_included_feature(self):
         self.assert_union_info("first", 8, ["first (uint64, 8 bytes)"])
         self.assert_union_info("second", 4, ["second (uint32, 4 bytes)"])
         self.assert_no_union_info("unresolved")
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_direct_union_sb_api(self):
+        process = self.setup_register_test(
+            """\
+            <vector id="v4f" type="ieee_single" count="4"/>
+            <vector id="v2d" type="ieee_double" count="2"/>
+            <union id="views">
+              <field name="f32" type="ieee_single"/>
+              <field name="f64" type="ieee_double"/>
+              <field name="floats" type="v4f"/>
+              <field name="doubles" type="v2d"/>
+              <field name="raw" type="uint128"/>
+            </union>
+            <reg name="u0" regnum="0" bitsize="128" type="views"/>
+            <reg name="pc" bitsize="64"/>""",
+            "0000c03f000020400000604000009040" + "00" * 8,
+        )
+
+        frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+        union = frame.FindRegister("u0")
+        self.assertTrue(union.IsValid())
+        self.assertTrue(union.GetType().IsValid())
+        self.assertEqual(union.GetByteSize(), 16)
+        self.assertEqual(union.GetNumChildren(), 5)
+        self.assertEqual(
+            [union.GetChildAtIndex(i).GetName() for i in range(5)],
+            ["f32", "f64", "floats", "doubles", "raw"],
+        )
+        self.assertAlmostEqual(
+            union.GetChildMemberWithName("f32").GetData().float[0], 1.5
+        )
+        self.assertAlmostEqual(
+            union.GetValueForExpressionPath(".floats[2]").GetData().float[0],
+            3.5,
+        )
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_smaller_than_register_sb_api(self):
+        process = self.setup_register_test(
+            """\
+            <union id="small">
+              <field name="value" type="uint32"/>
+            </union>
+            <reg name="u0" regnum="0" bitsize="128" type="small"/>
+            <reg name="pc" bitsize="64"/>""",
+            "2a000000" + "00" * 20,
+        )
+
+        union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
+        self.assertEqual(union.GetByteSize(), 16)
+        self.assertEqual(union.GetType().GetByteSize(), 4)
+        self.assertEqual(union.GetChildMemberWithName("value").GetValueAsUnsigned(), 42)
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_nested_union_and_vector_sb_api(self):
+        process = self.setup_register_test(
+            """\
+            <vector id="v4f" type="ieee_single" count="4"/>
+            <union id="float_views">
+              <field name="scalar" type="ieee_single"/>
+              <field name="lanes" type="v4f"/>
+            </union>
+            <union id="nested">
+              <field name="f32_view" type="float_views"/>
+              <field name="raw" type="uint128"/>
+            </union>
+            <reg name="n0" regnum="0" bitsize="128" type="nested"/>
+            <reg name="pc" bitsize="64"/>""",
+            "0000c03f000020400000604000009040" + "00" * 8,
+        )
+
+        frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+        union = frame.FindRegister("n0")
+        self.assertEqual(union.GetNumChildren(), 2)
+        lane = union.GetValueForExpressionPath(".f32_view.lanes[3]")
+        self.assertTrue(lane.IsValid())
+        self.assertAlmostEqual(lane.GetData().float[0], 4.5)
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_byte_view_and_vector_of_unions_sb_api(self):
+        process = self.setup_register_test(
+            """\
+            <vector id="c32" type="uint8" count="32"/>
+            <union id="bytes_view">
+              <field name="c" type="c32"/>
+            </union>
+            <union id="scalar32_views">
+              <field name="f32" type="ieee_single"/>
+              <field name="u32" type="uint32"/>
+            </union>
+            <vector id="v4views" type="scalar32_views" count="4"/>
+            <reg name="b0" regnum="0" bitsize="256" type="bytes_view"/>
+            <reg name="vu0" regnum="1" bitsize="128" type="v4views"/>
+            <reg name="pc" bitsize="64"/>""",
+            bytes(range(32)).hex() + "0000c03f000020400000604000009040" + "00" * 8,
+        )
+
+        frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+        byte_view = frame.FindRegister("b0").GetChildMemberWithName("c")
+        self.assertEqual(byte_view.GetNumChildren(), 32)
+        self.assertEqual(byte_view.GetChildAtIndex(16).GetValueAsUnsigned(), 16)
+
+        vector = frame.FindRegister("vu0")
+        self.assertEqual(vector.GetNumChildren(), 4)
+        third = vector.GetChildAtIndex(2)
+        self.assertEqual(
+            [third.GetChildAtIndex(i).GetName() for i in range(2)],
+            ["f32", "u32"],
+        )
+        self.assertAlmostEqual(
+            third.GetChildMemberWithName("f32").GetData().float[0], 3.5
+        )
+        self.expect("register read vu0[2].f32", substrs=["vu0[2].f32 = 3.5"])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_target_sized_union_vector_sb_api(self):
+        process = self.setup_register_test(
+            """\
+            <union id="pointer_view">
+              <field name="pointer" type="data_ptr"/>
+            </union>
+            <vector id="v2p" type="pointer_view" count="2"/>
+            <reg name="v0" regnum="0" bitsize="128" type="v2p"/>
+            <reg name="pc" bitsize="64"/>""",
+            "34120000000000007856000000000000" + "00" * 8,
+        )
+
+        vector = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("v0")
+        self.assertEqual(vector.GetNumChildren(), 2)
+        self.assertEqual(
+            vector.GetChildAtIndex(0)
+            .GetChildMemberWithName("pointer")
+            .GetValueAsUnsigned(),
+            0x1234,
+        )
+        self.assertEqual(
+            vector.GetChildAtIndex(1)
+            .GetChildMemberWithName("pointer")
+            .GetValueAsUnsigned(),
+            0x5678,
+        )
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    @skipIfLLVMTargetMissing("SystemZ")
+    def test_big_endian_union_sb_api(self):
+        process = self.setup_multidoc_test(
+            {
+                "target.xml": dedent(
+                    """\
+                <?xml version="1.0"?>
+                <target version="1.0">
+                  <architecture>s390x</architecture>
+                  <feature name="test.register.unions">
+                    <vector id="v2f" type="ieee_single" count="2"/>
+                    <union id="views">
+                      <field name="scalar" type="ieee_single"/>
+                      <field name="lanes" type="v2f"/>
+                    </union>
+                    <reg name="u0" regnum="0" bitsize="64" type="views"/>
+                    <union id="small">
+                      <field name="value" type="uint32"/>
+                    </union>
+                    <reg name="small" regnum="1" bitsize="128" type="small"/>
+                    <reg name="pswa" regnum="2" bitsize="64"/>
+                  </feature>
+                </target>"""
+                )
+            },
+            "3fc0000040200000" + "0000002a" + "00" * 20,
+        )
+
+        frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+        union = frame.FindRegister("u0")
+        self.assertAlmostEqual(
+            union.GetChildMemberWithName("scalar").GetData().float[0], 1.5
+        )
+        lanes = union.GetChildMemberWithName("lanes")
+        self.assertEqual(
+            [lanes.GetChildAtIndex(i).GetData().float[0] for i in range(2)],
+            [1.5, 2.5],
+        )
+        ull = process.GetTarget().GetBasicType(lldb.eBasicTypeUnsignedLongLong)
+        self.assertEqual(union.Cast(ull).GetValueAsUnsigned(), 0x3FC0000040200000)
+
+        small = frame.FindRegister("small")
+        self.assertEqual(small.GetByteSize(), 16)
+        self.assertEqual(small.GetType().GetByteSize(), 4)
+        self.assertEqual(small.GetChildMemberWithName("value").GetValueAsUnsigned(), 42)
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_union_cli_summary_and_member_paths(self):
+        process = self.setup_register_test(
+            """\
+            <vector id="v4f" type="ieee_single" count="4"/>
+            <union id="views">
+              <field name="f32" type="ieee_single"/>
+              <field name="f64" type="ieee_double"/>
+              <field name="u64" type="uint64"/>
+            </union>
+            <union id="vector_views">
+              <field name="floats" type="v4f"/>
+              <field name="raw" type="uint128"/>
+            </union>
+            <union id="nested">
+              <field name="view" type="vector_views"/>
+              <field name="raw" type="uint128"/>
+            </union>
+            <reg name="u0" altname="alt_u0" regnum="0" bitsize="64" type="views"/>
+            <reg name="u1" regnum="1" bitsize="128" type="vector_views"/>
+            <reg name="n0" regnum="2" bitsize="128" type="nested"/>
+            <reg name="pc" bitsize="64"/>""",
+            "0000c03fffffffff" + "0000c03f000020400000604000009040" * 2 + "00" * 8,
+        )
+
+        self.expect(
+            "register read u0",
+            patterns=[
+                r"u0 = 0xffffffff3fc00000\r?\n"
+                r"     = \(f32 = 1\.5, f64 = NaN, "
+                r"u64 = 18446744070484131840\)"
+            ],
+        )
+        self.expect("register read u0.f32", substrs=["u0.f32 = 1.5"])
+        self.expect("register read -A alt_u0.f32", substrs=["alt_u0.f32 = 1.5"])
+        self.expect("register read $u0.f64", substrs=["u0.f64 = NaN"])
+        self.expect("register read u1.floats[2]", substrs=["u1.floats[2] = 3.5"])
+        self.expect(
+            "register read n0.view.floats[3]",
+            substrs=["n0.view.floats[3] = 4.5"],
+        )
+
+        union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
+        self.assertEqual(
+            union.GetSummary(),
+            "(f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
+        )
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_exact_dotted_register_name_takes_precedence(self):
+        self.setup_register_test(
+            """\
+            <union id="views">
+              <field name="f32" type="ieee_single"/>
+              <field name="raw" type="uint64"/>
+            </union>
+            <reg name="u0" regnum="0" bitsize="64" type="views"/>
+            <reg name="u0.f32" regnum="1" bitsize="32"/>
+            <reg name="u0.view" regnum="2" bitsize="64" type="views"/>
+            <reg name="pc" bitsize="64"/>""",
+            "0000c03f00000000" + "2a000000" + "0000c03f00000000" + "00" * 8,
+        )
+
+        self.expect("register read u0.f32", substrs=["u0.f32 = 0x0000002a"])
+        self.expect("register read u0.view.f32", substrs=["u0.view.f32 = 1.5"])
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    def test_invalid_union_member_paths(self):
+        self.setup_register_test(
+            """\
+            <union id="views">
+              <field name="f32" type="ieee_single"/>
+              <field name="raw" type="uint64"/>
+            </union>
+            <vector id="v2views" type="views" count="2"/>
+            <reg name="u0" regnum="0" bitsize="64" type="views"/>
+            <reg name="v0" regnum="1" bitsize="128" type="v2views"/>
+            <reg name="pc" regnum="2" bitsize="64"/>""",
+            "00" * 32,
+        )
+
+        self.expect("register read u0.missing", error=True, substrs=["No field path"])
+        self.expect("register read u0.", error=True, substrs=["No field path"])
+        self.expect("register read u0..f32", error=True, substrs=["No field path"])
+        self.expect(
+            "register read v0[9].f32",
+            error=True,
+            substrs=["No field path '[9].f32'"],
+        )
+        self.expect(
+            "register read pc.field",
+            error=True,
+            substrs=["does not have a structured type"],
+        )
+
+    @skipIfXmlSupportMissing
+    @skipIfRemote
+    @skipIfLLVMTargetMissing("SystemZ")
+    def test_big_endian_union_cli(self):
+        self.setup_multidoc_test(
+            {
+                "target.xml": dedent(
+                    """\
+                <?xml version="1.0"?>
+                <target version="1.0">
+                  <architecture>s390x</architecture>
+                  <feature name="test.register.unions">
+                    <vector id="v2f" type="ieee_single" count="2"/>
+                    <union id="views">
+                      <field name="scalar" type="ieee_single"/>
+                      <field name="lanes" type="v2f"/>
+                    </union>
+                    <reg name="u0" regnum="0" bitsize="64" type="views"/>
+                    <reg name="pswa" regnum="1" bitsize="64"/>
+                  </feature>
+                </target>"""
+                )
+            },
+            "3fc0000040200000" + "00" * 8,
+        )
+
+        self.expect(
+            "register read u0",
+            patterns=[
+                r"u0 = 0x3fc0000040200000\r?\n"
+                r"     = \(scalar = 1\.5, lanes = \(1\.5, 2\.5\)\)"
+            ],
+        )
diff --git a/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp b/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
index 8cfb177fe625d9..88fa9f97152731 100644
--- a/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
+++ b/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
@@ -347,4 +347,164 @@ TEST_F(RegisterTypeBuilderClangTest, RejectsVectorSizeMismatch) {
   EXPECT_FALSE(builder.GetRegisterType(MakeRegisterInfo(pointer_vector, 15)));
 }
 
+TEST_F(RegisterTypeBuilderClangTest, BuildsPackedUnion) {
+  Target &target = m_debugger_sp->GetDummyTarget();
+  RegisterTypeBuiltin float_type("ieee_single", eEncodingIEEE754, eFormatFloat,
+                                 4);
+  RegisterTypeBuiltin double_type("ieee_double", eEncodingIEEE754, eFormatFloat,
+                                  8);
+  RegisterTypeUnion union_type("views",
+                               {RegisterTypeUnion::Field("f32", &float_type),
+                                RegisterTypeUnion::Field("f64", &double_type)});
+  RegisterTypeBuilderClang builder(target);
+
+  CompilerType type = builder.GetRegisterType(MakeRegisterInfo(union_type, 8));
+
+  ASSERT_TRUE(type);
+  EXPECT_EQ(type.GetTypeClass(), eTypeClassUnion);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 8u);
+  EXPECT_EQ(type.GetNumFields(), 2u);
+  for (size_t index = 0; index < 2; ++index) {
+    std::string name;
+    uint64_t bit_offset = UINT64_MAX;
+    ASSERT_TRUE(
+        type.GetFieldAtIndex(index, name, &bit_offset, nullptr, nullptr));
+    EXPECT_EQ(bit_offset, 0u);
+  }
+}
+
+TEST_F(RegisterTypeBuilderClangTest, AllowsUnionSmallerThanRegister) {
+  Target &target = m_debugger_sp->GetDummyTarget();
+  RegisterTypeBuiltin uint32_type("uint32", eEncodingUint, eFormatHex, 4);
+  RegisterTypeUnion union_type(
+      "small", {RegisterTypeUnion::Field("value", &uint32_type)});
+  RegisterTypeBuilderClang builder(target);
+
+  CompilerType type = builder.GetRegisterType(MakeRegisterInfo(union_type, 16));
+
+  ASSERT_TRUE(type);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 4u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsOddSizedUnion) {
+  Target &target = m_debugger_sp->GetDummyTarget();
+  RegisterTypeBuiltin byte_type("uint8", eEncodingUint, eFormatHex, 1);
+  RegisterTypeVector bytes_type("v3u8", &byte_type, 3);
+  RegisterTypeBuiltin uint16_type("uint16", eEncodingUint, eFormatHex, 2);
+  RegisterTypeUnion union_type(
+      "odd", {RegisterTypeUnion::Field("bytes", &bytes_type),
+              RegisterTypeUnion::Field("word", &uint16_type)});
+  RegisterTypeBuilderClang builder(target);
+
+  CompilerType type = builder.GetRegisterType(MakeRegisterInfo(union_type, 3));
+
+  ASSERT_TRUE(type);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 3u);
+  EXPECT_EQ(type.GetNumFields(), 2u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsNestedUnionAndVectorMembers) {
+  Target &target = m_debugger_sp->GetDummyTarget();
+  RegisterTypeBuiltin float_type("ieee_single", eEncodingIEEE754, eFormatFloat,
+                                 4);
+  RegisterTypeVector vector_type("v4f", &float_type, 4);
+  RegisterTypeUnion inner_type(
+      "inner", {RegisterTypeUnion::Field("scalar", &float_type),
+                RegisterTypeUnion::Field("lanes", &vector_type)});
+  RegisterTypeUnion outer_type("outer",
+                               {RegisterTypeUnion::Field("view", &inner_type),
+                                RegisterTypeUnion::Field("raw", &vector_type)});
+  RegisterTypeBuilderClang builder(target);
+
+  CompilerType type = builder.GetRegisterType(MakeRegisterInfo(outer_type, 16));
+
+  ASSERT_TRUE(type);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 16u);
+  EXPECT_EQ(type.GetNumFields(), 2u);
+  std::string name;
+  CompilerType inner = type.GetFieldAtIndex(0, name, nullptr, nullptr, nullptr);
+  ASSERT_TRUE(inner);
+  EXPECT_EQ(name, "view");
+  EXPECT_EQ(inner.GetNumFields(), 2u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsVectorOfUnions) {
+  Target &target = m_debugger_sp->GetDummyTarget();
+  RegisterTypeBuiltin float_type("ieee_single", eEncodingIEEE754, eFormatFloat,
+                                 4);
+  RegisterTypeBuiltin uint32_type("uint32", eEncodingUint, eFormatHex, 4);
+  RegisterTypeUnion element_type(
+      "views", {RegisterTypeUnion::Field("f32", &float_type),
+                RegisterTypeUnion::Field("u32", &uint32_type)});
+  RegisterTypeVector vector_type("v4views", &element_type, 4);
+  RegisterTypeBuilderClang builder(target);
+
+  CompilerType type =
+      builder.GetRegisterType(MakeRegisterInfo(vector_type, 16));
+
+  ASSERT_TRUE(type);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 16u);
+  EXPECT_NE(type.GetTypeInfo() & eTypeIsArray, 0u);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetNumChildren(true, nullptr)), 4u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsVectorOfTargetSizedUnions) {
+  Target &target = m_debugger_sp->GetDummyTarget();
+  lldb::TypeSystemClangSP type_system =
+      ScratchTypeSystemClang::GetForTarget(target);
+  ASSERT_TRUE(type_system);
+  std::optional<uint64_t> pointer_size = llvm::expectedToOptional(
+      type_system->GetType(type_system->getASTContext().VoidPtrTy)
+          .GetByteSize(nullptr));
+  ASSERT_TRUE(pointer_size);
+
+  RegisterTypeBuiltin pointer_type("data_ptr", eEncodingUint,
+                                   eFormatAddressInfo, std::nullopt);
+  RegisterTypeUnion element_type(
+      "pointer_view", {RegisterTypeUnion::Field("pointer", &pointer_type)});
+  RegisterTypeVector vector_type("pointer_views", &element_type, 2);
+  RegisterTypeBuilderClang builder(target);
+
+  CompilerType type = builder.GetRegisterType(
+      MakeRegisterInfo(vector_type, static_cast<uint32_t>(*pointer_size * 2)));
+
+  ASSERT_TRUE(type);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)),
+            *pointer_size * 2);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetNumChildren(true, nullptr)), 2u);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, BuildsTargetSizedUnionMember) {
+  Target &target = m_debugger_sp->GetDummyTarget();
+  lldb::TypeSystemClangSP type_system =
+      ScratchTypeSystemClang::GetForTarget(target);
+  ASSERT_TRUE(type_system);
+  std::optional<uint64_t> pointer_size = llvm::expectedToOptional(
+      type_system->GetType(type_system->getASTContext().VoidPtrTy)
+          .GetByteSize(nullptr));
+  ASSERT_TRUE(pointer_size);
+
+  RegisterTypeBuiltin pointer_type("data_ptr", eEncodingUint,
+                                   eFormatAddressInfo, std::nullopt);
+  RegisterTypeUnion union_type(
+      "pointer_view", {RegisterTypeUnion::Field("pointer", &pointer_type)});
+  RegisterTypeBuilderClang builder(target);
+
+  CompilerType type = builder.GetRegisterType(
+      MakeRegisterInfo(union_type, static_cast<uint32_t>(*pointer_size)));
+
+  ASSERT_TRUE(type);
+  EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), pointer_size);
+}
+
+TEST_F(RegisterTypeBuilderClangTest, RejectsOversizedUnion) {
+  Target &target = m_debugger_sp->GetDummyTarget();
+  RegisterTypeBuiltin uint64_type("uint64", eEncodingUint, eFormatHex, 8);
+  RegisterTypeUnion union_type(
+      "wide", {RegisterTypeUnion::Field("value", &uint64_type)});
+  RegisterTypeBuilderClang builder(target);
+
+  EXPECT_FALSE(builder.GetRegisterType(MakeRegisterInfo(union_type, 4)));
+}
+
 } // namespace

>From 6696a28c25db6e60b7e89d607746d5b1e4c2326c Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 16 Sep 2026 13:24:59 -0700
Subject: [PATCH 02/11] [lldb][7/7] Tighten register union value handling

---
 .../source/Commands/CommandObjectRegister.cpp | 85 ++++++++++---------
 lldb/source/DataFormatters/FormatManager.cpp  |  8 ++
 .../RegisterTypeBuilder/CMakeLists.txt        |  1 -
 .../RegisterTypeBuilderClang.cpp              | 12 ---
 .../gdb_remote_client/TestXMLRegisterUnion.py | 40 ++++++---
 .../Target/RegisterTypeBuilderClangTest.cpp   |  3 +-
 6 files changed, 81 insertions(+), 68 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 02717c82262777..792aebce5067cc 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -65,48 +65,41 @@ FindRegisterWithExpressionPath(RegisterContext &reg_ctx, llvm::StringRef name) {
   return {nullptr, {}};
 }
 
-static ValueObjectSP
-GetRegisterValueForExpressionPath(ValueObjectSP value,
-                                  llvm::StringRef expression_path) {
-  while (!expression_path.empty()) {
-    if (expression_path.consume_front(".")) {
-      size_t separator = expression_path.find_first_of(".[");
-      llvm::StringRef member = separator == llvm::StringRef::npos
-                                   ? expression_path
-                                   : expression_path.take_front(separator);
-      if (member.empty())
-        return {};
-      value = value->GetChildMemberWithName(member);
-      expression_path = separator == llvm::StringRef::npos
-                            ? llvm::StringRef()
-                            : expression_path.drop_front(separator);
-    } else if (expression_path.consume_front("[")) {
-      size_t closing_bracket = expression_path.find(']');
-      if (closing_bracket == llvm::StringRef::npos)
-        return {};
-
-      uint32_t index = 0;
-      if (expression_path.take_front(closing_bracket).getAsInteger(10, index))
-        return {};
-
-      llvm::Expected<uint32_t> num_children = value->GetNumChildren();
-      if (!num_children) {
-        llvm::consumeError(num_children.takeError());
-        return {};
-      }
-      if (index >= *num_children)
-        return {};
+static bool HasValidRegisterIndexes(ValueObjectSP value,
+                                    llvm::StringRef expression_path) {
+  size_t search_from = 0;
+  while (true) {
+    size_t open_bracket = expression_path.find('[', search_from);
+    if (open_bracket == llvm::StringRef::npos)
+      return true;
+
+    size_t close_bracket = expression_path.find(']', open_bracket + 1);
+    if (close_bracket == llvm::StringRef::npos)
+      return false;
 
-      value = value->GetChildAtIndex(index);
-      expression_path = expression_path.drop_front(closing_bracket + 1);
-    } else {
-      return {};
+    uint32_t index;
+    if (expression_path.slice(open_bracket + 1, close_bracket)
+            .getAsInteger(0, index))
+      return false;
+
+    llvm::StringRef parent_path = expression_path.take_front(open_bracket);
+    ValueObjectSP parent = parent_path.empty()
+                               ? value
+                               : value->GetValueForExpressionPath(parent_path);
+    if (!parent)
+      return false;
+
+    llvm::Expected<uint32_t> num_children = parent->GetNumChildren();
+    if (!num_children) {
+      llvm::consumeError(num_children.takeError());
+      return false;
     }
+    if (index >= *num_children)
+      return false;
 
-    if (!value)
-      return {};
+    search_from = close_bracket + 1;
   }
-  return value;
+  return true;
 }
 
 static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
@@ -248,9 +241,19 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
     lldb::RegisterContextSP reg_ctx_sp = frame->GetRegisterContextSP();
     ValueObjectSP register_value =
         ValueObjectRegister::Create(frame, reg_ctx_sp, &reg_info);
-    ValueObjectSP child =
-        GetRegisterValueForExpressionPath(register_value, expression_path);
-    if (!child) {
+    ValueObject::ExpressionPathScanEndReason reason =
+        ValueObject::eExpressionPathScanEndReasonUnknown;
+    ValueObject::ExpressionPathEndResultType result_type =
+        ValueObject::eExpressionPathEndResultTypeInvalid;
+    ValueObjectSP child;
+    // Generic expression paths permit out-of-bounds synthetic array members,
+    // but register paths must stay within the bytes supplied by the target.
+    if (HasValidRegisterIndexes(register_value, expression_path))
+      child = register_value->GetValueForExpressionPath(expression_path,
+                                                        &reason, &result_type);
+    if (!child ||
+        reason != ValueObject::eExpressionPathScanEndReasonEndOfString ||
+        result_type != ValueObject::eExpressionPathEndResultTypePlain) {
       llvm::StringRef error_path = expression_path;
       error_path.consume_front(".");
       result.AppendErrorWithFormat("No field path '%s' in register '%s'",
diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp
index e9f0e51e34149d..f1485d1e8a4075 100644
--- a/lldb/source/DataFormatters/FormatManager.cpp
+++ b/lldb/source/DataFormatters/FormatManager.cpp
@@ -763,6 +763,14 @@ void FormatManager::LoadSystemFormatters() {
   sys_category_sp->AddTypeSummary("OSType", eFormatterMatchExact,
                                   ostype_summary);
 
+  TypeSummaryImpl::Flags register_union_flags;
+  register_union_flags.SetShowMembersOneLiner(true);
+  // One rule covers all generated register union types without accumulating a
+  // formatter entry for every target description LLDB reads.
+  AddStringSummary(sys_category_sp, "",
+                   R"(^__lldb_register_union_[0-9]+_[0-9]+$)",
+                   register_union_flags, true);
+
   TypeFormatImpl::Flags fourchar_flags;
   fourchar_flags.SetCascades(true).SetSkipPointers(true).SetSkipReferences(
       true);
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt b/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
index c70e61ea0aa225..7411b24c86cbdb 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
+++ b/lldb/source/Plugins/RegisterTypeBuilder/CMakeLists.txt
@@ -7,6 +7,5 @@ add_lldb_library(lldbPluginRegisterTypeBuilderClang PLUGIN
     Support
   LINK_LIBS
     lldbCore
-    lldbDataFormatters
     lldbTarget
   )
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index ef0b1748f1c011..d18a0b9cafed08 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -10,8 +10,6 @@
 
 #include "RegisterTypeBuilderClang.h"
 #include "lldb/Core/PluginManager.h"
-#include "lldb/DataFormatters/DataVisualization.h"
-#include "lldb/DataFormatters/TypeSummary.h"
 #include "lldb/Utility/RegisterType.h"
 #include "lldb/lldb-enumerations.h"
 #include "llvm/ADT/bit.h"
@@ -240,16 +238,6 @@ RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
       expected_byte_size)
     return {};
 
-  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(type_name, lldb::eFormatterMatchExact,
-                                summary_sp);
-
   m_type_cache.try_emplace(
       std::make_pair(union_type->GetUID(), expected_byte_size), compiler_type);
   return compiler_type;
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index 19826ba8ef2912..be5c15b06c7a4d 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -496,15 +496,29 @@ def test_union_cli_summary_and_member_paths(self):
             "0000c03fffffffff" + "0000c03f000020400000604000009040" * 2 + "00" * 8,
         )
 
+        union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
+        self.assertEqual(
+            union.GetSummary(),
+            "(f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
+        )
+
         self.expect(
             "register read u0",
-            patterns=[
-                r"u0 = 0xffffffff3fc00000\r?\n"
-                r"     = \(f32 = 1\.5, f64 = NaN, "
-                r"u64 = 18446744070484131840\)"
+            substrs=[
+                "u0 = 0xffffffff3fc00000",
+                "     = (f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
             ],
         )
         self.expect("register read u0.f32", substrs=["u0.f32 = 1.5"])
+        self.expect(
+            "register read u0.u64 --format X",
+            substrs=["u0.u64 = 0xFFFFFFFF3FC00000"],
+        )
+        self.expect(
+            "register read u0 --format X",
+            substrs=["u0 = 0xFFFFFFFF3FC00000"],
+        )
+        self.expect("register read u0 --format X", matching=False, substrs=["f32 ="])
         self.expect("register read -A alt_u0.f32", substrs=["alt_u0.f32 = 1.5"])
         self.expect("register read $u0.f64", substrs=["u0.f64 = NaN"])
         self.expect("register read u1.floats[2]", substrs=["u1.floats[2] = 3.5"])
@@ -513,12 +527,6 @@ def test_union_cli_summary_and_member_paths(self):
             substrs=["n0.view.floats[3] = 4.5"],
         )
 
-        union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
-        self.assertEqual(
-            union.GetSummary(),
-            "(f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
-        )
-
     @skipIfXmlSupportMissing
     @skipIfRemote
     def test_exact_dotted_register_name_takes_precedence(self):
@@ -557,6 +565,12 @@ def test_invalid_union_member_paths(self):
         self.expect("register read u0.missing", error=True, substrs=["No field path"])
         self.expect("register read u0.", error=True, substrs=["No field path"])
         self.expect("register read u0..f32", error=True, substrs=["No field path"])
+        for path in ["u0[0]", "v0[", "v0[0", "v0[]", "v0[x]", "v0[0]junk"]:
+            self.expect(
+                "register read " + path,
+                error=True,
+                substrs=["No field path"],
+            )
         self.expect(
             "register read v0[9].f32",
             error=True,
@@ -596,8 +610,8 @@ def test_big_endian_union_cli(self):
 
         self.expect(
             "register read u0",
-            patterns=[
-                r"u0 = 0x3fc0000040200000\r?\n"
-                r"     = \(scalar = 1\.5, lanes = \(1\.5, 2\.5\)\)"
+            substrs=[
+                "u0 = 0x3fc0000040200000",
+                "     = (scalar = 1.5, lanes = (1.5, 2.5))",
             ],
         )
diff --git a/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp b/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
index 88fa9f97152731..9bec2179bba446 100644
--- a/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
+++ b/lldb/unittests/Target/RegisterTypeBuilderClangTest.cpp
@@ -364,6 +364,7 @@ TEST_F(RegisterTypeBuilderClangTest, BuildsPackedUnion) {
   EXPECT_EQ(type.GetTypeClass(), eTypeClassUnion);
   EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 8u);
   EXPECT_EQ(type.GetNumFields(), 2u);
+  EXPECT_EQ(type, builder.GetRegisterType(MakeRegisterInfo(union_type, 8)));
   for (size_t index = 0; index < 2; ++index) {
     std::string name;
     uint64_t bit_offset = UINT64_MAX;
@@ -444,7 +445,7 @@ TEST_F(RegisterTypeBuilderClangTest, BuildsVectorOfUnions) {
 
   ASSERT_TRUE(type);
   EXPECT_EQ(llvm::expectedToOptional(type.GetByteSize(nullptr)), 16u);
-  EXPECT_NE(type.GetTypeInfo() & eTypeIsArray, 0u);
+  EXPECT_TRUE(type.IsArrayType());
   EXPECT_EQ(llvm::expectedToOptional(type.GetNumChildren(true, nullptr)), 4u);
 }
 

>From 3e8fea598a0d81c9b6674eb982e852f80d2e8dcf Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 23 Sep 2026 11:33:55 -0700
Subject: [PATCH 03/11] Fixing greg's comments

---
 lldb/include/lldb/Utility/RegisterType.h      | 16 +++++-
 .../source/Commands/CommandObjectRegister.cpp | 52 ++++++++++---------
 lldb/source/Core/DumpRegisterValue.cpp        |  3 +-
 lldb/source/Core/Value.cpp                    |  2 +-
 lldb/source/DataFormatters/FormatManager.cpp  |  5 +-
 .../RegisterTypeBuilderClang.cpp              |  2 +
 lldb/source/Utility/RegisterType.cpp          |  8 ++-
 .../ValueObject/ValueObjectRegister.cpp       |  6 +--
 .../gdb_remote_client/TestXMLRegisterUnion.py | 44 ++++++++++------
 lldb/unittests/Utility/RegisterTypeTest.cpp   | 12 +++++
 10 files changed, 98 insertions(+), 52 deletions(-)

diff --git a/lldb/include/lldb/Utility/RegisterType.h b/lldb/include/lldb/Utility/RegisterType.h
index ae77da554dec38..3d271befcdf2d0 100644
--- a/lldb/include/lldb/Utility/RegisterType.h
+++ b/lldb/include/lldb/Utility/RegisterType.h
@@ -81,6 +81,18 @@ class RegisterType {
   std::vector<const RegisterType *> m_dependencies;
 };
 
+/// A register type whose value is interpreted as a structured layout.
+class RegisterTypeComposite : public RegisterType {
+public:
+  static bool classof(const RegisterType *type) {
+    return type->getKind() == eRegisterTypeKindVector ||
+           type->getKind() == eRegisterTypeKindUnion;
+  }
+
+protected:
+  RegisterTypeComposite(RegisterTypeKind kind, std::string id);
+};
+
 /// A predefined GDB target-description type. Builtin types are referenced by
 /// name and are not emitted as XML definitions.
 class RegisterTypeBuiltin : public RegisterType {
@@ -109,7 +121,7 @@ class RegisterTypeBuiltin : public RegisterType {
 };
 
 /// A GDB target-description vector type. The element type must outlive it.
-class RegisterTypeVector : public RegisterType {
+class RegisterTypeVector : public RegisterTypeComposite {
 public:
   RegisterTypeVector(std::string id, const RegisterType *element_type,
                      uint32_t count);
@@ -135,7 +147,7 @@ class RegisterTypeVector : public RegisterType {
 
 /// A GDB target-description union type. Each field is an alternative view of
 /// the same register data. Referenced field types must outlive the union.
-class RegisterTypeUnion : public RegisterType {
+class RegisterTypeUnion : public RegisterTypeComposite {
 public:
   class Field {
   public:
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 792aebce5067cc..9f42e873eaca23 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -47,6 +47,8 @@ static size_t GetNameSize(const RegisterInfo *reg_info, bool use_primary_name) {
   return reg_name ? strlen(reg_name) : 0;
 }
 
+// Return the exact register and an empty path, the longest register-name prefix
+// and its remaining path, or {nullptr, {}} when no register matches.
 static std::pair<const RegisterInfo *, llvm::StringRef>
 FindRegisterWithExpressionPath(RegisterContext &reg_ctx, llvm::StringRef name) {
   if (const RegisterInfo *reg_info = reg_ctx.GetRegisterInfoByName(name))
@@ -65,41 +67,54 @@ FindRegisterWithExpressionPath(RegisterContext &reg_ctx, llvm::StringRef name) {
   return {nullptr, {}};
 }
 
-static bool HasValidRegisterIndexes(ValueObjectSP value,
-                                    llvm::StringRef expression_path) {
+static ValueObjectSP
+ResolveRegisterExpressionPath(ValueObjectSP value,
+                              llvm::StringRef expression_path) {
+  // Generic expression paths permit out-of-bounds synthetic array members,
+  // but register paths must stay within the bytes supplied by the target.
   size_t search_from = 0;
   while (true) {
     size_t open_bracket = expression_path.find('[', search_from);
     if (open_bracket == llvm::StringRef::npos)
-      return true;
+      break;
 
     size_t close_bracket = expression_path.find(']', open_bracket + 1);
     if (close_bracket == llvm::StringRef::npos)
-      return false;
+      return {};
 
     uint32_t index;
     if (expression_path.slice(open_bracket + 1, close_bracket)
             .getAsInteger(0, index))
-      return false;
+      return {};
 
     llvm::StringRef parent_path = expression_path.take_front(open_bracket);
     ValueObjectSP parent = parent_path.empty()
                                ? value
                                : value->GetValueForExpressionPath(parent_path);
     if (!parent)
-      return false;
+      return {};
 
     llvm::Expected<uint32_t> num_children = parent->GetNumChildren();
     if (!num_children) {
       llvm::consumeError(num_children.takeError());
-      return false;
+      return {};
     }
     if (index >= *num_children)
-      return false;
+      return {};
 
     search_from = close_bracket + 1;
   }
-  return true;
+
+  ValueObject::ExpressionPathScanEndReason reason =
+      ValueObject::eExpressionPathScanEndReasonUnknown;
+  ValueObject::ExpressionPathEndResultType result_type =
+      ValueObject::eExpressionPathEndResultTypeInvalid;
+  ValueObjectSP child =
+      value->GetValueForExpressionPath(expression_path, &reason, &result_type);
+  if (reason != ValueObject::eExpressionPathScanEndReasonEndOfString ||
+      result_type != ValueObject::eExpressionPathEndResultTypePlain)
+    return {};
+  return child;
 }
 
 static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
@@ -228,8 +243,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
                          llvm::StringRef expression_path,
                          size_t reg_name_right_align_at,
                          CommandReturnObject &result) {
-    if (!llvm::isa_and_present<RegisterTypeUnion, RegisterTypeVector>(
-            reg_info.register_type)) {
+    if (!llvm::isa_and_present<RegisterTypeComposite>(reg_info.register_type)) {
       result.AppendErrorWithFormat(
           "Register '%s' does not have a structured type", reg_info.name);
       return false;
@@ -241,19 +255,9 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
     lldb::RegisterContextSP reg_ctx_sp = frame->GetRegisterContextSP();
     ValueObjectSP register_value =
         ValueObjectRegister::Create(frame, reg_ctx_sp, &reg_info);
-    ValueObject::ExpressionPathScanEndReason reason =
-        ValueObject::eExpressionPathScanEndReasonUnknown;
-    ValueObject::ExpressionPathEndResultType result_type =
-        ValueObject::eExpressionPathEndResultTypeInvalid;
-    ValueObjectSP child;
-    // Generic expression paths permit out-of-bounds synthetic array members,
-    // but register paths must stay within the bytes supplied by the target.
-    if (HasValidRegisterIndexes(register_value, expression_path))
-      child = register_value->GetValueForExpressionPath(expression_path,
-                                                        &reason, &result_type);
-    if (!child ||
-        reason != ValueObject::eExpressionPathScanEndReasonEndOfString ||
-        result_type != ValueObject::eExpressionPathEndResultTypePlain) {
+    ValueObjectSP child =
+        ResolveRegisterExpressionPath(register_value, expression_path);
+    if (!child) {
       llvm::StringRef error_path = expression_path;
       error_path.consume_front(".");
       result.AppendErrorWithFormat("No field path '%s' in register '%s'",
diff --git a/lldb/source/Core/DumpRegisterValue.cpp b/lldb/source/Core/DumpRegisterValue.cpp
index 84b1e03dd4576c..9c4b679b70970e 100644
--- a/lldb/source/Core/DumpRegisterValue.cpp
+++ b/lldb/source/Core/DumpRegisterValue.cpp
@@ -143,8 +143,7 @@ void lldb_private::DumpRegisterValue(const RegisterValue &reg_val, Stream &s,
   const RegisterTypeFlags *flags_type =
       llvm::dyn_cast_if_present<RegisterTypeFlags>(reg_info.register_type);
   if (!flags_type &&
-      !llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
-          reg_info.register_type))
+      !llvm::isa_and_present<RegisterTypeComposite>(reg_info.register_type))
     return;
   if (flags_type && reg_info.byte_size != 4 && reg_info.byte_size != 8)
     return;
diff --git a/lldb/source/Core/Value.cpp b/lldb/source/Core/Value.cpp
index 11d313ccd9e9b3..eec39e183446fa 100644
--- a/lldb/source/Core/Value.cpp
+++ b/lldb/source/Core/Value.cpp
@@ -497,7 +497,7 @@ Status Value::GetValueAsData(ExecutionContext *exe_ctx, DataExtractor &data,
         // ValueObjectRegister stores structured register types in target byte
         // order and all other register buffers in host byte order.
         if (m_context_type == ContextType::RegisterInfo &&
-            !llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
+            !llvm::isa_and_present<RegisterTypeComposite>(
                 GetRegisterInfo()->register_type))
           byte_order = endian::InlHostByteOrder();
         data.SetByteOrder(byte_order);
diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp
index f1485d1e8a4075..237ca0347acb3a 100644
--- a/lldb/source/DataFormatters/FormatManager.cpp
+++ b/lldb/source/DataFormatters/FormatManager.cpp
@@ -765,8 +765,9 @@ void FormatManager::LoadSystemFormatters() {
 
   TypeSummaryImpl::Flags register_union_flags;
   register_union_flags.SetShowMembersOneLiner(true);
-  // One rule covers all generated register union types without accumulating a
-  // formatter entry for every target description LLDB reads.
+  // RegisterTypeBuilderClang gives synthesized union types this prefix. One
+  // rule covers all of them without accumulating a formatter entry for every
+  // target description LLDB reads.
   AddStringSummary(sys_category_sp, "",
                    R"(^__lldb_register_union_[0-9]+_[0-9]+$)",
                    register_union_flags, true);
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index d18a0b9cafed08..c7a80c9c9e570d 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -306,6 +306,8 @@ std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
   }
   case RegisterType::eRegisterTypeKindEnum:
   case RegisterType::eRegisterTypeKindFlags:
+    // Enum and flags use dedicated top-level builders. They are not supported
+    // when recursively building vector or union members.
     return std::nullopt;
   }
 }
diff --git a/lldb/source/Utility/RegisterType.cpp b/lldb/source/Utility/RegisterType.cpp
index 84131da1254083..7adcec38318016 100644
--- a/lldb/source/Utility/RegisterType.cpp
+++ b/lldb/source/Utility/RegisterType.cpp
@@ -28,6 +28,10 @@ RegisterType::RegisterType(RegisterTypeKind kind, std::string id)
     : m_kind(kind), m_id(std::move(id)),
       m_uid(g_next_register_type_uid.fetch_add(1, std::memory_order_relaxed)) {}
 
+RegisterTypeComposite::RegisterTypeComposite(RegisterTypeKind kind,
+                                             std::string id)
+    : RegisterType(kind, std::move(id)) {}
+
 void RegisterType::ToXML(Stream &strm,
                          std::unordered_set<std::string> &previously_emitted,
                          const RegisterType *user) const {
@@ -65,7 +69,7 @@ void RegisterTypeBuiltin::ToXMLElement(Stream &, const RegisterType *) const {}
 RegisterTypeVector::RegisterTypeVector(std::string id,
                                        const RegisterType *element_type,
                                        uint32_t count)
-    : RegisterType(eRegisterTypeKindVector, std::move(id)),
+    : RegisterTypeComposite(eRegisterTypeKindVector, std::move(id)),
       m_element_type(element_type), m_count(count) {
   assert(m_element_type && "Vector element type cannot be null");
   assert(m_count && "Vector element count cannot be zero");
@@ -117,7 +121,7 @@ RegisterTypeUnion::Field::Field(std::string name, const RegisterType *type)
 }
 
 RegisterTypeUnion::RegisterTypeUnion(std::string id, std::vector<Field> fields)
-    : RegisterType(eRegisterTypeKindUnion, std::move(id)),
+    : RegisterTypeComposite(eRegisterTypeKindUnion, std::move(id)),
       m_fields(std::move(fields)) {
   assert(!m_fields.empty() && "Union must have at least one field");
 
diff --git a/lldb/source/ValueObject/ValueObjectRegister.cpp b/lldb/source/ValueObject/ValueObjectRegister.cpp
index 71be7ab993681d..497aabfb950ddd 100644
--- a/lldb/source/ValueObject/ValueObjectRegister.cpp
+++ b/lldb/source/ValueObject/ValueObjectRegister.cpp
@@ -229,8 +229,8 @@ CompilerType ValueObjectRegister::GetCompilerTypeImpl() {
   if (!target)
     return {};
 
-  if (llvm::isa_and_present<RegisterTypeBuiltin, RegisterTypeVector,
-                            RegisterTypeUnion>(m_reg_info.register_type)) {
+  if (llvm::isa_and_present<RegisterTypeBuiltin, RegisterTypeComposite>(
+          m_reg_info.register_type)) {
     m_compiler_type = target->GetRegisterType(m_reg_info);
     if (m_compiler_type.IsValid())
       return m_compiler_type;
@@ -285,7 +285,7 @@ bool ValueObjectRegister::UpdateValue() {
     if (m_reg_ctx_sp->ReadRegister(&m_reg_info, m_reg_value)) {
       Target *target = exe_ctx.GetTargetPtr();
       const bool has_structured_type =
-          llvm::isa_and_present<RegisterTypeVector, RegisterTypeUnion>(
+          llvm::isa_and_present<RegisterTypeComposite>(
               m_reg_info.register_type);
       // Scalar registers remain in host byte order, while CompilerType
       // children need target-order bytes to interpret structured layouts.
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index be5c15b06c7a4d..af15fec036b7f3 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -281,12 +281,9 @@ def test_direct_union_sb_api(self):
         process = self.setup_register_test(
             """\
             <vector id="v4f" type="ieee_single" count="4"/>
-            <vector id="v2d" type="ieee_double" count="2"/>
             <union id="views">
-              <field name="f32" type="ieee_single"/>
-              <field name="f64" type="ieee_double"/>
-              <field name="floats" type="v4f"/>
-              <field name="doubles" type="v2d"/>
+              <field name="scalar" type="ieee_single"/>
+              <field name="lanes" type="v4f"/>
               <field name="raw" type="uint128"/>
             </union>
             <reg name="u0" regnum="0" bitsize="128" type="views"/>
@@ -298,17 +295,20 @@ def test_direct_union_sb_api(self):
         union = frame.FindRegister("u0")
         self.assertTrue(union.IsValid())
         self.assertTrue(union.GetType().IsValid())
+        self.assertRegex(
+            union.GetType().GetName(), r"^__lldb_register_union_[0-9]+_16$"
+        )
         self.assertEqual(union.GetByteSize(), 16)
-        self.assertEqual(union.GetNumChildren(), 5)
+        self.assertEqual(union.GetNumChildren(), 3)
         self.assertEqual(
-            [union.GetChildAtIndex(i).GetName() for i in range(5)],
-            ["f32", "f64", "floats", "doubles", "raw"],
+            [union.GetChildAtIndex(i).GetName() for i in range(3)],
+            ["scalar", "lanes", "raw"],
         )
         self.assertAlmostEqual(
-            union.GetChildMemberWithName("f32").GetData().float[0], 1.5
+            union.GetChildMemberWithName("scalar").GetData().float[0], 1.5
         )
         self.assertAlmostEqual(
-            union.GetValueForExpressionPath(".floats[2]").GetData().float[0],
+            union.GetValueForExpressionPath(".lanes[2]").GetData().float[0],
             3.5,
         )
 
@@ -326,6 +326,7 @@ def test_union_smaller_than_register_sb_api(self):
         )
 
         union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
+        self.assertRegex(union.GetType().GetName(), r"^__lldb_register_union_[0-9]+_4$")
         self.assertEqual(union.GetByteSize(), 16)
         self.assertEqual(union.GetType().GetByteSize(), 4)
         self.assertEqual(union.GetChildMemberWithName("value").GetValueAsUnsigned(), 42)
@@ -562,14 +563,25 @@ def test_invalid_union_member_paths(self):
             "00" * 32,
         )
 
-        self.expect("register read u0.missing", error=True, substrs=["No field path"])
-        self.expect("register read u0.", error=True, substrs=["No field path"])
-        self.expect("register read u0..f32", error=True, substrs=["No field path"])
-        for path in ["u0[0]", "v0[", "v0[0", "v0[]", "v0[x]", "v0[0]junk"]:
+        invalid_paths = {
+            "u0.missing": "No field path 'missing' in register 'u0'",
+            "u0.": "No field path '' in register 'u0'",
+            "u0..f32": "No field path '.f32' in register 'u0'",
+            "u0[0]": "No field path '[0]' in register 'u0'",
+            "v0[": "No field path '[' in register 'v0'",
+            "v0[0": "No field path '[0' in register 'v0'",
+            "v0[]": "No field path '[]' in register 'v0'",
+            "v0[x]": "No field path '[x]' in register 'v0'",
+            "v0[-1]": "No field path '[-1]' in register 'v0'",
+            "v0[4294967296]": "No field path '[4294967296]' in register 'v0'",
+            "v0[0]junk": "No field path '[0]junk' in register 'v0'",
+            "v0[0].missing": "No field path '[0].missing' in register 'v0'",
+        }
+        for path, diagnostic in invalid_paths.items():
             self.expect(
                 "register read " + path,
                 error=True,
-                substrs=["No field path"],
+                substrs=[diagnostic],
             )
         self.expect(
             "register read v0[9].f32",
@@ -579,7 +591,7 @@ def test_invalid_union_member_paths(self):
         self.expect(
             "register read pc.field",
             error=True,
-            substrs=["does not have a structured type"],
+            substrs=["Register 'pc' does not have a structured type"],
         )
 
     @skipIfXmlSupportMissing
diff --git a/lldb/unittests/Utility/RegisterTypeTest.cpp b/lldb/unittests/Utility/RegisterTypeTest.cpp
index 3d50e6b7404c75..10bc4151222e5d 100644
--- a/lldb/unittests/Utility/RegisterTypeTest.cpp
+++ b/lldb/unittests/Utility/RegisterTypeTest.cpp
@@ -631,6 +631,18 @@ TEST(RegisterTypeBuiltinTest, DoesNotSerialize) {
   EXPECT_TRUE(strm.GetString().empty());
 }
 
+TEST(RegisterTypeCompositeTest, TypeIdentification) {
+  RegisterTypeBuiltin builtin_type("ieee_single", eEncodingIEEE754,
+                                   eFormatFloat, 4);
+  RegisterTypeVector vector_type("v4f", &builtin_type, 4);
+  RegisterTypeUnion union_type(
+      "views", {RegisterTypeUnion::Field("scalar", &builtin_type)});
+
+  EXPECT_FALSE(llvm::isa<RegisterTypeComposite>(&builtin_type));
+  EXPECT_TRUE(llvm::isa<RegisterTypeComposite>(&vector_type));
+  EXPECT_TRUE(llvm::isa<RegisterTypeComposite>(&union_type));
+}
+
 TEST(RegisterTypeVectorTest, ConstructionAndXML) {
   RegisterTypeBuiltin element_type("ieee_single", eEncodingIEEE754,
                                    eFormatFloat, 4);

>From b407997d15ea68efa87b48562adf64dea8f63da1 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 23 Sep 2026 11:44:26 -0700
Subject: [PATCH 04/11] [lldb][NFC] Address register union builder review
 feedback

---
 .../RegisterTypeBuilderClang.cpp                | 17 +++++++++++------
 .../RegisterTypeBuilderClang.h                  |  6 ++++--
 2 files changed, 15 insertions(+), 8 deletions(-)

diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index c7a80c9c9e570d..be4929d1e34f50 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -154,7 +154,7 @@ RegisterTypeBuilderClang::BuildVectorType(const RegisterTypeVector *vector_type,
     return *type;
 
   std::optional<uint32_t> element_size =
-      GetTargetByteSize(vector_type->GetElementType(), type_system);
+      GetExpectedByteSize(vector_type->GetElementType(), type_system);
   if (!element_size || *element_size > UINT32_MAX / vector_type->GetCount() ||
       *element_size * vector_type->GetCount() != expected_byte_size)
     return {};
@@ -208,9 +208,11 @@ RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
 
   std::vector<std::pair<llvm::StringRef, CompilerType>> fields;
   fields.reserve(union_type->GetFields().size());
+  // XML parsing restricts union fields to builtins, vectors, and unions.
+  // BuildType recursively constructs the vector and union cases.
   for (const RegisterTypeUnion::Field &field : union_type->GetFields()) {
     std::optional<uint32_t> field_size =
-        GetTargetByteSize(field.GetType(), type_system);
+        GetExpectedByteSize(field.GetType(), type_system);
     if (!field_size || *field_size > expected_byte_size)
       return {};
 
@@ -221,6 +223,9 @@ RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
     fields.emplace_back(field.GetName(), field_type);
   }
 
+  // XML type IDs are scoped to a feature and need not be unique or valid
+  // Clang identifiers. Keep the ID on RegisterTypeUnion for XML lookup and use
+  // the process-wide UID and instantiated size for a unique scratch AST tag.
   std::string type_name = "__lldb_register_union_" +
                           std::to_string(union_type->GetUID()) + "_" +
                           std::to_string(expected_byte_size);
@@ -263,7 +268,7 @@ RegisterTypeBuilderClang::BuildType(const RegisterType *register_type,
   }
 }
 
-std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
+std::optional<uint32_t> RegisterTypeBuilderClang::GetExpectedByteSize(
     const RegisterType *register_type, lldb::TypeSystemClangSP type_system) {
   if (std::optional<uint64_t> fixed_size = register_type->GetByteSize()) {
     if (*fixed_size <= UINT32_MAX)
@@ -287,7 +292,7 @@ std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
   case RegisterType::eRegisterTypeKindVector: {
     const auto *vector_type = llvm::cast<RegisterTypeVector>(register_type);
     std::optional<uint32_t> element_size =
-        GetTargetByteSize(vector_type->GetElementType(), type_system);
+        GetExpectedByteSize(vector_type->GetElementType(), type_system);
     if (!element_size || *element_size > UINT32_MAX / vector_type->GetCount())
       return std::nullopt;
     return *element_size * vector_type->GetCount();
@@ -297,7 +302,7 @@ std::optional<uint32_t> RegisterTypeBuilderClang::GetTargetByteSize(
     for (const RegisterTypeUnion::Field &field :
          llvm::cast<RegisterTypeUnion>(register_type)->GetFields()) {
       std::optional<uint32_t> field_size =
-          GetTargetByteSize(field.GetType(), type_system);
+          GetExpectedByteSize(field.GetType(), type_system);
       if (!field_size)
         return std::nullopt;
       byte_size = std::max(byte_size, *field_size);
@@ -345,7 +350,7 @@ RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo &reg_info) {
     return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
   case RegisterType::eRegisterTypeKindUnion: {
     std::optional<uint32_t> byte_size =
-        GetTargetByteSize(reg_info.register_type, type_system);
+        GetExpectedByteSize(reg_info.register_type, type_system);
     if (!byte_size || *byte_size > reg_info.byte_size)
       return {};
     return BuildType(reg_info.register_type, *byte_size, type_system);
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
index a8c005d77252f3..128da85ad4eccc 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
@@ -57,9 +57,11 @@ class RegisterTypeBuilderClang : public RegisterTypeBuilder {
                          uint32_t expected_byte_size,
                          lldb::TypeSystemClangSP type_system);
 
+  /// Return the target-resolved byte size of \p register_type. Builders verify
+  /// the resulting Clang type has this size.
   std::optional<uint32_t>
-  GetTargetByteSize(const RegisterType *register_type,
-                    lldb::TypeSystemClangSP type_system);
+  GetExpectedByteSize(const RegisterType *register_type,
+                      lldb::TypeSystemClangSP type_system);
 
   Target &m_target;
 

>From e2c3ba4cacaefcd6f33f2baf6aa5c4a8b6e896f9 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Fri, 25 Sep 2026 11:25:24 -0700
Subject: [PATCH 05/11] Use shared expression path resolution for registers

---
 .../source/Commands/CommandObjectRegister.cpp | 237 +++++++-----------
 lldb/source/DataFormatters/FormatManager.cpp  |   9 -
 lldb/source/Target/StackFrame.cpp             | 113 +++++++--
 lldb/source/ValueObject/DILEval.cpp           |  93 +++++--
 lldb/source/ValueObject/ValueObject.cpp       |  44 +++-
 .../gdb_remote_client/TestXMLRegisterUnion.py | 209 +++++++++++++--
 6 files changed, 489 insertions(+), 216 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 9f42e873eaca23..cb9b26c3507bcc 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -28,13 +28,15 @@
 #include "lldb/Target/Thread.h"
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/DataExtractor.h"
-#include "lldb/Utility/RegisterType.h"
 #include "lldb/Utility/RegisterValue.h"
 #include "lldb/Utility/StreamString.h"
-#include "lldb/ValueObject/ValueObjectRegister.h"
+#include "lldb/ValueObject/ValueObject.h"
 #include "llvm/Support/Errno.h"
 #include "llvm/Support/Error.h"
 
+#include <string>
+#include <vector>
+
 using namespace lldb;
 using namespace lldb_private;
 
@@ -47,76 +49,6 @@ static size_t GetNameSize(const RegisterInfo *reg_info, bool use_primary_name) {
   return reg_name ? strlen(reg_name) : 0;
 }
 
-// Return the exact register and an empty path, the longest register-name prefix
-// and its remaining path, or {nullptr, {}} when no register matches.
-static std::pair<const RegisterInfo *, llvm::StringRef>
-FindRegisterWithExpressionPath(RegisterContext &reg_ctx, llvm::StringRef name) {
-  if (const RegisterInfo *reg_info = reg_ctx.GetRegisterInfoByName(name))
-    return {reg_info, {}};
-
-  size_t search_end = name.size();
-  while (search_end) {
-    size_t split = name.take_front(search_end).find_last_of(".[");
-    if (split == llvm::StringRef::npos)
-      break;
-    if (const RegisterInfo *reg_info =
-            reg_ctx.GetRegisterInfoByName(name.take_front(split)))
-      return {reg_info, name.drop_front(split)};
-    search_end = split;
-  }
-  return {nullptr, {}};
-}
-
-static ValueObjectSP
-ResolveRegisterExpressionPath(ValueObjectSP value,
-                              llvm::StringRef expression_path) {
-  // Generic expression paths permit out-of-bounds synthetic array members,
-  // but register paths must stay within the bytes supplied by the target.
-  size_t search_from = 0;
-  while (true) {
-    size_t open_bracket = expression_path.find('[', search_from);
-    if (open_bracket == llvm::StringRef::npos)
-      break;
-
-    size_t close_bracket = expression_path.find(']', open_bracket + 1);
-    if (close_bracket == llvm::StringRef::npos)
-      return {};
-
-    uint32_t index;
-    if (expression_path.slice(open_bracket + 1, close_bracket)
-            .getAsInteger(0, index))
-      return {};
-
-    llvm::StringRef parent_path = expression_path.take_front(open_bracket);
-    ValueObjectSP parent = parent_path.empty()
-                               ? value
-                               : value->GetValueForExpressionPath(parent_path);
-    if (!parent)
-      return {};
-
-    llvm::Expected<uint32_t> num_children = parent->GetNumChildren();
-    if (!num_children) {
-      llvm::consumeError(num_children.takeError());
-      return {};
-    }
-    if (index >= *num_children)
-      return {};
-
-    search_from = close_bracket + 1;
-  }
-
-  ValueObject::ExpressionPathScanEndReason reason =
-      ValueObject::eExpressionPathScanEndReasonUnknown;
-  ValueObject::ExpressionPathEndResultType result_type =
-      ValueObject::eExpressionPathEndResultTypeInvalid;
-  ValueObjectSP child =
-      value->GetValueForExpressionPath(expression_path, &reason, &result_type);
-  if (reason != ValueObject::eExpressionPathScanEndReasonEndOfString ||
-      result_type != ValueObject::eExpressionPathEndResultTypePlain)
-    return {};
-  return child;
-}
-
 static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
                                          const RegisterSet &reg_set,
                                          bool use_primary_name,
@@ -140,31 +72,6 @@ static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
   return name_right_align_at;
 }
 
-// We expect that [command] only contains register names to be printed.
-static size_t ComputeLongestRegisterName(Args &command,
-                                         RegisterContext *reg_ctx,
-                                         bool use_primary_name) {
-  size_t name_right_align_at = 0;
-
-  // Loop through all the arguments to find the longest register name.
-  for (auto &entry : command) {
-    // In most LLDB commands we accept '$<register>' as well as '<register>'
-    // for example '$rbx' for 'rbx'. However internally the name does not have
-    // '$'.
-    llvm::StringRef arg_str = entry.ref();
-    arg_str.consume_front("$");
-
-    const RegisterInfo *reg_info =
-        FindRegisterWithExpressionPath(*reg_ctx, arg_str).first;
-    if (reg_info) {
-      name_right_align_at = std::max(name_right_align_at,
-                                     GetNameSize(reg_info, use_primary_name));
-    }
-  }
-
-  return name_right_align_at;
-}
-
 class CommandObjectRegisterRead : public CommandObjectParsed {
 public:
   CommandObjectRegisterRead(CommandInterpreter &interpreter)
@@ -238,34 +145,12 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
     return true;
   }
 
-  bool DumpRegisterField(const ExecutionContext &exe_ctx, Stream &strm,
-                         const RegisterInfo &reg_info,
+  bool DumpRegisterField(Stream &strm, const RegisterInfo &reg_info,
+                         const ValueObjectSP &value_sp,
                          llvm::StringRef expression_path,
                          size_t reg_name_right_align_at,
                          CommandReturnObject &result) {
-    if (!llvm::isa_and_present<RegisterTypeComposite>(reg_info.register_type)) {
-      result.AppendErrorWithFormat(
-          "Register '%s' does not have a structured type", reg_info.name);
-      return false;
-    }
-
-    StackFrame *frame = exe_ctx.GetFramePtr();
-    if (!frame)
-      return false;
-    lldb::RegisterContextSP reg_ctx_sp = frame->GetRegisterContextSP();
-    ValueObjectSP register_value =
-        ValueObjectRegister::Create(frame, reg_ctx_sp, &reg_info);
-    ValueObjectSP child =
-        ResolveRegisterExpressionPath(register_value, expression_path);
-    if (!child) {
-      llvm::StringRef error_path = expression_path;
-      error_path.consume_front(".");
-      result.AppendErrorWithFormat("No field path '%s' in register '%s'",
-                                   error_path.str().c_str(), reg_info.name);
-      return false;
-    }
-
-    DumpValueObjectOptions options(*child);
+    DumpValueObjectOptions options(*value_sp);
     options.SetHideRootType(true)
         .SetHideRootName(true)
         .SetHideName(true)
@@ -274,7 +159,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
       options.SetFormat(m_format_options.GetFormatValue().GetCurrentValue());
 
     StreamString value_stream;
-    if (llvm::Error error = child->Dump(value_stream, options)) {
+    if (llvm::Error error = value_sp->Dump(value_stream, options)) {
       result.AppendError(toString(std::move(error)));
       return false;
     }
@@ -382,35 +267,95 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
         result.AppendError("the --set <set> option can't be used when "
                            "registers names are supplied as arguments\n");
       } else {
-        int reg_name_right_align_at = ComputeLongestRegisterName(
-            command, reg_ctx, !m_command_options.alternate_name);
+        struct RegisterArgument {
+          const RegisterInfo *reg_info = nullptr;
+          ValueObjectSP value_sp;
+          std::string expression_path;
+          std::string error;
+        };
+
+        StackFrame *frame = m_exe_ctx.GetFramePtr();
+        if (!frame) {
+          result.AppendError("no frame");
+          return;
+        }
+        std::vector<RegisterArgument> register_arguments;
+        register_arguments.reserve(command.GetArgumentCount());
+        size_t reg_name_right_align_at = 0;
+
+        for (auto &entry : command) {
+          llvm::StringRef expression = entry.ref();
+          expression.consume_front("$");
+
+          RegisterArgument argument;
+          argument.reg_info = reg_ctx->GetRegisterInfoByName(expression);
+          if (!argument.reg_info) {
+            if (expression.find_first_of(".[") == llvm::StringRef::npos &&
+                !expression.contains("->")) {
+              argument.error =
+                  "Invalid register name '" + expression.str() + "'";
+            } else {
+              std::string variable_path = "$" + expression.str();
+              VariableSP variable_sp;
+              Status error;
+              argument.value_sp = frame->GetValueForVariableExpressionPath(
+                  variable_path, eNoDynamicValues,
+                  StackFrame::eExpressionPathOptionCheckPtrVsMember,
+                  variable_sp, error, eDILModeLegacy);
+              if (error.Fail() || !argument.value_sp) {
+                argument.error = error.AsCString("invalid register path");
+              } else if (ValueObject *root = argument.value_sp->GetRoot()) {
+                argument.reg_info = reg_ctx->GetRegisterInfoByName(
+                    root->GetName().GetStringRef());
+              }
+            }
+          }
+
+          if (!argument.reg_info) {
+            if (argument.error.empty())
+              argument.error =
+                  "Invalid register name '" + expression.str() + "'";
+          } else {
+            if (argument.value_sp) {
+              StreamString expression_stream;
+              argument.value_sp->GetExpressionPath(expression_stream);
+              llvm::StringRef expression_path = expression_stream.GetString();
+              if (!expression_path.consume_front("$") ||
+                  !expression_path.consume_front(argument.reg_info->name)) {
+                argument.error = "unable to reconstruct register path '" +
+                                 expression.str() + "'";
+              } else {
+                argument.expression_path = expression_path.str();
+              }
+            }
+            reg_name_right_align_at =
+                std::max(reg_name_right_align_at,
+                         GetNameSize(argument.reg_info,
+                                     !m_command_options.alternate_name));
+          }
+          register_arguments.push_back(std::move(argument));
+        }
+
         // Extra ident to be consistent with register sets dumping.
         strm.IndentMore();
-        for (auto &entry : command) {
-          // in most LLDB commands we accept $rbx as the name for register RBX
-          // - and here we would reject it and non-existant. we should be more
-          // consistent towards the user and allow them to say reg read $rbx -
-          // internally, however, we should be strict and not allow ourselves
-          // to call our registers $rbx in our own API
-          auto arg_str = entry.ref();
-          arg_str.consume_front("$");
-
-          auto [reg_info, expression_path] =
-              FindRegisterWithExpressionPath(*reg_ctx, arg_str);
-
-          if (reg_info && expression_path.empty()) {
+        for (const RegisterArgument &argument : register_arguments) {
+          if (!argument.error.empty()) {
+            result.AppendError(argument.error);
+            continue;
+          }
+
+          if (!argument.value_sp) {
             // If they have asked for a specific format don't obscure that by
             // printing a structured value afterwards.
             bool print_type = !m_format_options.GetFormatValue().OptionWasSet();
-            if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *reg_info, print_type,
-                              reg_name_right_align_at))
-              strm.Printf("%-12s = error: unavailable\n", reg_info->name);
-          } else if (reg_info) {
-            DumpRegisterField(m_exe_ctx, strm, *reg_info, expression_path,
-                              reg_name_right_align_at, result);
+            if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *argument.reg_info,
+                              print_type, reg_name_right_align_at))
+              strm.Printf("%-12s = error: unavailable\n",
+                          argument.reg_info->name);
           } else {
-            result.AppendErrorWithFormat("Invalid register name '%s'",
-                                         arg_str.str().c_str());
+            DumpRegisterField(strm, *argument.reg_info, argument.value_sp,
+                              argument.expression_path, reg_name_right_align_at,
+                              result);
           }
         }
         strm.IndentLess();
diff --git a/lldb/source/DataFormatters/FormatManager.cpp b/lldb/source/DataFormatters/FormatManager.cpp
index 237ca0347acb3a..e9f0e51e34149d 100644
--- a/lldb/source/DataFormatters/FormatManager.cpp
+++ b/lldb/source/DataFormatters/FormatManager.cpp
@@ -763,15 +763,6 @@ void FormatManager::LoadSystemFormatters() {
   sys_category_sp->AddTypeSummary("OSType", eFormatterMatchExact,
                                   ostype_summary);
 
-  TypeSummaryImpl::Flags register_union_flags;
-  register_union_flags.SetShowMembersOneLiner(true);
-  // RegisterTypeBuilderClang gives synthesized union types this prefix. One
-  // rule covers all of them without accumulating a formatter entry for every
-  // target description LLDB reads.
-  AddStringSummary(sys_category_sp, "",
-                   R"(^__lldb_register_union_[0-9]+_[0-9]+$)",
-                   register_union_flags, true);
-
   TypeFormatImpl::Flags fourchar_flags;
   fourchar_flags.SetCascades(true).SetSkipPointers(true).SetSkipReferences(
       true);
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 67073e151dab92..6acd5b9d89ea40 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -36,6 +36,7 @@
 #include "lldb/ValueObject/DILParser.h"
 #include "lldb/ValueObject/ValueObjectConstResult.h"
 #include "lldb/ValueObject/ValueObjectMemory.h"
+#include "lldb/ValueObject/ValueObjectRegister.h"
 #include "lldb/ValueObject/ValueObjectVariable.h"
 
 #include "lldb/lldb-enumerations.h"
@@ -48,6 +49,33 @@ using namespace lldb_private;
 // LLVM RTTI support.
 char StackFrame::ID;
 
+static std::pair<const RegisterInfo *, llvm::StringRef>
+FindRegisterInExpressionPath(RegisterContext &reg_ctx,
+                             llvm::StringRef expression_path) {
+  // Register names may contain periods. Prefer an exact name, then the
+  // longest name that leaves a member or index expression as the suffix.
+  if (const RegisterInfo *reg_info =
+          reg_ctx.GetRegisterInfoByName(expression_path))
+    return {reg_info, {}};
+
+  size_t search_end = expression_path.size();
+  while (search_end) {
+    llvm::StringRef prefix = expression_path.take_front(search_end);
+    size_t split = prefix.find_last_of(".[");
+    size_t arrow = prefix.rfind("->");
+    if (arrow != llvm::StringRef::npos &&
+        (split == llvm::StringRef::npos || arrow > split))
+      split = arrow;
+    if (split == llvm::StringRef::npos)
+      break;
+    if (const RegisterInfo *reg_info =
+            reg_ctx.GetRegisterInfoByName(expression_path.take_front(split)))
+      return {reg_info, expression_path.drop_front(split)};
+    search_end = split;
+  }
+  return {nullptr, {}};
+}
+
 // The first bits in the flags are reserved for the SymbolContext::Scope bits
 // so we know if we have tried to look up information in our internal symbol
 // context (m_sc) already.
@@ -615,14 +643,6 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
   bool deref = false;
   bool address_of = false;
   ValueObjectSP valobj_sp;
-  const bool get_file_globals = true;
-  // When looking up a variable for an expression, we need only consider the
-  // variables that are in scope.
-  VariableListSP var_list_sp(GetInScopeVariableList(get_file_globals));
-  VariableList *variable_list = var_list_sp.get();
-
-  if (!variable_list)
-    return ValueObjectSP();
 
   // If first character is a '*', then show pointer contents
   std::string var_expr_storage;
@@ -634,20 +654,54 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
     var_expr = var_expr.drop_front(); // Skip the '&'
   }
 
+  bool is_register_path = var_expr.consume_front("$");
+  if (is_register_path)
+    var_sp.reset();
   size_t separator_idx = var_expr.find_first_of(".-[=+~|&^%#@!/?,<>{}");
   StreamString var_expr_path_strm;
 
   ConstString name_const_string(var_expr.substr(0, separator_idx));
+  VariableListSP var_list_sp;
+  VariableList *variable_list = nullptr;
+
+  if (is_register_path) {
+    RegisterContextSP reg_ctx_sp = GetRegisterContextSP();
+    auto [reg_info, register_path] =
+        reg_ctx_sp
+            ? FindRegisterInExpressionPath(*reg_ctx_sp, var_expr)
+            : std::pair<const RegisterInfo *, llvm::StringRef>{nullptr, {}};
+    if (!reg_info) {
+      error = Status::FromErrorStringWithFormat(
+          "no register named '%s' found in this frame",
+          name_const_string.GetCString());
+      return ValueObjectSP();
+    }
+    valobj_sp = ValueObjectRegister::Create(this, reg_ctx_sp, reg_info);
+    if (!valobj_sp) {
+      error = Status::FromErrorStringWithFormat(
+          "unable to read register '%s' in this frame", reg_info->name);
+      return {};
+    }
+    name_const_string = ConstString(reg_info->name);
+    var_expr = register_path;
+  } else {
+    const bool get_file_globals = true;
+    // When looking up a variable for an expression, we need only consider the
+    // variables that are in scope.
+    var_list_sp = GetInScopeVariableList(get_file_globals);
+    variable_list = var_list_sp.get();
+    if (!variable_list)
+      return ValueObjectSP();
 
-  var_sp = variable_list->FindVariable(name_const_string, false);
+    var_sp = variable_list->FindVariable(name_const_string, false);
+    if (var_sp)
+      var_expr = var_expr.drop_front(name_const_string.GetLength());
+  }
 
   bool synthetically_added_instance_object = false;
 
-  if (var_sp) {
-    var_expr = var_expr.drop_front(name_const_string.GetLength());
-  }
-
-  if (!var_sp && (options & eExpressionPathOptionsAllowDirectIVarAccess)) {
+  if (!valobj_sp && !var_sp &&
+      (options & eExpressionPathOptionsAllowDirectIVarAccess)) {
     // Check for direct ivars access which helps us with implicit access to
     // ivars using "this" or "self".
     GetSymbolContext(eSymbolContextFunction | eSymbolContextBlock);
@@ -670,7 +724,8 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
     }
   }
 
-  if (!var_sp && (options & eExpressionPathOptionsInspectAnonymousUnions)) {
+  if (!valobj_sp && !var_sp &&
+      (options & eExpressionPathOptionsInspectAnonymousUnions)) {
     // Check if any anonymous unions are there which contain a variable with
     // the name we need
     for (const VariableSP &variable_sp : *variable_list) {
@@ -862,6 +917,18 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
       if (index_expr.empty()) {
         // The entire index expression was a single integer.
 
+        if (valobj_sp->GetValueType() == eValueTypeRegister &&
+            !valobj_sp->IsPointerType() &&
+            (child_index < 0 ||
+             static_cast<unsigned long>(child_index) > UINT32_MAX)) {
+          valobj_sp->GetExpressionPath(var_expr_path_strm);
+          error = Status::FromErrorStringWithFormat(
+              "array index %ld is not valid for \"(%s) %s\"", child_index,
+              valobj_sp->GetTypeName().AsCString("<invalid type>"),
+              var_expr_path_strm.GetData());
+          return ValueObjectSP();
+        }
+
         if (valobj_sp->GetCompilerType().IsPointerToScalarType() && deref) {
           // what we have is *ptr[low]. the most similar C++ syntax is to deref
           // ptr and extract bit low out of it. reading array item low would be
@@ -962,7 +1029,8 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
             }
           }
         } else if (valobj_sp->GetCompilerType().IsArrayType(
-                       nullptr, nullptr, &is_incomplete_array)) {
+                       nullptr, nullptr, &is_incomplete_array) ||
+                   valobj_sp->GetCompilerType().IsVectorType()) {
           // Pass false to dynamic_value here so we can tell the difference
           // between no dynamic value and no member of this type...
           child_valobj_sp = valobj_sp->GetChildAtIndex(child_index);
@@ -1061,6 +1129,19 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
         final_index = temp;
       }
 
+      if (valobj_sp->GetValueType() == eValueTypeRegister &&
+          !valobj_sp->IsPointerType() &&
+          (child_index < 0 || final_index < 0 ||
+           static_cast<unsigned long>(child_index) > UINT32_MAX ||
+           static_cast<unsigned long>(final_index) > UINT32_MAX)) {
+        valobj_sp->GetExpressionPath(var_expr_path_strm);
+        error = Status::FromErrorStringWithFormat(
+            "bitfield range %ld-%ld is not valid for \"(%s) %s\"", child_index,
+            final_index, valobj_sp->GetTypeName().AsCString("<invalid type>"),
+            var_expr_path_strm.GetData());
+        return {};
+      }
+
       if (valobj_sp->GetCompilerType().IsPointerToScalarType() && deref) {
         // what we have is *ptr[low-high]. the most similar C++ syntax is to
         // deref ptr and extract bits low thru high out of it. reading array
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index 81c5e383312fe1..091257e343f166 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -23,6 +23,7 @@
 #include "llvm/Support/ErrorExtras.h"
 #include "llvm/Support/FormatAdapters.h"
 #include <memory>
+#include <optional>
 
 namespace lldb_private::dil {
 
@@ -57,16 +58,48 @@ GetSourceLanguageFromCU(StackFrame &ctx) {
   return symbol_context.comp_unit->GetLanguage();
 }
 
-static llvm::Expected<lldb::TypeSystemSP> GetTypeSystemFromCU(StackFrame &ctx) {
+static llvm::Expected<lldb::TypeSystemSP>
+GetTypeSystemForFrame(StackFrame &ctx) {
   SymbolContext symbol_context =
       ctx.GetSymbolContext(lldb::eSymbolContextCompUnit);
-  if (!symbol_context.comp_unit)
-    return llvm::createStringErrorV("no compile unit for frame: {}",
-                                    ctx.GetFunctionName());
+  lldb::LanguageType language = lldb::eLanguageTypeUnknown;
+  if (symbol_context.comp_unit) {
+    language = symbol_context.comp_unit->GetLanguage();
+    symbol_context = ctx.GetSymbolContext(lldb::eSymbolContextModule);
+    if (symbol_context.module_sp)
+      return symbol_context.module_sp->GetTypeSystemForLanguage(language);
+  }
+
+  lldb::TargetSP target_sp = ctx.CalculateTarget();
+  if (!target_sp)
+    return llvm::createStringError("no target for frame");
+  if (language == lldb::eLanguageTypeUnknown)
+    language = target_sp->GetLanguage().AsLanguageType();
+  return target_sp->GetScratchTypeSystemForLanguage(language);
+}
 
-  lldb::LanguageType language = symbol_context.comp_unit->GetLanguage();
-  symbol_context = ctx.GetSymbolContext(lldb::eSymbolContextModule);
-  return symbol_context.module_sp->GetTypeSystemForLanguage(language);
+// DIL normally treats '.' as member access, but register names may contain
+// periods. Reconstruct the candidate so an exact register name takes
+// precedence.
+static std::optional<std::string> GetRegisterPathName(const ASTNode &node) {
+  if (node.GetKind() == NodeKind::eIdentifierNode) {
+    std::string name_storage =
+        static_cast<const IdentifierNode &>(node).GetName();
+    llvm::StringRef name = name_storage;
+    if (name.consume_front("$"))
+      return name.str();
+    return std::nullopt;
+  }
+
+  if (node.GetKind() != NodeKind::eMemberOfNode)
+    return std::nullopt;
+  const auto &member = static_cast<const MemberOfNode &>(node);
+  if (member.GetIsArrow())
+    return std::nullopt;
+  std::optional<std::string> base_name = GetRegisterPathName(member.GetBase());
+  if (!base_name)
+    return std::nullopt;
+  return *base_name + "." + member.GetFieldName().str();
 }
 
 llvm::Expected<lldb::ValueObjectSP>
@@ -75,7 +108,7 @@ Interpreter::UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location) {
     return llvm::make_error<DILDiagnosticError>(m_expr, "invalid value object",
                                                 location);
   llvm::Expected<lldb::TypeSystemSP> type_system =
-      GetTypeSystemFromCU(m_stack_frame);
+      GetTypeSystemForFrame(m_stack_frame);
   if (!type_system)
     return type_system.takeError();
 
@@ -214,7 +247,7 @@ Interpreter::PromoteSignedInteger(CompilerType &lhs_type,
 
     if (*rhs_size == *lhs_size) {
       llvm::Expected<lldb::TypeSystemSP> type_system =
-          GetTypeSystemFromCU(m_stack_frame);
+          GetTypeSystemForFrame(m_stack_frame);
       if (!type_system)
         return type_system.takeError();
       CompilerType r_type_unsigned = GetBasicType(
@@ -529,7 +562,7 @@ Interpreter::Visit(const IdentifierNode &node) {
     // If we got a "nullptr" identifier, and there is no defined variable with
     // this name, resolve it as a null pointer.
     llvm::Expected<lldb::TypeSystemSP> type_system =
-        GetTypeSystemFromCU(m_stack_frame);
+        GetTypeSystemForFrame(m_stack_frame);
     if (!type_system)
       return type_system.takeError();
     type_system.get()->GetPointerByteSize();
@@ -689,7 +722,7 @@ Interpreter::Visit(const UnaryOpNode &node) {
                                                   node.GetLocation());
     }
     llvm::Expected<lldb::TypeSystemSP> type_system =
-        GetTypeSystemFromCU(m_stack_frame);
+        GetTypeSystemForFrame(m_stack_frame);
     if (!type_system)
       return type_system.takeError();
     auto value_or_err = operand->GetValueAsBool();
@@ -897,7 +930,7 @@ llvm::Expected<lldb::ValueObjectSP> Interpreter::EvaluateBinarySubtraction(
     diff /= item_size;
 
     llvm::Expected<lldb::TypeSystemSP> type_system =
-        GetTypeSystemFromCU(m_stack_frame);
+        GetTypeSystemForFrame(m_stack_frame);
     if (!type_system)
       return type_system.takeError();
     CompilerType ptrdiff_type = type_system.get()->GetPointerDiffType(true);
@@ -1219,7 +1252,7 @@ Interpreter::EvaluateComparison(BinaryOpKind kind, lldb::ValueObjectSP lhs,
     return error;
 
   llvm::Expected<lldb::TypeSystemSP> type_system =
-      GetTypeSystemFromCU(m_stack_frame);
+      GetTypeSystemForFrame(m_stack_frame);
   if (!type_system)
     return type_system.takeError();
   CompilerType boolean_type = GetBasicType(*type_system, lldb::eBasicTypeBool);
@@ -1308,7 +1341,7 @@ Interpreter::EvaluateLogical(const BinaryOpNode &node) {
                                                 node.GetLocation());
   }
   llvm::Expected<lldb::TypeSystemSP> type_system =
-      GetTypeSystemFromCU(m_stack_frame);
+      GetTypeSystemForFrame(m_stack_frame);
   if (!type_system)
     return type_system.takeError();
 
@@ -1476,6 +1509,16 @@ Interpreter::Visit(const BinaryOpNode &node) {
 
 llvm::Expected<lldb::ValueObjectSP>
 Interpreter::Visit(const MemberOfNode &node) {
+  if (std::optional<std::string> register_name = GetRegisterPathName(node)) {
+    lldb::RegisterContextSP reg_ctx_sp = m_stack_frame.GetRegisterContextSP();
+    if (reg_ctx_sp) {
+      if (const RegisterInfo *reg_info =
+              reg_ctx_sp->GetRegisterInfoByName(*register_name))
+        return ValueObjectRegister::Create(&m_stack_frame, reg_ctx_sp,
+                                           reg_info);
+    }
+  }
+
   auto base_or_err = Evaluate(node.GetBase());
   if (!base_or_err)
     return base_or_err;
@@ -1593,6 +1636,17 @@ Interpreter::Visit(const ArraySubscriptNode &node) {
 
   CompilerType base_type = base->GetCompilerType().GetNonReferenceType();
   base->GetExpressionPath(var_expr_path_strm);
+  if (base->GetValueType() == lldb::eValueTypeRegister &&
+      !base_type.IsPointerType() &&
+      child_idx > std::numeric_limits<uint32_t>::max()) {
+    std::string err_msg = llvm::formatv(
+        "array index {0} is not valid for \"({1}) {2}\"", child_idx,
+        base->GetTypeName().AsCString("<invalid type>"),
+        var_expr_path_strm.GetData());
+    return llvm::make_error<DILDiagnosticError>(m_expr, std::move(err_msg),
+                                                node.GetLocation());
+  }
+
   bool is_incomplete_array = false;
   if (base_type.IsPointerType()) {
     bool is_objc_pointer = true;
@@ -1657,7 +1711,8 @@ Interpreter::Visit(const ArraySubscriptNode &node) {
       return llvm::make_error<DILDiagnosticError>(m_expr, std::move(err_msg),
                                                   node.GetLocation());
     }
-  } else if (base_type.IsArrayType(nullptr, nullptr, &is_incomplete_array)) {
+  } else if (base_type.IsArrayType(nullptr, nullptr, &is_incomplete_array) ||
+             base_type.IsVectorType()) {
     child_valobj_sp = base->GetChildAtIndex(child_idx);
     if (!child_valobj_sp && (is_incomplete_array || m_use_synthetic))
       child_valobj_sp = base->GetSyntheticArrayMember(child_idx, true);
@@ -1852,7 +1907,7 @@ Interpreter::PickIntegerType(lldb::TypeSystemSP type_system,
 llvm::Expected<lldb::ValueObjectSP>
 Interpreter::Visit(const IntegerLiteralNode &node) {
   llvm::Expected<lldb::TypeSystemSP> type_system =
-      GetTypeSystemFromCU(m_stack_frame);
+      GetTypeSystemForFrame(m_stack_frame);
   if (!type_system)
     return type_system.takeError();
 
@@ -1879,7 +1934,7 @@ Interpreter::Visit(const IntegerLiteralNode &node) {
 llvm::Expected<lldb::ValueObjectSP>
 Interpreter::Visit(const FloatLiteralNode &node) {
   llvm::Expected<lldb::TypeSystemSP> type_system =
-      GetTypeSystemFromCU(m_stack_frame);
+      GetTypeSystemForFrame(m_stack_frame);
   if (!type_system)
     return type_system.takeError();
 
@@ -1902,7 +1957,7 @@ llvm::Expected<lldb::ValueObjectSP>
 Interpreter::Visit(const BooleanLiteralNode &node) {
   bool value = node.GetValue();
   llvm::Expected<lldb::TypeSystemSP> type_system =
-      GetTypeSystemFromCU(m_stack_frame);
+      GetTypeSystemForFrame(m_stack_frame);
   if (!type_system)
     return type_system.takeError();
   return ValueObject::CreateValueObjectFromBool(m_stack_frame, *type_system,
@@ -2159,7 +2214,7 @@ llvm::Expected<lldb::ValueObjectSP> Interpreter::Visit(const SizeOfNode &node) {
   }
 
   llvm::Expected<lldb::TypeSystemSP> type_system =
-      GetTypeSystemFromCU(m_stack_frame);
+      GetTypeSystemForFrame(m_stack_frame);
   if (!type_system)
     return type_system.takeError();
   CompilerType size_type = type_system.get()->GetSizeType();
diff --git a/lldb/source/ValueObject/ValueObject.cpp b/lldb/source/ValueObject/ValueObject.cpp
index 989ad64a9a65c0..dc622cd6d13669 100644
--- a/lldb/source/ValueObject/ValueObject.cpp
+++ b/lldb/source/ValueObject/ValueObject.cpp
@@ -1898,6 +1898,14 @@ bool ValueObject::IsUninitializedReference() {
 
 ValueObjectSP ValueObject::GetSyntheticArrayMember(size_t index,
                                                    bool can_create) {
+  // Register values use exact-size host buffers. Do not create array children
+  // that would read beyond the bytes supplied by the register context.
+  uint64_t array_size = 0;
+  if (GetValueType() == eValueTypeRegister &&
+      GetCompilerType().IsArrayType(nullptr, &array_size) &&
+      index >= array_size)
+    return {};
+
   ValueObjectSP synthetic_child_sp;
   if (IsPointerType() || IsArrayType()) {
     std::string index_str = llvm::formatv("[{0}]", index);
@@ -1925,6 +1933,16 @@ ValueObjectSP ValueObject::GetSyntheticArrayMember(size_t index,
 
 ValueObjectSP ValueObject::GetSyntheticBitFieldChild(uint32_t from, uint32_t to,
                                                      bool can_create) {
+  if (from > to)
+    return {};
+  // Register children must stay within the exact bytes supplied by the
+  // register context.
+  if (GetValueType() == eValueTypeRegister) {
+    std::optional<uint64_t> byte_size = llvm::expectedToOptional(GetByteSize());
+    if (!byte_size || to / 8 >= *byte_size)
+      return {};
+  }
+
   ValueObjectSP synthetic_child_sp;
   if (IsScalarType()) {
     std::string index_str = llvm::formatv("[{0}-{1}]", from, to);
@@ -2251,7 +2269,8 @@ void ValueObject::GetExpressionPath(Stream &s,
             if (non_base_class_parent_type_info & eTypeIsPointer) {
               s.PutCString("->");
             } else if ((non_base_class_parent_type_info & eTypeHasChildren) &&
-                       !(non_base_class_parent_type_info & eTypeIsArray)) {
+                       !(non_base_class_parent_type_info &
+                         (eTypeIsArray | eTypeIsVector))) {
               s.PutChar('.');
             }
           }
@@ -2524,6 +2543,13 @@ ValueObjectSP ValueObject::GetValueForExpressionPath_Impl(
           return ValueObjectSP();
         }
       }
+      if (temp_expression.size() == 1) {
+        *reason_to_stop =
+            ValueObject::eExpressionPathScanEndReasonUnexpectedSymbol;
+        *final_result = ValueObject::eExpressionPathEndResultTypeInvalid;
+        return nullptr;
+      }
+
       if (temp_expression[1] ==
           ']') // if this is an unbounded range it only works for arrays
       {
@@ -2571,6 +2597,14 @@ ValueObjectSP ValueObject::GetValueForExpressionPath_Impl(
           *final_result = ValueObject::eExpressionPathEndResultTypeInvalid;
           return nullptr;
         }
+        if (root->GetValueType() == eValueTypeRegister &&
+            !root_compiler_type_info.Test(eTypeIsPointer) &&
+            index > UINT32_MAX) {
+          *reason_to_stop =
+              ValueObject::eExpressionPathScanEndReasonNoSuchChild;
+          *final_result = ValueObject::eExpressionPathEndResultTypeInvalid;
+          return nullptr;
+        }
 
         // from here on we do have a valid index
         if (root_compiler_type_info.Test(eTypeIsArray)) {
@@ -2732,6 +2766,14 @@ ValueObjectSP ValueObject::GetValueForExpressionPath_Impl(
         if (low_index > high_index) // swap indices if required
           std::swap(low_index, high_index);
 
+        if (root->GetValueType() == eValueTypeRegister &&
+            high_index > UINT32_MAX) {
+          *reason_to_stop =
+              ValueObject::eExpressionPathScanEndReasonNoSuchChild;
+          *final_result = ValueObject::eExpressionPathEndResultTypeInvalid;
+          return nullptr;
+        }
+
         if (root_compiler_type_info.Test(
                 eTypeIsScalar)) // expansion only works for scalars
         {
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index af15fec036b7f3..9b299ff4d51ca3 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -473,7 +473,7 @@ def test_big_endian_union_sb_api(self):
 
     @skipIfXmlSupportMissing
     @skipIfRemote
-    def test_union_cli_summary_and_member_paths(self):
+    def test_union_cli_and_member_paths(self):
         process = self.setup_register_test(
             """\
             <vector id="v4f" type="ieee_single" count="4"/>
@@ -490,18 +490,25 @@ def test_union_cli_summary_and_member_paths(self):
               <field name="view" type="vector_views"/>
               <field name="raw" type="uint128"/>
             </union>
+            <union id="index_view">
+              <field name="value" type="uint64"/>
+            </union>
             <reg name="u0" altname="alt_u0" regnum="0" bitsize="64" type="views"/>
             <reg name="u1" regnum="1" bitsize="128" type="vector_views"/>
             <reg name="n0" regnum="2" bitsize="128" type="nested"/>
+            <reg name="idx" regnum="3" bitsize="64" type="index_view"/>
             <reg name="pc" bitsize="64"/>""",
-            "0000c03fffffffff" + "0000c03f000020400000604000009040" * 2 + "00" * 8,
+            "0000c03fffffffff"
+            + "0000c03f000020400000604000009040" * 2
+            + "0200000000000000"
+            + "00" * 8,
         )
 
         union = process.GetThreadAtIndex(0).GetFrameAtIndex(0).FindRegister("u0")
-        self.assertEqual(
-            union.GetSummary(),
-            "(f32 = 1.5, f64 = NaN, u64 = 18446744070484131840)",
-        )
+        self.assertIsNone(union.GetSummary())
+        description = lldb.SBStream()
+        self.assertTrue(union.GetDescription(description))
+        self.assertIn("f32 = 1.5", description.GetData())
 
         self.expect(
             "register read u0",
@@ -511,6 +518,7 @@ def test_union_cli_summary_and_member_paths(self):
             ],
         )
         self.expect("register read u0.f32", substrs=["u0.f32 = 1.5"])
+        self.expect("register read U0.f32", substrs=["u0.f32 = 1.5"])
         self.expect(
             "register read u0.u64 --format X",
             substrs=["u0.u64 = 0xFFFFFFFF3FC00000"],
@@ -523,15 +531,60 @@ def test_union_cli_summary_and_member_paths(self):
         self.expect("register read -A alt_u0.f32", substrs=["alt_u0.f32 = 1.5"])
         self.expect("register read $u0.f64", substrs=["u0.f64 = NaN"])
         self.expect("register read u1.floats[2]", substrs=["u1.floats[2] = 3.5"])
+        self.expect(
+            "register read u1.floats[$idx.value]",
+            substrs=["u1.floats[2] = 3.5"],
+        )
+        self.expect(
+            "register read u0 u1.floats[2]",
+            substrs=["u0 = 0xffffffff3fc00000", "u1.floats[2] = 3.5"],
+        )
         self.expect(
             "register read n0.view.floats[3]",
             substrs=["n0.view.floats[3] = 4.5"],
         )
 
+        frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+        lane = frame.FindRegister("u1").GetValueForExpressionPath(".floats[2]")
+        expression_path = lldb.SBStream()
+        self.assertTrue(lane.GetExpressionPath(expression_path))
+        self.assertEqual(expression_path.GetData(), "$u1.floats[2]")
+        self.assertEqual(lane.GetValueType(), lldb.eValueTypeRegister)
+
+        resolved_lane = frame.GetValueForVariablePath("$u1.floats[2]")
+        self.assertTrue(resolved_lane.IsValid())
+        self.assertEqual(resolved_lane.GetValue(), "3.5")
+        self.assertEqual(resolved_lane.GetValueType(), lldb.eValueTypeRegister)
+
+        self.expect(
+            "v $u0",
+            substrs=[
+                "u0 = (f32 = 1.5",
+                "f64 = NaN",
+                "u64 = 18446744070484131840)",
+            ],
+        )
+        self.expect("v $u0.f32", substrs=["(float) $u0.f32 = 1.5"])
+        self.expect("v $u1.floats[2]", substrs=["(float) $u1.floats[2] = 3.5"])
+        self.expect(
+            "v $u1.floats[$idx.value]",
+            substrs=["(float) $u1.floats[$idx.value] = 3.5"],
+        )
+
+        self.runCmd("settings set target.experimental.use-DIL false")
+        try:
+            self.expect("register read u1.floats[2]", substrs=["u1.floats[2] = 3.5"])
+            self.expect("v $u1.floats[2]", substrs=["(float) $u1.floats[2] = 3.5"])
+            resolved_lane = frame.GetValueForVariablePath("$u1.floats[2]")
+            self.assertTrue(resolved_lane.IsValid())
+            self.assertEqual(resolved_lane.GetValue(), "3.5")
+        finally:
+            self.runCmd("settings set target.experimental.use-DIL true")
+
     @skipIfXmlSupportMissing
     @skipIfRemote
     def test_exact_dotted_register_name_takes_precedence(self):
-        self.setup_register_test(
+        process = self.setup_register_test(
             """\
             <union id="views">
               <field name="f32" type="ieee_single"/>
@@ -546,36 +599,68 @@ def test_exact_dotted_register_name_takes_precedence(self):
 
         self.expect("register read u0.f32", substrs=["u0.f32 = 0x0000002a"])
         self.expect("register read u0.view.f32", substrs=["u0.view.f32 = 1.5"])
+        self.expect("v $u0.f32", substrs=["u0.f32 = 0x0000002a"])
+        self.expect("v $u0.view.f32", substrs=["(float) $u0.view.f32 = 1.5"])
+
+        frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+        self.assertEqual(
+            frame.GetValueForVariablePath("$u0.f32").GetValue(), "0x0000002a"
+        )
+        self.assertEqual(
+            frame.GetValueForVariablePath("$u0.view.f32").GetValue(), "1.5"
+        )
+
+        self.runCmd("settings set target.experimental.use-DIL false")
+        try:
+            self.expect("register read u0.f32", substrs=["u0.f32 = 0x0000002a"])
+            self.expect("register read u0.view.f32", substrs=["u0.view.f32 = 1.5"])
+            self.expect("v $u0.view.f32", substrs=["(float) $u0.view.f32 = 1.5"])
+            self.assertEqual(
+                frame.GetValueForVariablePath("$u0.f32").GetValue(),
+                "0x0000002a",
+            )
+            self.assertEqual(
+                frame.GetValueForVariablePath("$u0.view.f32").GetValue(),
+                "1.5",
+            )
+        finally:
+            self.runCmd("settings set target.experimental.use-DIL true")
 
     @skipIfXmlSupportMissing
     @skipIfRemote
     def test_invalid_union_member_paths(self):
-        self.setup_register_test(
+        process = self.setup_register_test(
             """\
             <union id="views">
               <field name="f32" type="ieee_single"/>
               <field name="raw" type="uint64"/>
             </union>
             <vector id="v2views" type="views" count="2"/>
+            <vector id="v2f" type="ieee_single" count="2"/>
+            <union id="vector_view">
+              <field name="elements" type="v2views"/>
+            </union>
             <reg name="u0" regnum="0" bitsize="64" type="views"/>
             <reg name="v0" regnum="1" bitsize="128" type="v2views"/>
-            <reg name="pc" regnum="2" bitsize="64"/>""",
-            "00" * 32,
+            <reg name="n0" regnum="2" bitsize="128" type="vector_view"/>
+            <reg name="vf" regnum="3" bitsize="64" type="v2f"/>
+            <reg name="pc" regnum="4" bitsize="64"/>""",
+            "0000000001000000" + "00" * 48,
         )
 
         invalid_paths = {
-            "u0.missing": "No field path 'missing' in register 'u0'",
-            "u0.": "No field path '' in register 'u0'",
-            "u0..f32": "No field path '.f32' in register 'u0'",
-            "u0[0]": "No field path '[0]' in register 'u0'",
-            "v0[": "No field path '[' in register 'v0'",
-            "v0[0": "No field path '[0' in register 'v0'",
-            "v0[]": "No field path '[]' in register 'v0'",
-            "v0[x]": "No field path '[x]' in register 'v0'",
-            "v0[-1]": "No field path '[-1]' in register 'v0'",
-            "v0[4294967296]": "No field path '[4294967296]' in register 'v0'",
-            "v0[0]junk": "No field path '[0]junk' in register 'v0'",
-            "v0[0].missing": "No field path '[0].missing' in register 'v0'",
+            "u0.missing": '"missing" is not a member',
+            "u0.": '"" is not a member',
+            "u0..f32": '"" is not a member',
+            "u0[0]": "is not an array type",
+            "v0[": "Unexpected token",
+            "v0[0": "expected 'r_square'",
+            "v0[]": "Unexpected token",
+            "v0[x]": "use of undeclared identifier 'x'",
+            "v0[-1]": "is not allowed in DIL legacy mode",
+            "v0[4294967296]": "array index 4294967296 is not valid",
+            "v0[0]junk": "expected 'eof'",
+            "v0[0].missing": '"missing" is not a member',
         }
         for path, diagnostic in invalid_paths.items():
             self.expect(
@@ -586,13 +671,85 @@ def test_invalid_union_member_paths(self):
         self.expect(
             "register read v0[9].f32",
             error=True,
-            substrs=["No field path '[9].f32'"],
+            substrs=["array index 9 is not valid"],
         )
         self.expect(
             "register read pc.field",
             error=True,
-            substrs=["Register 'pc' does not have a structured type"],
+            substrs=['"field" is not a member'],
+        )
+        self.expect(
+            "register read pc->field",
+            error=True,
+            substrs=["member reference type"],
         )
+        self.expect(
+            "register read does_not_exist",
+            error=True,
+            substrs=["Invalid register name 'does_not_exist'"],
+        )
+
+        frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
+        vector = frame.FindRegister("v0")
+        self.assertTrue(vector.GetType().IsArrayType())
+        self.assertEqual(vector.GetValueType(), lldb.eValueTypeRegister)
+        self.assertEqual(
+            vector.GetChildAtIndex(0).GetValueType(), lldb.eValueTypeRegister
+        )
+        self.assertTrue(
+            vector.GetChildAtIndex(1, lldb.eNoDynamicValues, True).IsValid()
+        )
+        self.assertFalse(
+            vector.GetChildAtIndex(9, lldb.eNoDynamicValues, True).IsValid()
+        )
+        self.assertFalse(vector.GetValueForExpressionPath("[9].f32").IsValid())
+        self.assertFalse(vector.GetValueForExpressionPath("[4294967296]").IsValid())
+
+        scalar = frame.FindRegister("u0").GetChildMemberWithName("raw")
+        self.assertTrue(scalar.GetValueForExpressionPath("[63]").IsValid())
+        self.assertFalse(scalar.GetValueForExpressionPath("[64]").IsValid())
+        self.assertFalse(scalar.GetValueForExpressionPath("[64-64]").IsValid())
+        for command in ["register read u0.raw[64]", "v $u0.raw[64]"]:
+            self.expect(command, error=True, substrs=["bitfield range 64"])
+
+        nested_vector = frame.FindRegister("n0").GetChildMemberWithName("elements")
+        self.assertEqual(nested_vector.GetValueType(), lldb.eValueTypeRegister)
+        self.assertFalse(nested_vector.GetValueForExpressionPath("[9].f32").IsValid())
+        native_vector = frame.FindRegister("vf")
+        self.assertTrue(native_vector.GetType().IsVectorType())
+        self.assertEqual(native_vector.GetValueType(), lldb.eValueTypeRegister)
+        self.assertEqual(
+            native_vector.GetChildAtIndex(0).GetValueType(),
+            lldb.eValueTypeRegister,
+        )
+        self.expect(
+            "v $v0[$u0.raw].f32",
+            error=True,
+            substrs=["array index 4294967296 is not valid"],
+        )
+        self.expect(
+            "v $vf[$u0.raw]",
+            error=True,
+            substrs=["array index 4294967296 is not valid"],
+        )
+
+        self.runCmd("settings set target.experimental.use-DIL false")
+        try:
+            for command in [
+                "register read v0[9].f32",
+                "register read v0[4294967296]",
+                "v $v0[9].f32",
+                "v $v0[4294967296]",
+            ]:
+                self.expect(command, error=True, substrs=["array index"])
+            for command in [
+                "register read u0.raw[64]",
+                "register read u0.raw[64-64]",
+                "v $u0.raw[64]",
+            ]:
+                self.expect(command, error=True, substrs=["bitfield range 64"])
+        finally:
+            self.runCmd("settings set target.experimental.use-DIL true")
 
     @skipIfXmlSupportMissing
     @skipIfRemote
@@ -624,6 +781,8 @@ def test_big_endian_union_cli(self):
             "register read u0",
             substrs=[
                 "u0 = 0x3fc0000040200000",
-                "     = (scalar = 1.5, lanes = (1.5, 2.5))",
+                "     = {",
+                "scalar = 1.5",
+                "lanes = ([0] = 1.5, [1] = 2.5)",
             ],
         )

>From 8afdf6be6f93237b17683176ffa7bcbc0bb5ef1b Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Mon, 5 Oct 2026 13:29:14 -0700
Subject: [PATCH 06/11] address greg's comment

---
 .../source/Commands/CommandObjectRegister.cpp | 52 +++++++++----------
 1 file changed, 26 insertions(+), 26 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index cb9b26c3507bcc..1be96256aa1a15 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -288,27 +288,23 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
           expression.consume_front("$");
 
           RegisterArgument argument;
-          argument.reg_info = reg_ctx->GetRegisterInfoByName(expression);
-          if (!argument.reg_info) {
+          std::string variable_path = "$" + expression.str();
+          VariableSP variable_sp;
+          Status error;
+          argument.value_sp = frame->GetValueForVariableExpressionPath(
+              variable_path, eNoDynamicValues,
+              StackFrame::eExpressionPathOptionCheckPtrVsMember, variable_sp,
+              error, eDILModeLegacy);
+          if (error.Fail() || !argument.value_sp) {
             if (expression.find_first_of(".[") == llvm::StringRef::npos &&
-                !expression.contains("->")) {
+                !expression.contains("->"))
               argument.error =
                   "Invalid register name '" + expression.str() + "'";
-            } else {
-              std::string variable_path = "$" + expression.str();
-              VariableSP variable_sp;
-              Status error;
-              argument.value_sp = frame->GetValueForVariableExpressionPath(
-                  variable_path, eNoDynamicValues,
-                  StackFrame::eExpressionPathOptionCheckPtrVsMember,
-                  variable_sp, error, eDILModeLegacy);
-              if (error.Fail() || !argument.value_sp) {
-                argument.error = error.AsCString("invalid register path");
-              } else if (ValueObject *root = argument.value_sp->GetRoot()) {
-                argument.reg_info = reg_ctx->GetRegisterInfoByName(
-                    root->GetName().GetStringRef());
-              }
-            }
+            else
+              argument.error = error.AsCString("invalid register path");
+          } else if (ValueObject *root = argument.value_sp->GetRoot()) {
+            argument.reg_info =
+                reg_ctx->GetRegisterInfoByName(root->GetName().GetStringRef());
           }
 
           if (!argument.reg_info) {
@@ -317,15 +313,19 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
                   "Invalid register name '" + expression.str() + "'";
           } else {
             if (argument.value_sp) {
-              StreamString expression_stream;
-              argument.value_sp->GetExpressionPath(expression_stream);
-              llvm::StringRef expression_path = expression_stream.GetString();
-              if (!expression_path.consume_front("$") ||
-                  !expression_path.consume_front(argument.reg_info->name)) {
-                argument.error = "unable to reconstruct register path '" +
-                                 expression.str() + "'";
+              if (argument.value_sp.get() == argument.value_sp->GetRoot()) {
+                argument.value_sp.reset();
               } else {
-                argument.expression_path = expression_path.str();
+                StreamString expression_stream;
+                argument.value_sp->GetExpressionPath(expression_stream);
+                llvm::StringRef expression_path = expression_stream.GetString();
+                if (!expression_path.consume_front("$") ||
+                    !expression_path.consume_front(argument.reg_info->name)) {
+                  argument.error = "unable to reconstruct register path '" +
+                                   expression.str() + "'";
+                } else {
+                  argument.expression_path = expression_path.str();
+                }
               }
             }
             reg_name_right_align_at =

>From 57651afe1db3a815dc98f7a0e496aea645f3753d Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 6 Oct 2026 11:28:34 -0700
Subject: [PATCH 07/11] adding greg's suggestions

---
 .../source/Commands/CommandObjectRegister.cpp | 57 +++++++++----------
 lldb/source/ValueObject/DILEval.cpp           | 10 ++--
 .../gdb_remote_client/TestXMLRegisterUnion.py |  2 +-
 3 files changed, 34 insertions(+), 35 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 1be96256aa1a15..9060fea199f4b5 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -147,7 +147,6 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
 
   bool DumpRegisterField(Stream &strm, const RegisterInfo &reg_info,
                          const ValueObjectSP &value_sp,
-                         llvm::StringRef expression_path,
                          size_t reg_name_right_align_at,
                          CommandReturnObject &result) {
     DumpValueObjectOptions options(*value_sp);
@@ -170,9 +169,21 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
         m_command_options.alternate_name && reg_info.alt_name
             ? reg_info.alt_name
             : reg_info.name;
+
+    StreamString expression_stream;
+    value_sp->GetExpressionPath(expression_stream);
+    llvm::StringRef expression = expression_stream.GetString();
+    expression.consume_front("$");
+    llvm::StringRef expression_path = expression;
+
     strm.Indent();
-    strm.Printf("%*s%s = ", static_cast<int>(reg_name_right_align_at),
-                register_name, expression_path.str().c_str());
+    // Keep the resolved suffix while allowing an alternate register name.
+    if (expression_path.consume_front(reg_info.name))
+      strm.Printf("%*s%s = ", static_cast<int>(reg_name_right_align_at),
+                  register_name, expression_path.str().c_str());
+    else
+      strm.Printf("%*s = ", static_cast<int>(reg_name_right_align_at),
+                  expression.str().c_str());
     strm << value << '\n';
     return true;
   }
@@ -270,7 +281,6 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
         struct RegisterArgument {
           const RegisterInfo *reg_info = nullptr;
           ValueObjectSP value_sp;
-          std::string expression_path;
           std::string error;
         };
 
@@ -295,14 +305,12 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
               variable_path, eNoDynamicValues,
               StackFrame::eExpressionPathOptionCheckPtrVsMember, variable_sp,
               error, eDILModeLegacy);
-          if (error.Fail() || !argument.value_sp) {
-            if (expression.find_first_of(".[") == llvm::StringRef::npos &&
-                !expression.contains("->"))
-              argument.error =
-                  "Invalid register name '" + expression.str() + "'";
-            else
-              argument.error = error.AsCString("invalid register path");
-          } else if (ValueObject *root = argument.value_sp->GetRoot()) {
+          if (error.Fail())
+            argument.error = error.AsCString();
+          else if (!argument.value_sp)
+            argument.error =
+                "Invalid register expression '" + expression.str() + "'";
+          else if (ValueObject *root = argument.value_sp->GetRoot()) {
             argument.reg_info =
                 reg_ctx->GetRegisterInfoByName(root->GetName().GetStringRef());
           }
@@ -310,23 +318,13 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
           if (!argument.reg_info) {
             if (argument.error.empty())
               argument.error =
-                  "Invalid register name '" + expression.str() + "'";
+                  "Invalid register expression '" + expression.str() + "'";
           } else {
-            if (argument.value_sp) {
-              if (argument.value_sp.get() == argument.value_sp->GetRoot()) {
-                argument.value_sp.reset();
-              } else {
-                StreamString expression_stream;
-                argument.value_sp->GetExpressionPath(expression_stream);
-                llvm::StringRef expression_path = expression_stream.GetString();
-                if (!expression_path.consume_front("$") ||
-                    !expression_path.consume_front(argument.reg_info->name)) {
-                  argument.error = "unable to reconstruct register path '" +
-                                   expression.str() + "'";
-                } else {
-                  argument.expression_path = expression_path.str();
-                }
-              }
+            if (argument.value_sp &&
+                argument.value_sp.get() == argument.value_sp->GetRoot()) {
+              // Whole registers use DumpRegister below. Only descendants use
+              // DumpRegisterField.
+              argument.value_sp.reset();
             }
             reg_name_right_align_at =
                 std::max(reg_name_right_align_at,
@@ -354,8 +352,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
                           argument.reg_info->name);
           } else {
             DumpRegisterField(strm, *argument.reg_info, argument.value_sp,
-                              argument.expression_path, reg_name_right_align_at,
-                              result);
+                              reg_name_right_align_at, result);
           }
         }
         strm.IndentLess();
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index 091257e343f166..4923dd79bb2625 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -552,10 +552,12 @@ Interpreter::Visit(const IdentifierNode &node) {
 
   if (!identifier && node.GetName()[0] == '$') {
     auto language = GetSourceLanguageFromCU(m_stack_frame);
-    if (!language)
-      return language.takeError();
-    identifier = LookupPersistentIdentifier(node.GetName(), m_stack_frame,
-                                            m_target, language.get());
+    if (language)
+      identifier = LookupPersistentIdentifier(node.GetName(), m_stack_frame,
+                                              m_target, language.get());
+    else
+      // Fall through to the unknown identifier diagnostic without a CU.
+      llvm::consumeError(language.takeError());
   }
 
   if (!identifier && node.GetName() == "nullptr") {
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index 9b299ff4d51ca3..e8314f70a35d51 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -686,7 +686,7 @@ def test_invalid_union_member_paths(self):
         self.expect(
             "register read does_not_exist",
             error=True,
-            substrs=["Invalid register name 'does_not_exist'"],
+            substrs=["use of undeclared identifier '$does_not_exist'"],
         )
 
         frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)

>From e3b9ba2e0dea2c804cecf174be5a2a81f77e031b Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Tue, 6 Oct 2026 15:34:31 -0700
Subject: [PATCH 08/11] Update register error expectations

---
 .../register/register_command/TestRegisters.py         |  4 ++--
 .../gdb_remote_client/TestGDBServerTargetXML.py        |  8 +++++---
 .../gdb_remote_client/TestXMLRegisterUnion.py          | 10 ++++++++++
 3 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/lldb/test/API/commands/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register_command/TestRegisters.py
index b77a5e8794a15d..8c16cd4d748f80 100644
--- a/lldb/test/API/commands/register/register_command/TestRegisters.py
+++ b/lldb/test/API/commands/register/register_command/TestRegisters.py
@@ -472,7 +472,7 @@ def fp_register_write(self):
             else:
                 self.expect(
                     "register read ymm0",
-                    substrs=["Invalid register name 'ymm0'"],
+                    substrs=["use of undeclared identifier '$ymm0'"],
                     error=True,
                 )
 
@@ -493,7 +493,7 @@ def fp_register_write(self):
             else:
                 self.expect(
                     "register read bnd0",
-                    substrs=["Invalid register name 'bnd0'"],
+                    substrs=["use of undeclared identifier '$bnd0'"],
                     error=True,
                 )
 
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py b/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
index 42d4a1f2c419c8..72408af5b25794 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
@@ -881,7 +881,9 @@ def haltReason(self):
         self.match("register read rdx", ["rdx = 0x1817161514131211"])
         # edx should not be added
         self.match(
-            "register read edx", ["error: Invalid register name 'edx'"], error=True
+            "register read edx",
+            [r"use of undeclared identifier '\$edx'"],
+            error=True,
         )
 
     @skipIfXmlSupportMissing
@@ -954,7 +956,7 @@ def haltReason(self):
         self.match("register read ecx", ["ecx = 0x14131211"])
         # dx should not be added
         self.match(
-            "register read cx", ["error: Invalid register name 'cx'"], error=True
+            "register read cx", [r"use of undeclared identifier '\$cx'"], error=True
         )
 
     @skipIfXmlSupportMissing
@@ -1049,5 +1051,5 @@ def haltReason(self):
         self.match("register read x1", ["x1 = 0x1817161514131211"])
         # w1 should not be added
         self.match(
-            "register read w1", ["error: Invalid register name 'w1'"], error=True
+            "register read w1", [r"use of undeclared identifier '\$w1'"], error=True
         )
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index e8314f70a35d51..61c06ec217410c 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -689,6 +689,16 @@ def test_invalid_union_member_paths(self):
             substrs=["use of undeclared identifier '$does_not_exist'"],
         )
 
+        self.runCmd("settings set target.experimental.use-DIL false")
+        try:
+            self.expect(
+                "register read does_not_exist",
+                error=True,
+                substrs=["no register named 'does_not_exist' found in this frame"],
+            )
+        finally:
+            self.runCmd("settings set target.experimental.use-DIL true")
+
         frame = process.GetThreadAtIndex(0).GetFrameAtIndex(0)
         vector = frame.FindRegister("v0")
         self.assertTrue(vector.GetType().IsArrayType())

>From 92c2bab2093935d1744f79d9b5916f15d6b3c50b Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Wed, 7 Oct 2026 16:04:04 -0700
Subject: [PATCH 09/11] fixed issues that greg flagged

---
 .../source/Commands/CommandObjectRegister.cpp | 51 ++++++++-----------
 .../Process/scripted/ScriptedFrame.cpp        | 29 +++++++++--
 lldb/source/Target/StackFrame.cpp             | 12 ++---
 .../register_command/TestRegisters.py         |  8 +++
 ...rameProviderWrappedFrameRegisterContext.py |  6 +++
 .../frame_provider.py                         |  4 ++
 6 files changed, 71 insertions(+), 39 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 9060fea199f4b5..91d2ebc7774931 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -29,6 +29,7 @@
 #include "lldb/Utility/Args.h"
 #include "lldb/Utility/DataExtractor.h"
 #include "lldb/Utility/RegisterValue.h"
+#include "lldb/Utility/Status.h"
 #include "lldb/Utility/StreamString.h"
 #include "lldb/ValueObject/ValueObject.h"
 #include "llvm/Support/Errno.h"
@@ -145,7 +146,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
     return true;
   }
 
-  bool DumpRegisterField(Stream &strm, const RegisterInfo &reg_info,
+  void DumpRegisterField(Stream &strm, const RegisterInfo &reg_info,
                          const ValueObjectSP &value_sp,
                          size_t reg_name_right_align_at,
                          CommandReturnObject &result) {
@@ -160,7 +161,7 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
     StreamString value_stream;
     if (llvm::Error error = value_sp->Dump(value_stream, options)) {
       result.AppendError(toString(std::move(error)));
-      return false;
+      return;
     }
 
     llvm::StringRef value = value_stream.GetString();
@@ -185,7 +186,6 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
       strm.Printf("%*s = ", static_cast<int>(reg_name_right_align_at),
                   expression.str().c_str());
     strm << value << '\n';
-    return true;
   }
 
   bool DumpRegisterSet(const ExecutionContext &exe_ctx, Stream &strm,
@@ -279,9 +279,10 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
                            "registers names are supplied as arguments\n");
       } else {
         struct RegisterArgument {
+          std::string expression;
           const RegisterInfo *reg_info = nullptr;
           ValueObjectSP value_sp;
-          std::string error;
+          Status error;
         };
 
         StackFrame *frame = m_exe_ctx.GetFramePtr();
@@ -298,51 +299,41 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
           expression.consume_front("$");
 
           RegisterArgument argument;
-          std::string variable_path = "$" + expression.str();
+          argument.expression = expression.str();
+          std::string variable_path = "$" + argument.expression;
           VariableSP variable_sp;
-          Status error;
           argument.value_sp = frame->GetValueForVariableExpressionPath(
               variable_path, eNoDynamicValues,
               StackFrame::eExpressionPathOptionCheckPtrVsMember, variable_sp,
-              error, eDILModeLegacy);
-          if (error.Fail())
-            argument.error = error.AsCString();
-          else if (!argument.value_sp)
-            argument.error =
-                "Invalid register expression '" + expression.str() + "'";
-          else if (ValueObject *root = argument.value_sp->GetRoot()) {
-            argument.reg_info =
-                reg_ctx->GetRegisterInfoByName(root->GetName().GetStringRef());
+              argument.error, eDILModeLegacy);
+          if (argument.error.Success()) {
+            ValueObject *root = argument.value_sp->GetRoot();
+            if (root && root->GetValueType() == eValueTypeRegister)
+              argument.reg_info = reg_ctx->GetRegisterInfoByName(
+                  root->GetName().GetStringRef());
           }
 
-          if (!argument.reg_info) {
-            if (argument.error.empty())
-              argument.error =
-                  "Invalid register expression '" + expression.str() + "'";
-          } else {
-            if (argument.value_sp &&
-                argument.value_sp.get() == argument.value_sp->GetRoot()) {
-              // Whole registers use DumpRegister below. Only descendants use
-              // DumpRegisterField.
-              argument.value_sp.reset();
-            }
+          if (argument.error.Success() && !argument.reg_info)
+            argument.error = Status::FromErrorStringWithFormat(
+                "Invalid register expression '%s'",
+                argument.expression.c_str());
+          else if (argument.error.Success())
             reg_name_right_align_at =
                 std::max(reg_name_right_align_at,
                          GetNameSize(argument.reg_info,
                                      !m_command_options.alternate_name));
-          }
           register_arguments.push_back(std::move(argument));
         }
 
         // Extra ident to be consistent with register sets dumping.
         strm.IndentMore();
         for (const RegisterArgument &argument : register_arguments) {
-          if (!argument.error.empty()) {
-            result.AppendError(argument.error);
+          if (argument.error.Fail()) {
+            result.AppendError(argument.error.AsCString());
             continue;
           }
 
-          if (!argument.value_sp) {
+          if (argument.value_sp.get() == argument.value_sp->GetRoot()) {
             // If they have asked for a specific format don't obscure that by
             // printing a structured value afterwards.
             bool print_type = !m_format_options.GetFormatValue().OptionWasSet();
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index 1207aa7eef538c..afaaffdda6be54 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -353,10 +353,33 @@ lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
   // can't do that!
   // FIXME: We should make it possible for the frame implementation to create
   //        Variable objects.
-  (void)var_sp;
   // Otherwise, delegate to the scripted frame interface pointer.
-  return m_scripted_frame_interface_sp->GetValueObjectForVariableExpression(
-      var_expr, options, error);
+  error.Clear();
+  ValueObjectSP value_sp =
+      m_scripted_frame_interface_sp->GetValueObjectForVariableExpression(
+          var_expr, options, error);
+  if (value_sp || !var_expr.starts_with("$"))
+    return value_sp;
+
+  VariableSP fallback_var_sp;
+  Status fallback_error;
+  ValueObjectSP fallback_value_sp =
+      StackFrame::GetValueForVariableExpressionPath(var_expr, use_dynamic,
+                                                    options, fallback_var_sp,
+                                                    fallback_error, mode);
+
+  // If the provider declined without an error, use the standard result. If it
+  // reported an error, only a real register from this frame takes precedence.
+  ValueObject *fallback_root =
+      fallback_value_sp ? fallback_value_sp->GetRoot() : nullptr;
+  if (error.Success() ||
+      (fallback_error.Success() && fallback_root &&
+       fallback_root->GetValueType() == eValueTypeRegister)) {
+    var_sp = std::move(fallback_var_sp);
+    error = std::move(fallback_error);
+    return fallback_value_sp;
+  }
+  return {};
 }
 
 llvm::Expected<lldb::ThreadPlanSP>
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 6acd5b9d89ea40..4bbaa093638363 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -576,6 +576,8 @@ StackFrame::GetInScopeVariableList(bool get_file_globals,
 ValueObjectSP StackFrame::GetValueForVariableExpressionPath(
     llvm::StringRef var_expr, DynamicValueType use_dynamic, uint32_t options,
     VariableSP &var_sp, Status &error, lldb::DILMode mode) {
+  error.Clear();
+
   ExecutionContext exe_ctx;
   CalculateExecutionContext(exe_ctx);
   bool use_DIL = exe_ctx.GetTargetRef().GetUseDIL(&exe_ctx);
@@ -626,8 +628,11 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
     VariableSP &var_sp, Status &error) {
   llvm::StringRef original_var_expr = var_expr;
   // We can't fetch variable information for a history stack frame.
-  if (IsHistorical())
+  if (IsHistorical()) {
+    error = Status::FromErrorString(
+        "cannot resolve a variable path in a historical frame");
     return ValueObjectSP();
+  }
 
   if (var_expr.empty()) {
     error = Status::FromErrorStringWithFormatv("invalid variable path '{0}'",
@@ -677,11 +682,6 @@ ValueObjectSP StackFrame::LegacyGetValueForVariableExpressionPath(
       return ValueObjectSP();
     }
     valobj_sp = ValueObjectRegister::Create(this, reg_ctx_sp, reg_info);
-    if (!valobj_sp) {
-      error = Status::FromErrorStringWithFormat(
-          "unable to read register '%s' in this frame", reg_info->name);
-      return {};
-    }
     name_const_string = ConstString(reg_info->name);
     var_expr = register_path;
   } else {
diff --git a/lldb/test/API/commands/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register_command/TestRegisters.py
index 8c16cd4d748f80..6ab449ce4d4173 100644
--- a/lldb/test/API/commands/register/register_command/TestRegisters.py
+++ b/lldb/test/API/commands/register/register_command/TestRegisters.py
@@ -124,6 +124,14 @@ def test_register_expressions(self):
         if self.getArchitecture() in ["amd64", "x86_64"]:
             self.expect("expr -- ($rax & 0xffffffff) == $eax", substrs=["true"])
 
+        self.runCmd("expr -- int $not_a_register = 42")
+        self.expect("expr -- $not_a_register", substrs=[" = 42"])
+        self.expect(
+            "register read not_a_register",
+            substrs=["Invalid register expression 'not_a_register'"],
+            error=True,
+        )
+
     @skipIfiOSSimulator
     @skipIf(archs=no_match(["amd64", "x86_64"]))
     @expectedFailureWindowsAndNoLLDBServer(bugnumber="llvm.org/pr37683")
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/TestFrameProviderWrappedFrameRegisterContext.py b/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/TestFrameProviderWrappedFrameRegisterContext.py
index f2f31e8e1b0fd1..3dbe21bbff85bb 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/TestFrameProviderWrappedFrameRegisterContext.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/TestFrameProviderWrappedFrameRegisterContext.py
@@ -33,3 +33,9 @@ def test_backtrace_with_forwarded_register_context(self):
         # which reads registers through the scripted frame's register context.
         # Checking the values, not just for a crash, proves the lookup worked.
         self.expect("bt", substrs=["compute(a=3, b=4)", "main"])
+
+        frame = process.GetSelectedThread().GetFrameAtIndex(0)
+        register = frame.GetRegisters().GetValueAtIndex(0).GetChildAtIndex(0)
+        register_name = register.GetName()
+        self.assertTrue(frame.GetValueForVariablePath("$" + register_name).IsValid())
+        self.expect("register read " + register_name, substrs=[register_name + " ="])
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/frame_provider.py b/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/frame_provider.py
index 103a870bfcb86d..13774220b29327 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/frame_provider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/wrapped_frame_register_context/frame_provider.py
@@ -78,6 +78,10 @@ def read(entry):
 
         return struct.pack(struct_format, *struct_data)
 
+    def get_value_for_variable_expression(self, expr, options, error):
+        error.SetErrorString(f"no scripted value for {expr}")
+        return None
+
 
 class WrapVariablesProvider(ScriptedFrameProvider):
     @staticmethod

>From eb25629dbcef28e709e1fec98317d3ff461a269b Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Thu, 8 Oct 2026 13:56:24 -0700
Subject: [PATCH 10/11] Preserve $

---
 lldb/source/Commands/CommandObjectRegister.cpp            | 8 ++++----
 .../commands/register/register_command/TestRegisters.py   | 5 +++++
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 91d2ebc7774931..142c0715953edf 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -295,12 +295,12 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
         size_t reg_name_right_align_at = 0;
 
         for (auto &entry : command) {
+          RegisterArgument argument;
+          argument.expression = entry.ref().str();
+
           llvm::StringRef expression = entry.ref();
           expression.consume_front("$");
-
-          RegisterArgument argument;
-          argument.expression = expression.str();
-          std::string variable_path = "$" + argument.expression;
+          std::string variable_path = "$" + expression.str();
           VariableSP variable_sp;
           argument.value_sp = frame->GetValueForVariableExpressionPath(
               variable_path, eNoDynamicValues,
diff --git a/lldb/test/API/commands/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register_command/TestRegisters.py
index 6ab449ce4d4173..7bd11f5e84f844 100644
--- a/lldb/test/API/commands/register/register_command/TestRegisters.py
+++ b/lldb/test/API/commands/register/register_command/TestRegisters.py
@@ -131,6 +131,11 @@ def test_register_expressions(self):
             substrs=["Invalid register expression 'not_a_register'"],
             error=True,
         )
+        self.expect(
+            "register read $not_a_register",
+            substrs=["Invalid register expression '$not_a_register'"],
+            error=True,
+        )
 
     @skipIfiOSSimulator
     @skipIf(archs=no_match(["amd64", "x86_64"]))

>From a7554caa8889aa06ab3bf2c09a7c88ae8903bb06 Mon Sep 17 00:00:00 2001
From: Bar Soloveychik <barsolo at fb.com>
Date: Sun, 18 Oct 2026 02:18:45 -0700
Subject: [PATCH 11/11] fixed David comments

---
 lldb/source/Commands/CommandObjectRegister.cpp   |  7 +++++++
 .../RegisterTypeBuilderClang.cpp                 | 16 ++++++++--------
 .../RegisterTypeBuilderClang.h                   |  2 +-
 .../register/register_command/TestRegisters.py   |  4 ++--
 .../gdb_remote_client/TestGDBServerTargetXML.py  | 10 +++++++---
 .../gdb_remote_client/TestXMLRegisterUnion.py    |  4 ++--
 6 files changed, 27 insertions(+), 16 deletions(-)

diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index 142c0715953edf..bf3d41e2ed3861 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -306,6 +306,13 @@ class CommandObjectRegisterRead : public CommandObjectParsed {
               variable_path, eNoDynamicValues,
               StackFrame::eExpressionPathOptionCheckPtrVsMember, variable_sp,
               argument.error, eDILModeLegacy);
+          if (argument.error.Fail() &&
+              expression.find_first_of(".[") == llvm::StringRef::npos &&
+              !expression.contains("->")) {
+            argument.error = Status::FromErrorStringWithFormat(
+                "Invalid register name or identifier '%s'",
+                argument.expression.c_str());
+          }
           if (argument.error.Success()) {
             ValueObject *root = argument.value_sp->GetRoot();
             if (root && root->GetValueType() == eValueTypeRegister)
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
index be4929d1e34f50..29f5daf97645a7 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.cpp
@@ -199,8 +199,13 @@ RegisterTypeBuilderClang::BuildVectorType(const RegisterTypeVector *vector_type,
 
 CompilerType
 RegisterTypeBuilderClang::BuildUnionType(const RegisterTypeUnion *union_type,
-                                         uint32_t expected_byte_size,
+                                         uint32_t available_byte_size,
                                          lldb::TypeSystemClangSP type_system) {
+  std::optional<uint32_t> byte_size =
+      GetExpectedByteSize(union_type, type_system);
+  if (!byte_size || *byte_size > available_byte_size)
+    return {};
+  uint32_t expected_byte_size = *byte_size;
   if (!expected_byte_size)
     return {};
   if (auto type = GetExistingCompilerType(union_type, expected_byte_size))
@@ -348,13 +353,8 @@ RegisterTypeBuilderClang::GetRegisterType(const RegisterInfo &reg_info) {
         reg_info.byte_size, type_system);
   case RegisterType::eRegisterTypeKindVector:
     return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
-  case RegisterType::eRegisterTypeKindUnion: {
-    std::optional<uint32_t> byte_size =
-        GetExpectedByteSize(reg_info.register_type, type_system);
-    if (!byte_size || *byte_size > reg_info.byte_size)
-      return {};
-    return BuildType(reg_info.register_type, *byte_size, type_system);
-  }
+  case RegisterType::eRegisterTypeKindUnion:
+    return BuildType(reg_info.register_type, reg_info.byte_size, type_system);
   }
 }
 
diff --git a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
index 128da85ad4eccc..77f1584f6c98d7 100644
--- a/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
+++ b/lldb/source/Plugins/RegisterTypeBuilder/RegisterTypeBuilderClang.h
@@ -50,7 +50,7 @@ class RegisterTypeBuilderClang : public RegisterTypeBuilder {
                                lldb::TypeSystemClangSP type_system);
 
   CompilerType BuildUnionType(const RegisterTypeUnion *union_type,
-                              uint32_t expected_byte_size,
+                              uint32_t available_byte_size,
                               lldb::TypeSystemClangSP type_system);
 
   CompilerType BuildType(const RegisterType *register_type,
diff --git a/lldb/test/API/commands/register/register_command/TestRegisters.py b/lldb/test/API/commands/register/register_command/TestRegisters.py
index 7bd11f5e84f844..80341310ef45be 100644
--- a/lldb/test/API/commands/register/register_command/TestRegisters.py
+++ b/lldb/test/API/commands/register/register_command/TestRegisters.py
@@ -485,7 +485,7 @@ def fp_register_write(self):
             else:
                 self.expect(
                     "register read ymm0",
-                    substrs=["use of undeclared identifier '$ymm0'"],
+                    substrs=["Invalid register name or identifier 'ymm0'"],
                     error=True,
                 )
 
@@ -506,7 +506,7 @@ def fp_register_write(self):
             else:
                 self.expect(
                     "register read bnd0",
-                    substrs=["use of undeclared identifier '$bnd0'"],
+                    substrs=["Invalid register name or identifier 'bnd0'"],
                     error=True,
                 )
 
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py b/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
index 72408af5b25794..c970b893e8ef12 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestGDBServerTargetXML.py
@@ -882,7 +882,7 @@ def haltReason(self):
         # edx should not be added
         self.match(
             "register read edx",
-            [r"use of undeclared identifier '\$edx'"],
+            ["Invalid register name or identifier 'edx'"],
             error=True,
         )
 
@@ -956,7 +956,9 @@ def haltReason(self):
         self.match("register read ecx", ["ecx = 0x14131211"])
         # dx should not be added
         self.match(
-            "register read cx", [r"use of undeclared identifier '\$cx'"], error=True
+            "register read cx",
+            ["Invalid register name or identifier 'cx'"],
+            error=True,
         )
 
     @skipIfXmlSupportMissing
@@ -1051,5 +1053,7 @@ def haltReason(self):
         self.match("register read x1", ["x1 = 0x1817161514131211"])
         # w1 should not be added
         self.match(
-            "register read w1", [r"use of undeclared identifier '\$w1'"], error=True
+            "register read w1",
+            ["Invalid register name or identifier 'w1'"],
+            error=True,
         )
diff --git a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
index 61c06ec217410c..36571f054b60dc 100644
--- a/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
+++ b/lldb/test/API/functionalities/gdb_remote_client/TestXMLRegisterUnion.py
@@ -686,7 +686,7 @@ def test_invalid_union_member_paths(self):
         self.expect(
             "register read does_not_exist",
             error=True,
-            substrs=["use of undeclared identifier '$does_not_exist'"],
+            substrs=["Invalid register name or identifier 'does_not_exist'"],
         )
 
         self.runCmd("settings set target.experimental.use-DIL false")
@@ -694,7 +694,7 @@ def test_invalid_union_member_paths(self):
             self.expect(
                 "register read does_not_exist",
                 error=True,
-                substrs=["no register named 'does_not_exist' found in this frame"],
+                substrs=["Invalid register name or identifier 'does_not_exist'"],
             )
         finally:
             self.runCmd("settings set target.experimental.use-DIL true")



More information about the lldb-commits mailing list