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
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits