[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