[lldb] Improved formatting of 'register read' command. (#188049)

Made alignment of 'register read' command's output to be dynamic to the
register's name lengths -> `=` signs of different registers align when
printed as sets, also aligned when picked in some custom ordering. No
alignment between separate sets is included (i.e, each set is aligned to
itself only). Sample change:

```
Micro-architectural Registers:
      r_pc = 0x0000108c  main.xexe`main + 20 at main.c:5:5
  instr_trace_r_init_print = 0x00
  wb_csr_file_r_mstatus = 0x00000000
  wb_csr_file_r_mie = 0x00000000
```

This example is changed into:

```
Micro-architectural Registers:
                      r_pc = 0x0000108c  main.xexe`main + 20 at main.c:5:5
  instr_trace_r_init_print = 0x00
     wb_csr_file_r_mstatus = 0x00000000
         wb_csr_file_r_mie = 0x00000000
```

---------

Co-authored-by: Aleksandr Levin <alexander.levin03@mail.ru>
diff --git a/lldb/source/Commands/CommandObjectRegister.cpp b/lldb/source/Commands/CommandObjectRegister.cpp
index c86fd11..83ff915 100644
--- a/lldb/source/Commands/CommandObjectRegister.cpp
+++ b/lldb/source/Commands/CommandObjectRegister.cpp
@@ -36,6 +36,58 @@
 #define LLDB_OPTIONS_register_read
 #include "CommandOptions.inc"
 
+static size_t GetNameSize(const RegisterInfo *reg_info, bool use_primary_name) {
+  const char *reg_name = use_primary_name ? reg_info->name : reg_info->alt_name;
+  return reg_name ? strlen(reg_name) : 0;
+}
+
+static size_t ComputeLongestRegisterName(RegisterContext *reg_ctx,
+                                         const RegisterSet &reg_set,
+                                         bool use_primary_name,
+                                         bool primitive_only) {
+  const size_t num_registers = reg_set.num_registers;
+  size_t name_right_align_at = 0;
+
+  // Loop through all the registers to find the longest register name.
+  for (size_t reg_idx = 0; reg_idx < num_registers; ++reg_idx) {
+    const size_t reg = reg_set.registers[reg_idx];
+    if (const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg)) {
+      // Derived registers are skipped if primitive_only is true.
+      if (primitive_only && reg_info->value_regs)
+        continue;
+
+      name_right_align_at = std::max(name_right_align_at,
+                                     GetNameSize(reg_info, use_primary_name));
+    }
+  }
+
+  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("$");
+
+    if (const RegisterInfo *reg_info =
+            reg_ctx->GetRegisterInfoByName(arg_str)) {
+      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)
@@ -75,7 +127,7 @@
 
   bool DumpRegister(const ExecutionContext &exe_ctx, Stream &strm,
                     RegisterContext &reg_ctx, const RegisterInfo &reg_info,
-                    bool print_flags) {
+                    bool print_flags, size_t reg_name_right_align_at) {
     RegisterValue reg_value;
     if (!reg_ctx.ReadRegister(&reg_info, reg_value))
       return false;
@@ -85,7 +137,8 @@
     bool prefix_with_altname = (bool)m_command_options.alternate_name;
     bool prefix_with_name = !prefix_with_altname;
     DumpRegisterValue(reg_value, strm, reg_info, prefix_with_name,
-                      prefix_with_altname, m_format_options.GetFormat(), 8,
+                      prefix_with_altname, m_format_options.GetFormat(),
+                      reg_name_right_align_at,
                       exe_ctx.GetBestExecutionContextScope(), print_flags,
                       exe_ctx.GetTargetSP());
     if ((reg_info.encoding == eEncodingUint) ||
@@ -123,6 +176,8 @@
       strm.Printf("%s:\n", (reg_set->name ? reg_set->name : "unknown"));
       strm.IndentMore();
       const size_t num_registers = reg_set->num_registers;
+      size_t reg_name_right_align_at = ComputeLongestRegisterName(
+          reg_ctx, *reg_set, !m_command_options.alternate_name, primitive_only);
       for (size_t reg_idx = 0; reg_idx < num_registers; ++reg_idx) {
         const uint32_t reg = reg_set->registers[reg_idx];
         const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex(reg);
@@ -130,8 +185,9 @@
         if (primitive_only && reg_info && reg_info->value_regs)
           continue;
 
-        if (reg_info && DumpRegister(exe_ctx, strm, *reg_ctx, *reg_info,
-                                     /*print_flags=*/false))
+        if (reg_info &&
+            DumpRegister(exe_ctx, strm, *reg_ctx, *reg_info,
+                         /*print_flags=*/false, reg_name_right_align_at))
           ++available_count;
         else
           ++unavailable_count;
@@ -195,6 +251,10 @@
         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);
+        // 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
@@ -210,14 +270,15 @@
             // printing flags afterwards.
             bool print_flags =
                 !m_format_options.GetFormatValue().OptionWasSet();
-            if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *reg_info,
-                              print_flags))
+            if (!DumpRegister(m_exe_ctx, strm, *reg_ctx, *reg_info, print_flags,
+                              reg_name_right_align_at))
               strm.Printf("%-12s = error: unavailable\n", reg_info->name);
           } else {
             result.AppendErrorWithFormat("Invalid register name '%s'",
                                          arg_str.str().c_str());
           }
         }
