[Lldb-commits] [lldb] [lldb] Enable caching for BytecodeSyntheticChildren::FrontEnd::Update (PR #181199)
Dave Lee via lldb-commits
lldb-commits at lists.llvm.org
Wed Mar 25 14:52:51 PDT 2026
https://github.com/kastiglione updated https://github.com/llvm/llvm-project/pull/181199
>From 40eabae47e330e17a5e1419919f786d4840654ff Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Thu, 12 Feb 2026 10:29:20 -0800
Subject: [PATCH 1/4] [lldb] Enable caching for
BytecodeSyntheticChildren::FrontEnd::Update
---
lldb/source/DataFormatters/TypeSynthetic.cpp | 34 ++++++++++++++++++-
.../TestBytecodeSynthetic.py | 21 +++++++++++-
.../bytecode-synthetic/main.cpp | 5 ++-
3 files changed, 57 insertions(+), 3 deletions(-)
diff --git a/lldb/source/DataFormatters/TypeSynthetic.cpp b/lldb/source/DataFormatters/TypeSynthetic.cpp
index 10655cdca6f05..32d035eb8a90b 100644
--- a/lldb/source/DataFormatters/TypeSynthetic.cpp
+++ b/lldb/source/DataFormatters/TypeSynthetic.cpp
@@ -22,6 +22,7 @@
#include "lldb/Utility/StreamString.h"
#include "llvm/Support/Error.h"
#include <cstdint>
+#include <optional>
using namespace lldb;
using namespace lldb_private;
@@ -291,10 +292,41 @@ lldb::ChildCacheState BytecodeSyntheticChildren::FrontEnd::Update() {
return ChildCacheState::eRefetch;
}
+ // If the top of the stack is a 0 or 1, that value is popped of the stack and
+ // treated as a return value of eRefetch or eReuse respectively. If the top of
+ // the stack is any other value, it stays on the stack and becomes part of
+ // `self`.
+ //
+ // This dynamic logic can lead to errors for synthetic formatter authors.
+ // Consider the case where an `update` implementation places the number of
+ // children last on the stack. LLDB will _sometimes_ (but not always) consume
+ // that value as a ChildCacheState value. This would cause downstream problems
+ // in `num_children`, because the count won't be on the stack.
+ //
+ // Bytecode authors are encouraged to explicitly push a ChildCacheState value
+ // on to the stack.
+ std::optional<ChildCacheState> cache_state = std::nullopt;
+ const FormatterBytecode::DataStackElement &top = data.back();
+ if (auto *u = std::get_if<uint64_t>(&top))
+ if (*u == 0 || *u == 1)
+ cache_state = static_cast<ChildCacheState>(*u);
+ if (auto *i = std::get_if<int64_t>(&top))
+ if (*i == 0 || *i == 1)
+ cache_state = static_cast<ChildCacheState>(*i);
+
+ if (cache_state) {
+ data.pop_back();
+ if (cache_state == ChildCacheState::eReuse)
+ LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+ "Bytecode formatter returned eReuse from `update` (type: `{0}`, "
+ "name: `{1}`)",
+ m_backend.GetDisplayTypeName(), m_backend.GetName());
+ }
+
if (data.size() > 0)
m_self = std::move(data);
- return ChildCacheState::eRefetch;
+ return cache_state.value_or(ChildCacheState::eRefetch);
}
llvm::Expected<uint32_t>
diff --git a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
index c5c2811c59439..cd4be83ad8563 100644
--- a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
@@ -5,8 +5,9 @@
class TestCase(TestBase):
+
@skipUnlessDarwin
- def test(self):
+ def test_synthetic(self):
self.build()
if self.TraceOn():
self.expect("log enable -v lldb formatters")
@@ -21,3 +22,21 @@ def test(self):
self.assertEqual(account.child[0].name, "username")
self.expect("v acc", matching=False, substrs=["password"])
+
+ @skipUnlessDarwin
+ def test_update_reuse(self):
+ self.build()
+
+ log = self.getBuildArtifact("formatter.log")
+ self.runCmd(f"log enable lldb formatters -f {log}")
+
+ _, _, thread, _ = lldbutil.run_to_source_breakpoint(
+ self, "break here", lldb.SBFileSpec("main.cpp")
+ )
+
+ frame = thread.selected_frame
+ account = frame.var("acc")
+ self.assertEqual(account.num_children, 1)
+
+ self.filecheck(f"platform shell cat {log}", __file__)
+ # CHECK: Bytecode formatter returned eReuse from `update` (type: `Account`, name: `acc`)
diff --git a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/main.cpp b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/main.cpp
index 3e6ae77bbca83..051e44c937bed 100644
--- a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/main.cpp
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/main.cpp
@@ -16,10 +16,13 @@ int main(int argc, char **argv) {
__attribute__((used, section("__DATA_CONST,__lldbformatters"))) unsigned char
_Account_synthetic[] =
"\x01" // version
- "\x15" // remaining record size
+ "\x19" // remaining record size
"\x07" // type name size
"Account" // type name
"\x00" // flags
+ "\x06" // sig_update
+ "\x02" // program size
+ "\x20\x01" // `return eReuse`
"\x02" // sig_get_num_children
"\x02" // program size
"\x20\x01" // `return 1`
>From 8b2f81906d427b2399f0db9a5bd5ecd34f6c9e35 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Thu, 12 Feb 2026 10:33:26 -0800
Subject: [PATCH 2/4] python formatting
---
.../data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
index cd4be83ad8563..a90415a02f99e 100644
--- a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
@@ -5,7 +5,6 @@
class TestCase(TestBase):
-
@skipUnlessDarwin
def test_synthetic(self):
self.build()
>From e5f73b34d3e3e297b2e7195fa791ffd8087cf2fd Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Tue, 17 Feb 2026 17:58:26 -0800
Subject: [PATCH 3/4] Always return eReuse from Update()
---
lldb/source/DataFormatters/TypeSynthetic.cpp | 36 ++-----------------
.../TestBytecodeSynthetic.py | 20 +----------
.../bytecode-synthetic/main.cpp | 5 +--
3 files changed, 5 insertions(+), 56 deletions(-)
diff --git a/lldb/source/DataFormatters/TypeSynthetic.cpp b/lldb/source/DataFormatters/TypeSynthetic.cpp
index 32d035eb8a90b..7409dbf572d16 100644
--- a/lldb/source/DataFormatters/TypeSynthetic.cpp
+++ b/lldb/source/DataFormatters/TypeSynthetic.cpp
@@ -22,7 +22,6 @@
#include "lldb/Utility/StreamString.h"
#include "llvm/Support/Error.h"
#include <cstdint>
-#include <optional>
using namespace lldb;
using namespace lldb_private;
@@ -292,41 +291,12 @@ lldb::ChildCacheState BytecodeSyntheticChildren::FrontEnd::Update() {
return ChildCacheState::eRefetch;
}
- // If the top of the stack is a 0 or 1, that value is popped of the stack and
- // treated as a return value of eRefetch or eReuse respectively. If the top of
- // the stack is any other value, it stays on the stack and becomes part of
- // `self`.
- //
- // This dynamic logic can lead to errors for synthetic formatter authors.
- // Consider the case where an `update` implementation places the number of
- // children last on the stack. LLDB will _sometimes_ (but not always) consume
- // that value as a ChildCacheState value. This would cause downstream problems
- // in `num_children`, because the count won't be on the stack.
- //
- // Bytecode authors are encouraged to explicitly push a ChildCacheState value
- // on to the stack.
- std::optional<ChildCacheState> cache_state = std::nullopt;
- const FormatterBytecode::DataStackElement &top = data.back();
- if (auto *u = std::get_if<uint64_t>(&top))
- if (*u == 0 || *u == 1)
- cache_state = static_cast<ChildCacheState>(*u);
- if (auto *i = std::get_if<int64_t>(&top))
- if (*i == 0 || *i == 1)
- cache_state = static_cast<ChildCacheState>(*i);
-
- if (cache_state) {
- data.pop_back();
- if (cache_state == ChildCacheState::eReuse)
- LLDB_LOG(GetLog(LLDBLog::DataFormatters),
- "Bytecode formatter returned eReuse from `update` (type: `{0}`, "
- "name: `{1}`)",
- m_backend.GetDisplayTypeName(), m_backend.GetName());
- }
-
if (data.size() > 0)
m_self = std::move(data);
- return cache_state.value_or(ChildCacheState::eRefetch);
+ // At this time it is assumed that synthetic bytecode formatters can always
+ // reuse the work performed in `update`.
+ return ChildCacheState::eReuse;
}
llvm::Expected<uint32_t>
diff --git a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
index a90415a02f99e..c5c2811c59439 100644
--- a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/TestBytecodeSynthetic.py
@@ -6,7 +6,7 @@
class TestCase(TestBase):
@skipUnlessDarwin
- def test_synthetic(self):
+ def test(self):
self.build()
if self.TraceOn():
self.expect("log enable -v lldb formatters")
@@ -21,21 +21,3 @@ def test_synthetic(self):
self.assertEqual(account.child[0].name, "username")
self.expect("v acc", matching=False, substrs=["password"])
-
- @skipUnlessDarwin
- def test_update_reuse(self):
- self.build()
-
- log = self.getBuildArtifact("formatter.log")
- self.runCmd(f"log enable lldb formatters -f {log}")
-
- _, _, thread, _ = lldbutil.run_to_source_breakpoint(
- self, "break here", lldb.SBFileSpec("main.cpp")
- )
-
- frame = thread.selected_frame
- account = frame.var("acc")
- self.assertEqual(account.num_children, 1)
-
- self.filecheck(f"platform shell cat {log}", __file__)
- # CHECK: Bytecode formatter returned eReuse from `update` (type: `Account`, name: `acc`)
diff --git a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/main.cpp b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/main.cpp
index 051e44c937bed..3e6ae77bbca83 100644
--- a/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/main.cpp
+++ b/lldb/test/API/functionalities/data-formatter/bytecode-synthetic/main.cpp
@@ -16,13 +16,10 @@ int main(int argc, char **argv) {
__attribute__((used, section("__DATA_CONST,__lldbformatters"))) unsigned char
_Account_synthetic[] =
"\x01" // version
- "\x19" // remaining record size
+ "\x15" // remaining record size
"\x07" // type name size
"Account" // type name
"\x00" // flags
- "\x06" // sig_update
- "\x02" // program size
- "\x20\x01" // `return eReuse`
"\x02" // sig_get_num_children
"\x02" // program size
"\x20\x01" // `return 1`
>From 585673509f885453804cecf7df30951601cdf1c3 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Wed, 25 Mar 2026 14:52:02 -0700
Subject: [PATCH 4/4] Revert back to allowing @update control over reuse
---
lldb/source/DataFormatters/TypeSynthetic.cpp | 27 +++++++++++++++++---
1 file changed, 24 insertions(+), 3 deletions(-)
diff --git a/lldb/source/DataFormatters/TypeSynthetic.cpp b/lldb/source/DataFormatters/TypeSynthetic.cpp
index 7409dbf572d16..087095a4c0d83 100644
--- a/lldb/source/DataFormatters/TypeSynthetic.cpp
+++ b/lldb/source/DataFormatters/TypeSynthetic.cpp
@@ -291,12 +291,33 @@ lldb::ChildCacheState BytecodeSyntheticChildren::FrontEnd::Update() {
return ChildCacheState::eRefetch;
}
+ std::optional<ChildCacheState> can_reuse = std::nullopt;
+ const FormatterBytecode::DataStackElement &top = data.back();
+ if (auto *u = std::get_if<uint64_t>(&top))
+ if (*u == 0 || *u == 1)
+ can_reuse = static_cast<ChildCacheState>(*u);
+ if (auto *i = std::get_if<int64_t>(&top))
+ if (*i == 0 || *i == 1)
+ can_reuse = static_cast<ChildCacheState>(*i);
+
+ if (can_reuse) {
+ data.pop_back();
+ LLDB_LOG(
+ GetLog(LLDBLog::DataFormatters),
+ "Bytecode formatter can reuse @update: {0} (type: `{1}`, name: `{2}`)",
+ can_reuse ? "true" : "false", m_backend.GetDisplayTypeName(),
+ m_backend.GetName());
+ } else {
+ LLDB_LOG(GetLog(LLDBLog::DataFormatters),
+ "Bytecode formatter did not return a valid reuse response from "
+ "@update (type: `{1}`, name: `{2}`)",
+ m_backend.GetDisplayTypeName(), m_backend.GetName());
+ }
+
if (data.size() > 0)
m_self = std::move(data);
- // At this time it is assumed that synthetic bytecode formatters can always
- // reuse the work performed in `update`.
- return ChildCacheState::eReuse;
+ return can_reuse.value_or(ChildCacheState::eRefetch);
}
llvm::Expected<uint32_t>
More information about the lldb-commits
mailing list