[Lldb-commits] [lldb] [lldb] Add numeric operations to formatter_bytecode.Compiler (PR #235187)

Dave Lee via lldb-commits lldb-commits at lists.llvm.org
Mon Oct 26 09:47:01 PDT 2026


https://github.com/kastiglione updated https://github.com/llvm/llvm-project/pull/235187

>From 400fc741874c3db44321d955c4016de4340c0339 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Sun, 4 Oct 2026 07:13:53 -0700
Subject: [PATCH 1/2] [lldb] Add numeric operations to
 formatter_bytecode.Compiler

---
 lldb/examples/python/formatter_bytecode.py | 71 +++++++++++++++++++++-
 1 file changed, 70 insertions(+), 1 deletion(-)

diff --git a/lldb/examples/python/formatter_bytecode.py b/lldb/examples/python/formatter_bytecode.py
index 93d919dd995bf2..2d547f842391f7 100644
--- a/lldb/examples/python/formatter_bytecode.py
+++ b/lldb/examples/python/formatter_bytecode.py
@@ -876,6 +876,20 @@ def next_byte():
     "GetValueAsUnsigned": "@get_value_as_unsigned",
 }
 
+_BINOPS = {
+    ast.Add: "+",
+    ast.Sub: "-",
+    ast.Mult: "*",
+    # The bytecode has only integer division, which truncates.
+    ast.FloorDiv: "/",
+    ast.Mod: "%",
+    ast.LShift: "<<",
+    ast.RShift: ">>",
+    ast.BitAnd: "&",
+    ast.BitOr: "|",
+    ast.BitXor: "^",
+}
+
 _COMPS = {
     ast.Eq: "=",
     ast.NotEq: "!=",
@@ -929,8 +943,9 @@ class Compiler(ast.NodeVisitor):
     [x] Return statements
     [x] String, integer, float, boolean, and None literals
     [x] Binary comparisons
+    [x] Unary operators (-, +, ~)
     [ ] Boolean operators
-    [ ] Math operations
+    [x] Math operations (+, -, *, //, %, <<, >>, &, |, ^)
 
     # Unsupported Features
 
@@ -1096,6 +1111,38 @@ def visit_Return(self, node: ast.Return) -> None:
             self.visit(node.value)
         self._output("return")
 
+    def visit_BinOp(self, node: ast.BinOp) -> None:
+        if op := _BINOPS.get(type(node.op)):
+            self.visit(node.left)
+            self.visit(node.right)
+            self._output(op)
+        else:
+            raise CompilerError(
+                f"unsupported binary operator: {type(node.op).__name__}", node
+            )
+
+    def visit_UnaryOp(self, node: ast.UnaryOp) -> None:
+        operand = node.operand
+        if isinstance(node.op, ast.USub):
+            # Fold negative integer literals.
+            if (
+                isinstance(operand, ast.Constant)
+                and isinstance(operand.value, int)
+                and not isinstance(operand.value, bool)
+            ):
+                self._output(-operand.value)
+                return
+            self._output(0)
+            self.visit(operand)
+            self._output("-")
+        elif isinstance(node.op, ast.UAdd):
+            self.visit(operand)
+        elif isinstance(node.op, ast.Invert):
+            self.visit(operand)
+            self._output("~")
+        else:
+            raise CompilerError("unsupported unary operator", node)
+
     def visit_Constant(self, node: ast.Constant) -> None:
         if isinstance(node.value, str):
             self._output(_string_literal(node.value))
@@ -1653,6 +1700,20 @@ def get_child_index(self, name):
                 )
                 return methods["get_child_index"]
 
+        def test_unary(self):
+            methods = self.compile_methods(
+                """
+                class Synthetic:
+                    def get_child_index(self, name):
+                        return -1
+
+                    def num_children(self):
+                        return -(-5) + ~0 + +1
+                """
+            )
+            self.assertEqual(interpret(methods["get_child_index"], [], [{}, "x"]), -1)
+            self.assertEqual(interpret(methods["get_num_children"], [], [{}]), 5)
+
             def run(expr, name="x", valobj=None):
                 self_dict = {"valobj": valobj or self.FakeValue(42)}
                 return interpret(compile_fstring(expr), [], [self_dict, name])
@@ -1716,5 +1777,13 @@ def num_children(self):
                             return 1
                     """
                 )
+            with self.assertRaisesRegex(CompilerError, "unsupported binary operator"):
+                self.compile_methods(
+                    """
+                    class Synthetic:
+                        def num_children(self):
+                            return 1 / 2
+                    """
+                )
 
     unittest.main(argv=[__file__])

>From 5a29df427ae9a22c23d834c056da1c4cf17998c8 Mon Sep 17 00:00:00 2001
From: Dave Lee <davelee.com at gmail.com>
Date: Mon, 26 Oct 2026 09:46:33 -0700
Subject: [PATCH 2/2] Add negative int literal to assembler

---
 lldb/examples/python/formatter_bytecode.py | 62 +++++++++++++++-------
 1 file changed, 43 insertions(+), 19 deletions(-)

diff --git a/lldb/examples/python/formatter_bytecode.py b/lldb/examples/python/formatter_bytecode.py
index 2d547f842391f7..3ceb442faab2e0 100644
--- a/lldb/examples/python/formatter_bytecode.py
+++ b/lldb/examples/python/formatter_bytecode.py
@@ -23,7 +23,7 @@
 import textwrap
 from copy import copy
 from dataclasses import dataclass
-from typing import Any, BinaryIO, Optional, Sequence, TextIO, Tuple, Union, cast
+from typing import Any, BinaryIO, Callable, Optional, Sequence, TextIO, Tuple, Union, cast
 
 BINARY_VERSION = 2
 
@@ -403,7 +403,7 @@ def emit(byte):
             emit(op_begin)
             emit(len(block))  # FIXME: uleb
             bytecode[-1].extend(block)
-        elif tok[0].isdigit():
+        elif tok[0].isdigit() or (tok[0] == "-" and tok[1:2].isdigit()):
             if tok[-1] == "u":
                 emit(op_lit_uint)
                 emit(int(tok[:-1]))  # FIXME
@@ -411,7 +411,7 @@ def emit(byte):
                 # With the introduction of op_lit_integer, op_lit_int is no
                 # longer emitted by the assembler.
                 emit(op_lit_integer)
-                emit(int(tok))  # FIXME
+                bytecode[-1].extend(_to_sleb(int(tok)))
         elif tok[0] == "@":
             emit(op_lit_selector)
             emit(selector[tok])
@@ -487,8 +487,7 @@ def next_byte():
             b = next_byte()
             asm += str(b)
         elif b == op_lit_integer:
-            b = next_byte()
-            asm += str(b)
+            asm += str(_from_sleb(next_byte))
         elif b == op_lit_selector:
             b = next_byte()
             asm += selector[b]
@@ -694,8 +693,7 @@ def next_byte():
             b = next_byte()  # FIXME uleb
             data.append(int(b))
         elif b == op_lit_integer:
-            b = next_byte()  # FIXME sleb
-            data.append(int(b))
+            data.append(_from_sleb(next_byte))
         elif b == op_lit_selector:
             b = next_byte()
             data.append(b)
@@ -742,7 +740,7 @@ def next_byte():
         elif b == op_xor:
             data.append(data.pop() ^ data.pop())
         elif b == op_not:
-            data.append(not data.pop())
+            data.append(~int(data.pop()))
         elif b == op_eq:
             data.append(data.pop() == data.pop())
         elif b == op_neq:
@@ -1302,6 +1300,32 @@ def _to_uleb(value: int) -> bytes:
     return bytes(result)
 
 
+def _to_sleb(value: int) -> bytes:
+    """Encode an integer to SLEB128 bytes."""
+    result = bytearray()
+    while True:
+        byte = value & 0x7F
+        value >>= 7
+        done = (value == 0 and not byte & 0x40) or (value == -1 and byte & 0x40)
+        result.append(byte if done else byte | 0x80)
+        if done:
+            return bytes(result)
+
+
+def _from_sleb(next_byte: Callable[[], int]) -> int:
+    """Decode a SLEB128 integer, fetching bytes with next_byte."""
+    result = 0
+    shift = 0
+    while True:
+        byte = next_byte()
+        result |= (byte & 0x7F) << shift
+        shift += 7
+        if not (byte & 0x80):
+            if byte & 0x40:
+                result -= 1 << shift
+            return result
+
+
 def _from_uleb(stream: BinaryIO) -> int:
     """Decode a ULEB128 integer by reading bytes from the stream."""
     result = 0
@@ -1689,17 +1713,6 @@ def num_children(self):
             self.assertEqual(interpret(methods["update"], [], [self_dict]), 0)
             self.assertEqual(interpret(methods["get_num_children"], [], [self_dict]), 3)
 
-        def test_fstring(self):
-            def compile_fstring(expr):
-                methods = self.compile_methods(
-                    f"""
-                    class Synthetic:
-                        def get_child_index(self, name):
-                            return {expr}
-                    """
-                )
-                return methods["get_child_index"]
-
         def test_unary(self):
             methods = self.compile_methods(
                 """
@@ -1714,6 +1727,17 @@ def num_children(self):
             self.assertEqual(interpret(methods["get_child_index"], [], [{}, "x"]), -1)
             self.assertEqual(interpret(methods["get_num_children"], [], [{}]), 5)
 
+        def test_fstring(self):
+            def compile_fstring(expr):
+                methods = self.compile_methods(
+                    f"""
+                    class Synthetic:
+                        def get_child_index(self, name):
+                            return {expr}
+                    """
+                )
+                return methods["get_child_index"]
+
             def run(expr, name="x", valobj=None):
                 self_dict = {"valobj": valobj or self.FakeValue(42)}
                 return interpret(compile_fstring(expr), [], [self_dict, name])



More information about the lldb-commits mailing list