summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSam Estep <sam@samestep.com>2025-08-18 11:29:57 -0400
committerGitHub <noreply@github.com>2025-08-18 15:29:57 +0000
commitd9851d4e45d2344e0e220ef6199ef4e117066f0f (patch)
treec5e4453f4ffeb538cca1e5819f3562f54c14fff4
parent09054bff3d0874a92958b514ae2a9ff2b32483e5 (diff)
Make LLDB IR data formatters more robust (#7927)
This is a followup on #7828 to fix bugs that were causing CodeLLDB to give wrong values and hang (see vadimcn/codelldb#1302) because I didn't realize that these data formatters can be passed _either_ a value of a given type _or_ a pointer to a value of that type, and need to handle both cases. I also introduced loop bounds to prevent hangs in the case where these synthetic values are constructed for things like uninitialized variables. From looking at the preexisting data formatters from #4272 in `source/core/core_lldb.py`, it seems like they _technically_ have similar bugs to this, but since those types are simpler, it's unclear to me whether that can actually manifest in meaningful ways like these bugs in `source/slang/slang_lldb.py` were doing. Anyways, to test this, put a breakpoint here: https://github.com/shader-slang/slang/blob/6d399804a353154259cf4410940f144db8f9b5cf/source/slang/slang-emit-cpp.cpp#L1733 And use this `.vscode/launch.json` for CodeLLDB: ```json { "version": "0.2.0", "configurations": [ { "name": "LLDB", "preLaunchTask": "Debug build", "type": "lldb", "request": "launch", "initCommands": ["command source .lldbinit"], "program": "build/Debug/bin/slangc", "args": [ "tests/cpu-program/cpu-hello-world-test.slang", "-target", "executable", "-o", "hello" ] } ] } ``` Before this PR, the `inst` variable will display in the debug pane as `{kIROp_StringLit 0x00007fffffff5f68}`, which is the wrong pointer value. You can also check this by running `p inst` in the Debug Console, which will print this: ``` (Slang::IRInst *) 0x000055555fdac3b8 {kIROp_StringLit 0x00007fffffff5f68} ``` In contrast, running `p *inst` prints the correct pointer value: ``` (Slang::IRInst) {kIROp_StringLit 0x000055555fdac3b8} { [op] = kIROp_StringLit [UID] = 76 [type] = 0x000055555fdac348 {kIROp_StringType None} [decorations/children] = {} [parent] = 0x000055555fdac2d0 {kIROp_ModuleInst None} [uses] = 0x000055555fdadf18 {kIROp_StringLit 0x000055555fdac3b8} } ``` But as you can see, in that case the synthetic `[value]` child is completely missing. Then if you try to expand `inst` in the debug pane, CodeLLDB will hang (or at least it does when I try this). After this PR, the hex integer for the pointer is always consistent, and CodeLLDB does not hang in the debug pane when you expand `inst`, and shows the correct `[value]` child just like when running `v *inst`. As an aside: after this PR, the `[value]` child is still missing when specifically running `p *inst` in the Debug Console. It _is_ possible to fix this: ```diff diff --git a/source/slang/slang_lldb.py b/source/slang/slang_lldb.py index 23905d8c5..d2b3a4da9 100644 --- a/source/slang/slang_lldb.py +++ b/source/slang/slang_lldb.py @@ -93,13 +93,11 @@ class IRInst_synthetic(lldb.SBSyntheticValueProvider): value: list[tuple[str, lldb.SBValue]] = [] match op.value: case "kIROp_StringLit": - string_lit_t = target.FindFirstType("Slang::IRStringLit") - string_lit = self.valobj.Cast(string_lit_t) + string_lit = self.valobj.EvaluateExpression("(Slang::IRStringLit*)this") val = string_lit.GetChildMemberWithName("value") value = [("[value]", val.GetChildMemberWithName("stringVal"))] case "kIROp_IntLit": - int_lit_t = target.FindFirstType("Slang::IRIntLit") - int_lit = self.valobj.Cast(int_lit_t) + int_lit = self.valobj.EvaluateExpression("(Slang::IRIntLit*)this") val = int_lit.GetChildMemberWithName("value") value = [("[value]", val.GetChildMemberWithName("intVal"))] diff --git a/typings/lldb.pyi b/typings/lldb.pyi index 2672ba244..3a08e9141 100644 --- a/typings/lldb.pyi +++ b/typings/lldb.pyi @@ -496,7 +496,7 @@ class SBValue: def Persist(self): ... def GetDescription(self, description): ... def GetExpressionPath(self, *args): ... - def EvaluateExpression(self, *args): ... + def EvaluateExpression(self, expr: str) -> SBValue: ... def Watch(self, *args): ... def WatchPointee(self, resolve_location, read, write, error): ... def GetVTable(self): ... ``` However, that makes the debugger run _significantly_ slower, so I'm choosing not do do it here. --------- Co-authored-by: Ellie Hermaszewska <ellieh@nvidia.com>
-rw-r--r--source/slang/slang_lldb.py32
-rw-r--r--typings/lldb.pyi4
2 files changed, 33 insertions, 3 deletions
diff --git a/source/slang/slang_lldb.py b/source/slang/slang_lldb.py
index d4cb3b6f6..a5d2500af 100644
--- a/source/slang/slang_lldb.py
+++ b/source/slang/slang_lldb.py
@@ -57,6 +57,15 @@ class IRInstListBase_synthetic(lldb.SBSyntheticValueProvider):
self.children.append(child.Clone(f"[{i}]"))
pointer = child.GetNonSyntheticValue().GetChildMemberWithName("next")
i += 1
+ if i >= 5000:
+ # The debugger can call this function on uninitialized
+ # values, so we need to ensure that we stop iterating at
+ # some point. Ideally we'd provide a synthetic child
+ # called something like `[more]` to see another batch of
+ # many children in the case where there really is just a
+ # very large list, but this is good enough because one
+ # can always manually follow `next` pointers.
+ break
def has_children(self):
return True
@@ -77,12 +86,26 @@ class IRInst_synthetic(lldb.SBSyntheticValueProvider):
def update(self):
self.children = Children()
+
+ if self.valobj.type.IsPointerType():
+ if self.valobj.unsigned != 0:
+ valobj = self.valobj.deref
+ for i in range(valobj.GetNumChildren()):
+ self.children.append(valobj.GetChildAtIndex(i))
+ return
+
target = self.valobj.target
ty = self.valobj.type
op = self.valobj.GetChildMemberWithName("m_op")
# literal values
value: list[tuple[str, lldb.SBValue]] = []
+ # Using `Cast` here seems to work just fine in the LLDB CLI with
+ # `v`, as well as in CodeLLDB, but for some reason it does not
+ # work correctly with `p`, causing the `[value]` child to be
+ # missing in that case. It is possible to fix that by using
+ # `EvaluateExpression` instead, but that significantly degrades
+ # performance, so we choose not to do it here.
match op.value:
case "kIROp_StringLit":
string_lit_t = target.FindFirstType("Slang::IRStringLit")
@@ -100,7 +123,12 @@ class IRInst_synthetic(lldb.SBSyntheticValueProvider):
offset = ty.GetByteSize()
ir_use_t = target.FindFirstType("Slang::IRUse")
ir_use_size = ir_use_t.GetByteSize()
- for index in range(self.valobj.GetChildMemberWithName("operandCount").unsigned):
+ operand_count = self.valobj.GetChildMemberWithName("operandCount").unsigned
+ # We must ensure that we don't loop for an unbounded amount of
+ # time, so we cap the number of operands displayed here. Ideally
+ # we'd provide a way to view more in the case of instructions
+ # with more than this many operands, though.
+ for index in range(min(operand_count, 10)):
name = f"[operand{index}]"
operand = self.valobj.CreateChildAtOffset(
name, offset + index * ir_use_size, ir_use_t
@@ -137,6 +165,8 @@ class IRInst_synthetic(lldb.SBSyntheticValueProvider):
def IRInst_summary(valobj: lldb.SBValue, dict) -> str:
+ if valobj.type.IsPointerType():
+ return "nullptr" if valobj.unsigned == 0 else valobj.deref.summary
val = valobj.GetNonSyntheticValue()
op = val.GetChildMemberWithName("m_op")
return f"{{{op.value} {val.address_of.value}}}"
diff --git a/typings/lldb.pyi b/typings/lldb.pyi
index 646bb46b2..658f8add2 100644
--- a/typings/lldb.pyi
+++ b/typings/lldb.pyi
@@ -333,7 +333,7 @@ class SBType:
def IsValid(self): ...
def GetByteSize(self) -> int: ...
def GetByteAlign(self): ...
- def IsPointerType(self): ...
+ def IsPointerType(self) -> bool: ...
def IsReferenceType(self): ...
def IsFunctionType(self): ...
def IsPolymorphicClass(self): ...
@@ -470,7 +470,7 @@ class SBValue:
def CreateValueFromExpression(self, *args): ...
def CreateValueFromAddress(self, name, address, type): ...
def CreateValueFromData(self, name, data, type): ...
- def GetChildAtIndex(self, *args): ...
+ def GetChildAtIndex(self, idx: int) -> SBValue: ...
def GetIndexOfChildWithName(self, name): ...
def GetChildMemberWithName(self, name: str) -> SBValue: ...
def GetValueForExpressionPath(self, expr_path): ...