Michael137 created this revision.
Michael137 added reviewers: aprantl, shafik.
Herald added a subscriber: mgorny.
Herald added a project: All.
Michael137 requested review of this revision.
Herald added a project: LLDB.
Herald added a subscriber: lldb-commits.

This patch adds a libcxx formatter for std::span. The
implementation is based on the libcxx formatter for
std::vector. The main difference is the fact that
std::span conditionally has a __size member based
on whether it has a static or dynamic extent.

Example output of formatted span:

  (std::span<const int, 18446744073709551615>) $0 = size=6 {
    [0] = 0
    [1] = 1
    [2] = 2
    [3] = 3
    [4] = 4
    [5] = 5
  }

The second template parameter here is actually `std::dynamic_extent`,
but the type declaration we get back from the `TypeSystemClang` is the
actual value (which in this case is `(size_t)-1`). This is consistent
with diagnostics from clang, which doesn't desugar this value either.
E.g.,:

  span.cpp:30:31: error: implicit instantiation of undefined template
      'Undefined<std::span<int, 18446744073709551615>>'

Testing:

- Added API-tests
- Confirmed manually using LLDB cli that printing spans works in various 
scenarios


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D127481

Files:
  lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
  lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
  lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
  lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp
  
lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile
  
lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py
  
lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp

Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/main.cpp
@@ -0,0 +1,58 @@
+#include <array>
+#include <span>
+#include <stdio.h>
+#include <string>
+#include <vector>
+
+// TODO: use these
+
+template <class T, size_t N>
+void by_ref_and_ptr(std::span<T, N> &ref, std::span<T, N> *ptr) {
+  // Stop here to check by ref
+  return;
+}
+
+int main() {
+  std::array numbers = {1, 12, 123, 1234, 12345};
+
+  using dynamic_string_span = std::span<std::string>;
+
+  // Test span of ints
+
+  //   Full view of numbers with static extent
+  std::span numbers_span = numbers;
+
+  printf("break here");
+
+  by_ref_and_ptr(numbers_span, &numbers_span);
+
+  // Test spans of strings
+  std::vector<std::string> strings{"goofy", "is", "smart", "!!!"};
+  strings.reserve(strings.size() + 1);
+
+  //   Partial view of strings with dynamic extent
+  dynamic_string_span strings_span{std::span{strings}.subspan(2)};
+
+  auto strings_span_it = strings_span.begin();
+
+  printf("break here");
+
+  //   Vector size doesn't increase, span should
+  //   print unchanged and the strings_span_it
+  //   remains valid
+  strings.emplace_back("???");
+
+  printf("break here");
+
+  // Now some empty spans
+  std::span<int, 0> static_zero_span;
+  std::span<int> dynamic_zero_span;
+
+  // Multiple spans
+  std::array span_arr{strings_span, strings_span};
+  std::span<std::span<std::string>, 2> nested = span_arr;
+
+  printf("break here");
+
+  return 0; // break here
+}
Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/TestDataFormatterLibcxxSpan.py
@@ -0,0 +1,164 @@
+"""
+Test lldb data formatter subsystem for std::span
+"""
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test import lldbutil
+
+class LibcxxSpanDataFormatterTestCase(TestBase):
+
+    mydir = TestBase.compute_mydir(__file__)
+
+    def check_numbers(self, var_name):
+        """Helper to check that data formatter sees contents of std::span correctly"""
+        self.expect("frame variable " + var_name,
+                    substrs=[var_name + ' = size=5',
+                             '[0] = 1',
+                             '[1] = 12',
+                             '[2] = 123',
+                             '[3] = 1234',
+                             '[4] = 12345',
+                             '}'])
+
+        self.expect("p " + var_name,
+                    substrs=['$', 'size=5',
+                             '[0] = 1',
+                             '[1] = 12',
+                             '[2] = 123',
+                             '[3] = 1234',
+                             '[4] = 12345',
+                             '}'])
+
+        self.expect_expr(var_name, result_summary="size=5", result_children=[
+            ValueCheck(value="1"),
+            ValueCheck(value="12"),
+            ValueCheck(value="123"),
+            ValueCheck(value="1234"),
+            ValueCheck(value="12345"),
+        ])
+
+        # check access-by-index
+        self.expect("frame variable " + var_name + "[0]",
+                    substrs=['1'])
+        self.expect("frame variable " + var_name + "[1]",
+                    substrs=['12'])
+        self.expect("frame variable " + var_name + "[2]",
+                    substrs=['123'])
+        self.expect("frame variable " + var_name + "[3]",
+                    substrs=['1234'])
+
+    @add_test_categories(["libc++"])
+    def test_with_run_command(self):
+        """Test that std::span variables are formatted correctly when printed."""
+        self.build()
+        (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, "break here", lldb.SBFileSpec("main.cpp", False))
+
+        # This is the function to remove the custom formats in order to have a
+        # clean slate for the next test case.
+        def cleanup():
+            self.runCmd('type format clear', check=False)
+            self.runCmd('type summary clear', check=False)
+            self.runCmd('type filter clear', check=False)
+            self.runCmd('type synth clear', check=False)
+            self.runCmd(
+                "settings set target.max-children-count 256",
+                check=False)
+
+        # Execute the cleanup function during test case tear down.
+        self.addTearDownHook(cleanup)
+
+        lldbutil.continue_to_breakpoint(process, bkpt)
+
+        # std::span of std::array with extents known at compile-time
+        self.check_numbers("numbers_span")
+
+        # check access to synthetic children for static spans
+        self.runCmd("type summary add --summary-string \"item 0 is ${var[0]}\" -x \"std::span<\" span")
+        self.expect('frame variable numbers_span', substrs=['item 0 is 1'])
+
+        self.runCmd("type summary add --summary-string \"item 0 is ${svar[0]}\" -x \"std::span<\" span")
+        self.expect('frame variable numbers_span', substrs=['item 0 is 1'])
+
+        self.runCmd("type summary delete span")
+
+        # New span with strings
+        lldbutil.continue_to_breakpoint(process, bkpt)
+
+        self.expect("frame variable strings_span",
+                    substrs=['smart',
+                             '!!!'])
+
+        self.expect("p strings_span",
+                    substrs=['smart',
+                             '!!!'])
+
+        self.expect("p strings_span_it",
+                    substrs=['item = "smart"'])
+
+        # check access to synthetic children for dynamic spans
+        self.runCmd("type summary add --summary-string \"item 0 is ${var[0]}\" dynamic_string_span")
+        self.expect('frame variable strings_span', substrs=['item 0 is "smart"'])
+
+        self.runCmd("type summary add --summary-string \"item 0 is ${svar[0]}\" dynamic_string_span")
+        self.expect('frame variable strings_span', substrs=['item 0 is "smart"'])
+
+        self.runCmd("type summary delete dynamic_string_span")
+
+        # test summaries based on synthetic children
+        self.runCmd(
+                "type summary add --summary-string \"span has ${svar%#} items\" -e dynamic_string_span")
+
+        self.expect("frame variable strings_span",
+                    substrs=['span has 2 items'])
+
+        self.expect("p strings_span",
+                    substrs=['span has 2 items',
+                             'smart',
+                             '!!!'])
+
+        # check access-by-index
+        self.expect("frame variable strings_span[0]",
+                    substrs=['smart'])
+        self.expect("frame variable strings_span[1]",
+                    substrs=['!!!'])
+
+        # Newly inserted value not visible to span
+        lldbutil.continue_to_breakpoint(process, bkpt)
+
+        self.expect("p strings_span",
+                    substrs=['smart',
+                             '!!!'])
+
+        lldbutil.continue_to_breakpoint(process, bkpt)
+
+        # Empty spans
+        self.expect("frame variable static_zero_span",
+                    substrs=['static_zero_span = size=0'])
+
+        self.expect("frame variable dynamic_zero_span",
+                    substrs=['dynamic_zero_span = size=0'])
+
+        # Nested spans
+        self.expect("frame variable nested",
+                    substrs=['nested = size=2',
+                             '[0] = size=2',
+                             '[1] = size=2'])
+
+    @add_test_categories(["libc++"])
+    def test_ref_and_ptr(self):
+        """Test that std::span is correctly formatted when passed by ref and ptr"""
+        self.build()
+        (self.target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
+            self, "Stop here to check by ref", lldb.SBFileSpec("main.cpp", False))
+
+        # The reference should display the same was as the value did
+        self.check_numbers("ref")
+
+        # The pointer should just show the right number of elements:
+
+        self.expect("frame variable ptr", substrs=['ptr =', ' size=5'])
+
+        self.expect("p ptr", substrs=['$', 'size=5'])
Index: lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile
===================================================================
--- /dev/null
+++ lldb/test/API/functionalities/data-formatter/data-formatter-stl/libcxx/span/Makefile
@@ -0,0 +1,6 @@
+CXX_SOURCES := main.cpp
+
+USE_LIBCPP := 1
+
+CXXFLAGS_EXTRAS := -std=c++20 -O0
+include Makefile.rules
Index: lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp
===================================================================
--- /dev/null
+++ lldb/source/Plugins/Language/CPlusPlus/LibCxxSpan.cpp
@@ -0,0 +1,159 @@
+//===-- LibCxxSpan.cpp ----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "LibCxx.h"
+
+#include "lldb/Core/ValueObject.h"
+#include "lldb/DataFormatters/FormattersHelpers.h"
+#include "lldb/Utility/ConstString.h"
+#include "llvm/ADT/APSInt.h"
+
+using namespace lldb;
+using namespace lldb_private;
+using namespace lldb_private::formatters;
+
+namespace lldb_private {
+namespace formatters {
+
+class LibcxxStdSpanSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
+public:
+  LibcxxStdSpanSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
+
+  ~LibcxxStdSpanSyntheticFrontEnd() override;
+
+  size_t CalculateNumChildren() override;
+
+  lldb::ValueObjectSP GetChildAtIndex(size_t idx) override;
+
+  bool Update() override;
+
+  bool MightHaveChildren() override;
+
+  size_t GetIndexOfChildWithName(ConstString name) override;
+
+private:
+  ValueObject *m_start = nullptr; ///< First element of span
+  CompilerType m_element_type{};  ///< Type of span elements
+  size_t m_num_elements = 0;      ///< Number of elements in span
+  uint32_t m_element_size = 0;    ///< Size in bytes of each span element
+};
+
+lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    LibcxxStdSpanSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp)
+    : SyntheticChildrenFrontEnd(*valobj_sp) {
+  if (valobj_sp)
+    Update();
+}
+
+lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    ~LibcxxStdSpanSyntheticFrontEnd() {
+  // this needs to stay around because it's a child object who will follow
+  // its parent's life cycle
+  // delete m_start;
+}
+
+size_t lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    CalculateNumChildren() {
+  return m_num_elements;
+}
+
+lldb::ValueObjectSP
+lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::GetChildAtIndex(
+    size_t idx) {
+  if (!m_start)
+    return {};
+
+  uint64_t offset = idx * m_element_size;
+  offset = offset + m_start->GetValueAsUnsigned(0);
+  StreamString name;
+  name.Printf("[%" PRIu64 "]", (uint64_t)idx);
+  return CreateValueObjectFromAddress(name.GetString(), offset,
+                                      m_backend.GetExecutionContextRef(),
+                                      m_element_type);
+}
+
+/*
+ * std::span can either be instantiated with a compile-time known
+ * extent or a std::dynaic_extent (this is the default if only the
+ * type template argument is provided). The layout of std::span
+ * depends on whether the extent is dynamic or not. For static
+ * extents (e.g., std::span<int, 9>):
+ *
+ * (std::__1::span<const int, 9>) s = {
+ *   __data = 0x000000016fdff494
+ * }
+ *
+ * For dynamic extents, e.g., std::span<int>, the layout is:
+ *
+ * (std::__1::span<const int, 18446744073709551615>) s = {
+ *   __data = 0x000000016fdff494
+ *   __size = 6
+ * }
+ *
+ * This function checks for a '__size' member to determine the number
+ * of elements in the span. If no such member exists, we get the size
+ * from the only other place it can be: the template argument.
+ */
+bool lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::Update() {
+  // Get element type
+  ValueObjectSP data_type_finder_sp(
+      m_backend.GetChildMemberWithName(ConstString("__data"), true));
+  if (!data_type_finder_sp)
+    return false;
+
+  m_element_type = data_type_finder_sp->GetCompilerType().GetPointeeType();
+
+  // Get element size
+  if (llvm::Optional<uint64_t> size = m_element_type.GetByteSize(nullptr)) {
+    m_element_size = *size;
+
+    // Get data
+    if (m_element_size > 0) {
+      m_start = data_type_finder_sp.get();
+    }
+
+    // Get size
+    auto size_sp =
+        m_backend.GetChildMemberWithName(ConstString("__size"), true);
+    if (size_sp) {
+      m_num_elements = size_sp->GetValueAsUnsigned(0);
+    } else if (auto arg =
+                   m_backend.GetCompilerType().GetIntegralTemplateArgument(1)) {
+
+      m_num_elements = arg->value.getLimitedValue();
+    }
+  }
+
+  return true;
+}
+
+bool lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    MightHaveChildren() {
+  return true;
+}
+
+size_t lldb_private::formatters::LibcxxStdSpanSyntheticFrontEnd::
+    GetIndexOfChildWithName(ConstString name) {
+  if (!m_start)
+    return UINT32_MAX;
+  return ExtractIndexFromString(name.GetCString());
+}
+
+lldb_private::SyntheticChildrenFrontEnd *
+LibcxxStdSpanSyntheticFrontEndCreator(CXXSyntheticChildren *,
+                                      lldb::ValueObjectSP valobj_sp) {
+  if (!valobj_sp)
+    return nullptr;
+  CompilerType type = valobj_sp->GetCompilerType();
+  if (!type.IsValid() || type.GetNumTemplateArguments() != 2)
+    return nullptr;
+  return new LibcxxStdSpanSyntheticFrontEnd(valobj_sp);
+}
+
+} // namespace formatters
+} // namespace lldb_private
Index: lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
===================================================================
--- lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
+++ lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
@@ -74,6 +74,10 @@
 bool LibcxxContainerSummaryProvider(ValueObject &valobj, Stream &stream,
                                     const TypeSummaryOptions &options);
 