+        strm.IndentLess();
       }
     }
     if (result.GetStatus() != eReturnStatusFailed)
diff --git a/lldb/test/Shell/Commands/command-register-read-alignment.test b/lldb/test/Shell/Commands/command-register-read-alignment.test
new file mode 100644
index 0000000..aec13fa
--- /dev/null
+++ b/lldb/test/Shell/Commands/command-register-read-alignment.test
@@ -0,0 +1,46 @@
+# REQUIRES: target-x86
+
+# RUN: %clang_host -g -O0 %S/Inputs/main.c -o %t.out
+# RUN: %lldb -b -o "breakpoint set --name main" \
+# RUN:        -o run \
+# RUN:        -o "register read rip r10d rflags" \
+# RUN:        -o "register read -A rsp rbp rip" \
+# RUN:        -o "register read -s 0" \
+# RUN:        -o "register read --all" \
+# RUN:        %t.out | FileCheck --strict-whitespace %s
+
+# CHECK: (lldb) register read rip r10d rflags
+# CHECK-NEXT: {{^     rip = }}
+# CHECK-NEXT: {{^    r10d = }}
+# CHECK-NEXT: {{^  rflags = }}
+
+# CHECK: (lldb) register read -A rsp rbp rip
+# CHECK-NEXT: {{^  rsp/sp = }}
+# CHECK-NEXT: {{^  rbp/fp = }}
+# CHECK-NEXT: {{^  rip/pc = }}
+
+## Registers within a set align to each other.
+# CHECK: (lldb) register read -s 0
+# CHECK: General Purpose Registers:
+# CHECK: {{^      rsi = }}
+# CHECK: {{^       r8 = }}
+# CHECK: {{^      rip = }}
+# CHECK: {{^   rflags = }}
+# CHECK: {{^       cs = }}
+# CHECK: {{^  fs_base = }}
+# CHECK: {{^  gs_base = }}
+# CHECK: {{^       ds = }}
+
+## Registers within sets are aligned, but not aligned across different sets.
+# CHECK: (lldb) register read --all
+
+# CHECK: General Purpose Registers:
+# CHECK: {{^      rip = }}
+# CHECK: {{^   rflags = }}
+# CHECK: {{^  fs_base = }}
+
+# CHECK: Floating Point Registers:
+# CHECK: {{^       ftag = }}
+# CHECK: {{^        fdp = }}
+# CHECK: {{^  mxcsrmask = }}
+# CHECK: {{^      xmm10 = }}