[Lldb-commits] [lldb] [lldb] Add arithmetic binary addition to DIL (PR #177208)
Ilia Kuklin via lldb-commits
lldb-commits at lists.llvm.org
Wed Jan 21 09:56:58 PST 2026
https://github.com/kuilpd updated https://github.com/llvm/llvm-project/pull/177208
>From b01fc9e9eb2f65369d37724358037d3bfbdca03b Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Tue, 20 Jan 2026 21:24:32 +0500
Subject: [PATCH 1/2] [lldb] Add binary addition to DIL
---
lldb/docs/dil-expr-lang.ebnf | 4 +-
lldb/include/lldb/ValueObject/DILAST.h | 35 ++++
lldb/include/lldb/ValueObject/DILEval.h | 16 ++
lldb/include/lldb/ValueObject/DILParser.h | 1 +
lldb/source/ValueObject/DILAST.cpp | 14 ++
lldb/source/ValueObject/DILEval.cpp | 155 ++++++++++++++++++
lldb/source/ValueObject/DILParser.cpp | 22 ++-
.../Arithmetic/TestFrameVarDILArithmetic.py | 35 +++-
.../frame/var-dil/expr/Arithmetic/main.cpp | 17 ++
9 files changed, 296 insertions(+), 3 deletions(-)
diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf
index 99a8b0fcaa006..127c1d6337efa 100644
--- a/lldb/docs/dil-expr-lang.ebnf
+++ b/lldb/docs/dil-expr-lang.ebnf
@@ -3,7 +3,9 @@
(* This is currently a subset of the final DIL Language, matching the current
DIL implementation. *)
-expression = cast_expression;
+expression = additive_expression ;
+
+additive_expression = cast_expression {"+" cast_expression} ;
cast_expression = unary_expression
| "(" type_id ")" cast_expression;
diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h
index da7659959093a..226f80ac70c5e 100644
--- a/lldb/include/lldb/ValueObject/DILAST.h
+++ b/lldb/include/lldb/ValueObject/DILAST.h
@@ -9,6 +9,7 @@
#ifndef LLDB_VALUEOBJECT_DILAST_H
#define LLDB_VALUEOBJECT_DILAST_H
+#include "lldb/ValueObject/DILLexer.h"
#include "lldb/ValueObject/ValueObject.h"
#include "llvm/Support/Error.h"
#include <cstdint>
@@ -19,6 +20,7 @@ namespace lldb_private::dil {
/// The various types DIL AST nodes (used by the DIL parser).
enum class NodeKind {
eArraySubscriptNode,
+ eBinaryOpNode,
eBitExtractionNode,
eBooleanLiteralNode,
eCastNode,
@@ -38,6 +40,14 @@ enum class UnaryOpKind {
Plus, // "+"
};
+/// The binary operators recognized by DIL.
+enum class BinaryOpKind {
+ Add, // "+"
+};
+
+/// Translates DIL tokens to BinaryOpKind.
+BinaryOpKind GetBinaryOpKindFromToken(Token::Kind token_kind);
+
/// The type casts allowed by DIL.
enum class CastKind {
eArithmetic, ///< Casting to a scalar.
@@ -148,6 +158,29 @@ class UnaryOpNode : public ASTNode {
ASTNodeUP m_operand;
};
+class BinaryOpNode : public ASTNode {
+public:
+ BinaryOpNode(uint32_t location, BinaryOpKind kind, ASTNodeUP lhs,
+ ASTNodeUP rhs)
+ : ASTNode(location, NodeKind::eBinaryOpNode), m_kind(kind),
+ m_lhs(std::move(lhs)), m_rhs(std::move(rhs)) {}
+
+ llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override;
+
+ BinaryOpKind GetKind() const { return m_kind; }
+ ASTNode &GetLHS() const { return *m_lhs; }
+ ASTNode &GetRHS() const { return *m_rhs; }
+
+ static bool classof(const ASTNode *node) {
+ return node->GetKind() == NodeKind::eBinaryOpNode;
+ }
+
+private:
+ BinaryOpKind m_kind;
+ ASTNodeUP m_lhs;
+ ASTNodeUP m_rhs;
+};
+
class ArraySubscriptNode : public ASTNode {
public:
ArraySubscriptNode(uint32_t location, ASTNodeUP base, ASTNodeUP index)
@@ -292,6 +325,8 @@ class Visitor {
virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const UnaryOpNode &node) = 0;
virtual llvm::Expected<lldb::ValueObjectSP>
+ Visit(const BinaryOpNode &node) = 0;
+ virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const ArraySubscriptNode &node) = 0;
virtual llvm::Expected<lldb::ValueObjectSP>
Visit(const BitFieldExtractionNode &node) = 0;
diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h
index 550f5083b1dc6..6807a6616b846 100644
--- a/lldb/include/lldb/ValueObject/DILEval.h
+++ b/lldb/include/lldb/ValueObject/DILEval.h
@@ -57,6 +57,7 @@ class Interpreter : Visitor {
Visit(const IdentifierNode &node) override;
llvm::Expected<lldb::ValueObjectSP> Visit(const MemberOfNode &node) override;
llvm::Expected<lldb::ValueObjectSP> Visit(const UnaryOpNode &node) override;
+ llvm::Expected<lldb::ValueObjectSP> Visit(const BinaryOpNode &node) override;
llvm::Expected<lldb::ValueObjectSP>
Visit(const ArraySubscriptNode &node) override;
llvm::Expected<lldb::ValueObjectSP>
@@ -73,6 +74,21 @@ class Interpreter : Visitor {
/// includes array-to-pointer and integral promotion for eligible types.
llvm::Expected<lldb::ValueObjectSP>
UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location);
+
+ /// Perform an arithmetic conversion on two values from an arithmetic
+ /// operation.
+ /// \returns The result type of an arithmetic operation.
+ llvm::Expected<CompilerType> ArithmeticConversion(lldb::ValueObjectSP &lhs,
+ lldb::ValueObjectSP &rhs,
+ uint32_t location);
+ llvm::Expected<lldb::ValueObjectSP> EvaluateScalarOp(BinaryOpKind kind,
+ lldb::ValueObjectSP lhs,
+ lldb::ValueObjectSP rhs,
+ CompilerType result_type,
+ uint32_t location);
+ llvm::Expected<lldb::ValueObjectSP>
+ EvaluateBinaryAddition(lldb::ValueObjectSP lhs, lldb::ValueObjectSP rhs,
+ uint32_t location);
llvm::Expected<CompilerType>
PickIntegerType(lldb::TypeSystemSP type_system,
std::shared_ptr<ExecutionContextScope> ctx,
diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h
index bd2fc373cd9b5..d36e9084dfcb5 100644
--- a/lldb/include/lldb/ValueObject/DILParser.h
+++ b/lldb/include/lldb/ValueObject/DILParser.h
@@ -87,6 +87,7 @@ class DILParser {
ASTNodeUP Run();
ASTNodeUP ParseExpression();
+ ASTNodeUP ParseAdditiveExpression();
ASTNodeUP ParseUnaryExpression();
ASTNodeUP ParsePostfixExpression();
ASTNodeUP ParsePrimaryExpression();
diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp
index d8a714d33712d..180708f79c269 100644
--- a/lldb/source/ValueObject/DILAST.cpp
+++ b/lldb/source/ValueObject/DILAST.cpp
@@ -11,6 +11,16 @@
namespace lldb_private::dil {
+BinaryOpKind GetBinaryOpKindFromToken(Token::Kind token_kind) {
+ switch (token_kind) {
+ case Token::plus:
+ return BinaryOpKind::Add;
+ default:
+ break;
+ }
+ llvm_unreachable("Unknown binary operator kind.");
+}
+
llvm::Expected<lldb::ValueObjectSP> ErrorNode::Accept(Visitor *v) const {
llvm_unreachable("Attempting to Visit a DIL ErrorNode.");
}
@@ -27,6 +37,10 @@ llvm::Expected<lldb::ValueObjectSP> UnaryOpNode::Accept(Visitor *v) const {
return v->Visit(*this);
}
+llvm::Expected<lldb::ValueObjectSP> BinaryOpNode::Accept(Visitor *v) const {
+ return v->Visit(*this);
+}
+
llvm::Expected<lldb::ValueObjectSP>
ArraySubscriptNode::Accept(Visitor *v) const {
return v->Visit(*this);
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index 68a2a228962fb..e6f701b54c7f4 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -139,6 +139,85 @@ Interpreter::UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location) {
return valobj;
}
+static size_t IntegerConversionRank(CompilerType type) {
+ switch (type.GetCanonicalType().GetBasicTypeEnumeration()) {
+ case lldb::eBasicTypeBool:
+ return 1;
+ case lldb::eBasicTypeChar:
+ case lldb::eBasicTypeSignedChar:
+ case lldb::eBasicTypeUnsignedChar:
+ return 2;
+ case lldb::eBasicTypeShort:
+ case lldb::eBasicTypeUnsignedShort:
+ return 3;
+ case lldb::eBasicTypeInt:
+ case lldb::eBasicTypeUnsignedInt:
+ return 4;
+ case lldb::eBasicTypeLong:
+ case lldb::eBasicTypeUnsignedLong:
+ return 5;
+ case lldb::eBasicTypeLongLong:
+ case lldb::eBasicTypeUnsignedLongLong:
+ return 6;
+ case lldb::eBasicTypeInt128:
+ case lldb::eBasicTypeUnsignedInt128:
+ return 7;
+ default:
+ break;
+ }
+ return 0;
+}
+
+llvm::Expected<CompilerType>
+Interpreter::ArithmeticConversion(lldb::ValueObjectSP &lhs,
+ lldb::ValueObjectSP &rhs, uint32_t location) {
+ // Apply unary conversion for both operands.
+ auto lhs_or_err = UnaryConversion(lhs, location);
+ if (!lhs_or_err)
+ return lhs_or_err.takeError();
+ lhs = *lhs_or_err;
+ auto rhs_or_err = UnaryConversion(rhs, location);
+ if (!rhs_or_err)
+ return rhs_or_err.takeError();
+ rhs = *rhs_or_err;
+
+ CompilerType lhs_type = lhs->GetCompilerType();
+ CompilerType rhs_type = rhs->GetCompilerType();
+
+ if (lhs_type.CompareTypes(rhs_type))
+ return lhs_type;
+
+ // If either of the operands is not arithmetic (e.g. pointer), we're done.
+ if (!lhs_type.IsScalarType() || !rhs_type.IsScalarType())
+ return CompilerType();
+
+ // Handle conversions for floating types (float, double).
+ if (lhs_type.IsFloat() || rhs_type.IsFloat()) {
+ // If both are floats, convert the smaller operand to the bigger.
+ if (lhs_type.IsFloat() && rhs_type.IsFloat()) {
+ if (lhs_type.GetBasicTypeEnumeration() >
+ rhs_type.GetBasicTypeEnumeration())
+ return lhs_type;
+ return rhs_type;
+ }
+ if (lhs_type.IsFloat() && rhs_type.IsInteger())
+ return lhs_type;
+ return rhs_type;
+ }
+
+ if (lhs_type.IsInteger() && rhs_type.IsInteger()) {
+ using Rank = std::tuple<size_t, bool>;
+ Rank l_rank = {IntegerConversionRank(lhs_type), !lhs_type.IsSigned()};
+ Rank r_rank = {IntegerConversionRank(rhs_type), !rhs_type.IsSigned()};
+
+ if (l_rank < r_rank)
+ return rhs_type;
+ if (l_rank > r_rank)
+ return lhs_type;
+ }
+ return rhs_type;
+}
+
static lldb::VariableSP DILFindVariable(ConstString name,
VariableList &variable_list) {
lldb::VariableSP exact_match;
@@ -405,6 +484,82 @@ Interpreter::Visit(const UnaryOpNode &node) {
node.GetLocation());
}
+llvm::Expected<lldb::ValueObjectSP>
+Interpreter::EvaluateScalarOp(BinaryOpKind kind, lldb::ValueObjectSP lhs,
+ lldb::ValueObjectSP rhs, CompilerType result_type,
+ uint32_t location) {
+ Scalar l, r;
+ bool l_resolved = lhs->ResolveValue(l);
+ bool r_resolved = rhs->ResolveValue(r);
+
+ if (!l_resolved || !r_resolved)
+ return llvm::make_error<DILDiagnosticError>(m_expr, "invalid scalar value",
+ location);
+
+ auto value_object = [this, result_type](Scalar scalar) {
+ return ValueObject::CreateValueObjectFromScalar(m_target, scalar,
+ result_type, "result");
+ };
+
+ switch (kind) {
+ case BinaryOpKind::Add:
+ return value_object(l + r);
+ }
+ return llvm::make_error<DILDiagnosticError>(
+ m_expr, "invalid arithmetic operation", location);
+}
+
+llvm::Expected<lldb::ValueObjectSP> Interpreter::EvaluateBinaryAddition(
+ lldb::ValueObjectSP lhs, lldb::ValueObjectSP rhs, uint32_t location) {
+ // Operation '+' works for:
+ // {scalar,unscoped_enum} <-> {scalar,unscoped_enum}
+ // TODO: Pointer arithmetics
+ auto orig_lhs_type = lhs->GetCompilerType();
+ auto orig_rhs_type = rhs->GetCompilerType();
+ auto type_or_err = ArithmeticConversion(lhs, rhs, location);
+ if (!type_or_err)
+ return type_or_err.takeError();
+ CompilerType result_type = *type_or_err;
+
+ if (result_type.IsScalarType())
+ return EvaluateScalarOp(BinaryOpKind::Add, lhs, rhs, result_type, location);
+
+ std::string errMsg =
+ llvm::formatv("invalid operands to binary expression ('{0}' and '{1}')",
+ orig_lhs_type.GetTypeName(), orig_rhs_type.GetTypeName());
+ return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location);
+}
+
+llvm::Expected<lldb::ValueObjectSP>
+Interpreter::Visit(const BinaryOpNode &node) {
+ auto lhs_or_err = EvaluateAndDereference(node.GetLHS());
+ if (!lhs_or_err)
+ return lhs_or_err;
+ lldb::ValueObjectSP lhs = *lhs_or_err;
+ auto rhs_or_err = EvaluateAndDereference(node.GetRHS());
+ if (!rhs_or_err)
+ return rhs_or_err;
+ lldb::ValueObjectSP rhs = *rhs_or_err;
+
+ lldb::TypeSystemSP lhs_system =
+ lhs->GetCompilerType().GetTypeSystem().GetSharedPointer();
+ lldb::TypeSystemSP rhs_system =
+ rhs->GetCompilerType().GetTypeSystem().GetSharedPointer();
+ if (lhs_system->GetPluginName() != rhs_system->GetPluginName()) {
+ // TODO: Attempt to convert values to current CU's type system
+ return llvm::make_error<DILDiagnosticError>(
+ m_expr, "operands have different type systems", node.GetLocation());
+ }
+
+ switch (node.GetKind()) {
+ case BinaryOpKind::Add:
+ return EvaluateBinaryAddition(lhs, rhs, node.GetLocation());
+ }
+
+ return llvm::make_error<DILDiagnosticError>(
+ m_expr, "unimplemented binary operation", node.GetLocation());
+}
+
llvm::Expected<lldb::ValueObjectSP>
Interpreter::Visit(const MemberOfNode &node) {
auto base_or_err = Evaluate(node.GetBase());
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index 7dc284c8e070e..2283e96ff95e2 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -85,7 +85,27 @@ ASTNodeUP DILParser::Run() {
// expression:
// cast_expression
//
-ASTNodeUP DILParser::ParseExpression() { return ParseCastExpression(); }
+ASTNodeUP DILParser::ParseExpression() { return ParseAdditiveExpression(); }
+
+// Parse an additive_expression.
+//
+// additive_expression:
+// cast_expression {"+" cast_expression}
+//
+ASTNodeUP DILParser::ParseAdditiveExpression() {
+ auto lhs = ParseCastExpression();
+
+ while (CurToken().Is(Token::plus)) {
+ Token token = CurToken();
+ m_dil_lexer.Advance();
+ auto rhs = ParseCastExpression();
+ lhs = std::make_unique<BinaryOpNode>(
+ token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()),
+ std::move(lhs), std::move(rhs));
+ }
+
+ return lhs;
+}
// Parse a cast_expression.
//
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py
index 53a85fed303f4..fe8ee99835311 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py
@@ -13,7 +13,7 @@ class TestFrameVarDILArithmetic(TestBase):
def test_arithmetic(self):
self.build()
- lldbutil.run_to_source_breakpoint(
+ (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(
self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
)
@@ -44,3 +44,36 @@ def test_arithmetic(self):
self.expect_var_path("+bitfield.b", value="2", type="int")
self.expect_var_path("+bitfield.c", value="3", type="unsigned int")
self.expect_var_path("+bitfield.d", value="4", type="uint64_t")
+
+ # Check basic math and resulting types
+ self.expect_var_path("1 + 2", value="3", type="int")
+ self.expect_var_path("1 + true", value="2", type="int")
+ self.expect_var_path("1L + wchar", value="2", type="long")
+ self.expect_var_path("1L + char16", value="3", type="long")
+ self.expect_var_path("1LL + char32", value="4", type="long long")
+ self.expect_var_path("1UL + 1L", value="2", type="unsigned long")
+ self.expect_var_path("s + x", value="12", type="int")
+ self.expect_var_path("s + l", value="15", type="long")
+ self.expect_var_path("1.0 + 2.5", value="3.5", type="double")
+ self.expect_var_path("1 + 2.5f", value="3.5", type="float")
+ self.expect_var_path("2. + .5", value="2.5", type="double")
+ self.expect_var_path("2.f + .5f", value="2.5", type="float")
+ self.expect_var_path("f + d", value="3.5", type="double")
+
+ # Check limits and overflows
+ frame = thread.GetFrameAtIndex(0)
+ int_min = frame.GetValueForVariablePath("int_min").GetValue()
+ int_max = frame.GetValueForVariablePath("int_max").GetValue()
+ uint_max = frame.GetValueForVariablePath("uint_max").GetValue()
+ ll_max = frame.GetValueForVariablePath("ll_max").GetValue()
+ ll_min = frame.GetValueForVariablePath("ll_min").GetValue()
+ ull_max = frame.GetValueForVariablePath("ull_max").GetValue()
+ self.expect_var_path("int_max + 1", value=int_min)
+ self.expect_var_path("uint_max + 1", value="0")
+ self.expect_var_path("ll_max + 1", value=ll_min)
+ self.expect_var_path("ull_max + 1", value="0")
+
+ # Check references and typedefs
+ self.expect_var_path("ref + 1", value="3")
+ self.expect_var_path("my_ref + 1", value="3")
+ self.expect_var_path("ref + my_ref", value="4")
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/main.cpp
index 2c70e93433f5f..129a4214a42c4 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/main.cpp
+++ b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/main.cpp
@@ -1,11 +1,19 @@
#include <cstdint>
+#include <limits>
int main(int argc, char **argv) {
short s = 10;
unsigned short us = 1;
+ long l = 5;
+ float f = 1.0f;
+ double d = 2.5;
int x = 2;
int &ref = x;
+ int *p = &x;
+ typedef int &myref;
+ myref my_ref = x;
+
enum Enum { kZero, kOne } enum_one = kOne;
wchar_t wchar = 1;
char16_t char16 = 2;
@@ -19,5 +27,14 @@ int main(int argc, char **argv) {
};
BitFieldStruct bitfield = {1, 2, 3, 4};
+ int int_max = std::numeric_limits<int>::max();
+ int int_min = std::numeric_limits<int>::min();
+ unsigned int uint_max = std::numeric_limits<unsigned int>::max();
+ unsigned int uint_zero = 0;
+ long long ll_max = std::numeric_limits<long long>::max();
+ long long ll_min = std::numeric_limits<long long>::min();
+ unsigned long long ull_max = std::numeric_limits<unsigned long long>::max();
+ unsigned long long ull_zero = 0;
+
return 0; // Set a breakpoint here
}
>From 22be3635bfb1b43d1563a062de5691be7cadc86d Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Wed, 21 Jan 2026 22:55:54 +0500
Subject: [PATCH 2/2] Add + to DWIMPrint exclusion list
---
lldb/source/Commands/CommandObjectDWIMPrint.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
index 40f00c90bbbfb..88b1ad79c78cb 100644
--- a/lldb/source/Commands/CommandObjectDWIMPrint.cpp
+++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
@@ -164,7 +164,7 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
// both operators can be overloaded in C++, and could result in ambiguity in
// how the expression is handled. Additionally, `*` and `&` are not supported.
const bool try_variable_path =
- expr.find_first_of("*&->[]") == StringRef::npos;
+ expr.find_first_of("*&->[]+") == StringRef::npos;
if (frame && try_variable_path) {
VariableSP var_sp;
Status status;
More information about the lldb-commits
mailing list