+// libc++ std::span<>
+bool LibcxxSpanSummaryProvider(ValueObject &valobj, Stream &stream,
+                               const TypeSummaryOptions &options);
+
 class LibCxxMapIteratorSyntheticFrontEnd : public SyntheticChildrenFrontEnd {
 public:
   LibCxxMapIteratorSyntheticFrontEnd(lldb::ValueObjectSP valobj_sp);
@@ -193,6 +197,10 @@
 LibcxxVariantFrontEndCreator(CXXSyntheticChildren *,
                              lldb::ValueObjectSP valobj_sp);
 
+SyntheticChildrenFrontEnd *
+LibcxxStdSpanSyntheticFrontEndCreator(CXXSyntheticChildren *,
+                                      lldb::ValueObjectSP);
+
 } // namespace formatters
 } // namespace lldb_private
 
Index: lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
===================================================================
--- lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
+++ lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp
@@ -756,6 +756,12 @@
       lldb_private::formatters::LibcxxAtomicSyntheticFrontEndCreator,
       "libc++ std::atomic synthetic children",
       ConstString("^std::__[[:alnum:]]+::atomic<.+>$"), stl_synth_flags, true);
+  AddCXXSynthetic(
+      cpp_category_sp,
+      lldb_private::formatters::LibcxxStdSpanSyntheticFrontEndCreator,
+      "libc++ std::span synthetic children",
+      ConstString("^std::__[[:alnum:]]+::span<.+>(( )?&)?$"), stl_deref_flags,
+      true);
 
   cpp_category_sp->GetRegexTypeSyntheticsContainer()->Add(
       RegularExpression("^(std::__[[:alnum:]]+::)deque<.+>(( )?&)?$"),
@@ -869,6 +875,11 @@
                 "libc++ std::variant summary provider",
                 ConstString("^std::__[[:alnum:]]+::variant<.+>(( )?&)?$"),
                 stl_summary_flags, true);
+  AddCXXSummary(cpp_category_sp,
+                lldb_private::formatters::LibcxxContainerSummaryProvider,
+                "libc++ std::span summary provider",
+                ConstString("^std::__[[:alnum:]]+::span<.+>(( )?&)?$"),
+                stl_summary_flags, true);
 
   stl_summary_flags.SetSkipPointers(true);
 
Index: lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
===================================================================
--- lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
+++ lldb/source/Plugins/Language/CPlusPlus/CMakeLists.txt
@@ -11,6 +11,7 @@
   LibCxxList.cpp
   LibCxxMap.cpp
   LibCxxQueue.cpp
+  LibCxxSpan.cpp
   LibCxxTuple.cpp
   LibCxxUnorderedMap.cpp
   LibCxxVariant.cpp
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to