[Lldb-commits] [lldb] [lldb] Add comparison operators to DIL (PR #208832)
Ilia Kuklin via lldb-commits
lldb-commits at lists.llvm.org
Mon Jul 20 05:41:01 PDT 2026
https://github.com/kuilpd updated https://github.com/llvm/llvm-project/pull/208832
>From 6f77215d31c060774ed2b2c3127d49eedca3cc9f Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Thu, 9 Jul 2026 16:46:53 +0500
Subject: [PATCH 1/3] [lldb] Add comparison operators to DIL
---
lldb/docs/dil-expr-lang.ebnf | 12 +-
lldb/include/lldb/ValueObject/DILAST.h | 19 +-
lldb/include/lldb/ValueObject/DILEval.h | 7 +
lldb/include/lldb/ValueObject/DILLexer.h | 6 +
lldb/include/lldb/ValueObject/DILParser.h | 2 +
lldb/source/ValueObject/DILAST.cpp | 12 ++
lldb/source/ValueObject/DILEval.cpp | 202 ++++++++++++++++++
lldb/source/ValueObject/DILLexer.cpp | 42 ++--
lldb/source/ValueObject/DILParser.cpp | 51 ++++-
.../frame/var-dil/expr/Comparison/Makefile | 3 +
.../TestFrameVarDILExprComparison.py | 173 +++++++++++++++
.../frame/var-dil/expr/Comparison/main.cpp | 34 +++
12 files changed, 538 insertions(+), 25 deletions(-)
create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Comparison/Makefile
create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Comparison/TestFrameVarDILExprComparison.py
create mode 100644 lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp
diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf
index f3c465711e956..827dec34dd0a8 100644
--- a/lldb/docs/dil-expr-lang.ebnf
+++ b/lldb/docs/dil-expr-lang.ebnf
@@ -5,13 +5,21 @@
expression = assignment_expression ;
-assignment_expression = shift_expression
- | shift_expression assignment_operator assignment_expression ;
+assignment_expression = equality_expression
+ | equality_expression assignment_operator assignment_expression ;
assignment_operator = "="
| "+="
| "-=" ;
+equality_expression = relational_expression {"==" relational_expression}
+ | relational_expression {"!=" relational_expression} ;
+
+relational_expression = shift_expression {"<" shift_expression}
+ | shift_expression {">" shift_expression}
+ | shift_expression {"<=" shift_expression}
+ | shift_expression {">=" shift_expression} ;
+
shift_expression = additive_expression {"<<" additive_expression}
| additive_expression {">>" additive_expression} ;
diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h
index 93310a91a15bb..90c15a6352062 100644
--- a/lldb/include/lldb/ValueObject/DILAST.h
+++ b/lldb/include/lldb/ValueObject/DILAST.h
@@ -42,16 +42,22 @@ enum class UnaryOpKind {
/// The binary operators recognized by DIL.
enum class BinaryOpKind {
- Add, ///< "+"
- AddAssign, ///< "+="
Assign, ///< "="
- Div, ///< "/"
+ Add, ///< "+"
+ Sub, ///< "-"
Mul, ///< "*"
+ Div, ///< "/"
Rem, ///< "%"
Shl, ///< "<<"
Shr, ///< ">>"
- Sub, ///< "-"
+ AddAssign, ///< "+="
SubAssign, ///< "-="
+ LT, ///< "<"
+ GT, ///< ">"
+ LE, ///< "<="
+ GE, ///< ">="
+ EQ, ///< "=="
+ NE, ///< "!="
};
/// Translates DIL tokens to BinaryOpKind.
@@ -87,6 +93,8 @@ class ASTNode {
virtual llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const = 0;
+ virtual bool IsConstLiteral() const { return false; }
+
uint32_t GetLocation() const { return m_location; }
NodeKind GetKind() const { return m_kind; }
@@ -246,6 +254,7 @@ class IntegerLiteralNode : public ASTNode {
llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override;
+ bool IsConstLiteral() const override { return true; }
const llvm::APInt &GetValue() const { return m_value; }
uint32_t GetRadix() const { return m_radix; }
bool IsUnsigned() const { return m_is_unsigned; }
@@ -270,6 +279,7 @@ class FloatLiteralNode : public ASTNode {
llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override;
+ bool IsConstLiteral() const override { return true; }
const llvm::APFloat &GetValue() const { return m_value; }
static bool classof(const ASTNode &node) {
@@ -287,6 +297,7 @@ class BooleanLiteralNode : public ASTNode {
llvm::Expected<lldb::ValueObjectSP> Accept(Visitor *v) const override;
+ bool IsConstLiteral() const override { return true; }
bool GetValue() const & { return m_value; }
static bool classof(const ASTNode &node) {
diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h
index 35784ea9987f9..5b05073983855 100644
--- a/lldb/include/lldb/ValueObject/DILEval.h
+++ b/lldb/include/lldb/ValueObject/DILEval.h
@@ -106,6 +106,13 @@ class Interpreter : Visitor {
lldb::ValueObjectSP rhs,
CompilerType result_type,
uint32_t location);
+ llvm::Error ValidateComparison(BinaryOpKind kind, lldb::ValueObjectSP &lhs,
+ lldb::ValueObjectSP &rhs, bool lhs_is_literal,
+ bool rhs_is_literal, uint32_t location);
+ llvm::Expected<lldb::ValueObjectSP>
+ EvaluateComparison(BinaryOpKind kind, lldb::ValueObjectSP lhs,
+ lldb::ValueObjectSP rhs, bool lhs_is_literal,
+ bool rhs_is_literal, uint32_t location);
llvm::Expected<lldb::ValueObjectSP>
EvaluateBinaryShift(BinaryOpKind kind, lldb::ValueObjectSP lhs,
lldb::ValueObjectSP rhs, uint32_t location);
diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h
index f9f42dc59b311..263ef0353d601 100644
--- a/lldb/include/lldb/ValueObject/DILLexer.h
+++ b/lldb/include/lldb/ValueObject/DILLexer.h
@@ -31,7 +31,11 @@ class Token {
coloncolon,
eof,
equal,
+ equalequal,
+ exclaimequal,
float_constant,
+ greater,
+ greaterequal,
greatergreater,
identifier,
integer_constant,
@@ -39,6 +43,8 @@ class Token {
kw_true,
l_paren,
l_square,
+ less,
+ lessequal,
lessless,
minus,
minusequal,
diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h
index 9e2bbff4b6614..2cebb5c222fa7 100644
--- a/lldb/include/lldb/ValueObject/DILParser.h
+++ b/lldb/include/lldb/ValueObject/DILParser.h
@@ -84,6 +84,8 @@ class DILParser {
ASTNodeUP ParseExpression();
ASTNodeUP ParseAssignmentExpression();
+ ASTNodeUP ParseEqualityExpression();
+ ASTNodeUP ParseRelationalExpression();
ASTNodeUP ParseShiftExpression();
ASTNodeUP ParseAdditiveExpression();
ASTNodeUP ParseMultiplicativeExpression();
diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp
index 40bf07bdd5aab..02252d9345a5a 100644
--- a/lldb/source/ValueObject/DILAST.cpp
+++ b/lldb/source/ValueObject/DILAST.cpp
@@ -33,6 +33,18 @@ BinaryOpKind GetBinaryOpKindFromToken(Token::Kind token_kind) {
return BinaryOpKind::Shl;
case Token::greatergreater:
return BinaryOpKind::Shr;
+ case Token::less:
+ return BinaryOpKind::LT;
+ case Token::greater:
+ return BinaryOpKind::GT;
+ case Token::lessequal:
+ return BinaryOpKind::LE;
+ case Token::greaterequal:
+ return BinaryOpKind::GE;
+ case Token::equalequal:
+ return BinaryOpKind::EQ;
+ case Token::exclaimequal:
+ return BinaryOpKind::NE;
default:
break;
}
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index 4c5ac96dccf74..2947d59c80e37 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -653,6 +653,18 @@ Interpreter::EvaluateScalarOp(BinaryOpKind kind, lldb::ValueObjectSP lhs,
return value_object(l << r);
case BinaryOpKind::Shr:
return value_object(l >> r);
+ case BinaryOpKind::LT:
+ return value_object(l < r);
+ case BinaryOpKind::GT:
+ return value_object(l > r);
+ case BinaryOpKind::LE:
+ return value_object(l <= r);
+ case BinaryOpKind::GE:
+ return value_object(l >= r);
+ case BinaryOpKind::EQ:
+ return value_object(l == r);
+ case BinaryOpKind::NE:
+ return value_object(l != r);
default:
break;
}
@@ -906,6 +918,186 @@ Interpreter::EvaluateAssignment(lldb::ValueObjectSP lhs,
return lhs;
}
+static bool IsLiteralZero(lldb::ValueObjectSP &val, bool is_literal) {
+ bool is_zero = val->GetValueAsUnsigned(-1) == 0;
+ bool is_boolean = val->GetCompilerType().IsBoolean();
+ return is_zero && !is_boolean && is_literal;
+}
+
+llvm::Error
+Interpreter::ValidateComparison(BinaryOpKind kind, lldb::ValueObjectSP &lhs,
+ lldb::ValueObjectSP &rhs, bool lhs_is_literal,
+ bool rhs_is_literal, uint32_t location) {
+ auto orig_lhs_type = lhs->GetCompilerType();
+ auto orig_rhs_type = rhs->GetCompilerType();
+
+ if (orig_lhs_type == orig_rhs_type)
+ return llvm::Error::success();
+
+ bool is_ordered = (kind == BinaryOpKind::LT || kind == BinaryOpKind::LE ||
+ kind == BinaryOpKind::GT || kind == BinaryOpKind::GE);
+ bool lhs_nullptr_or_zero =
+ orig_lhs_type.IsNullPtrType() || IsLiteralZero(lhs, lhs_is_literal);
+ bool rhs_nullptr_or_zero =
+ orig_rhs_type.IsNullPtrType() || IsLiteralZero(rhs, rhs_is_literal);
+
+ if (orig_lhs_type.IsArrayType())
+ lhs = ArrayToPointerConversion(*lhs, m_stack_frame, "result");
+ if (orig_rhs_type.IsArrayType())
+ rhs = ArrayToPointerConversion(*rhs, m_stack_frame, "result");
+
+ CompilerType lhs_type = lhs->GetCompilerType();
+ CompilerType rhs_type = rhs->GetCompilerType();
+ lldb::ValueObjectSP lhs_child;
+ lldb::ValueObjectSP rhs_child;
+ bool is_signed;
+
+ if (!lhs_nullptr_or_zero && !lhs_type.IsPointerType() &&
+ !lhs_type.IsIntegerOrEnumerationType(is_signed)) {
+ // lhs is not a nullptr, pointer, enum or integer. Check to see if its
+ // first child could be a pointer. If so, update lhs_type accordingly.
+ lhs_child = lhs->GetChildAtIndex(0);
+ if (lhs_child && (lhs_child->IsPointerType() ||
+ lhs_child->GetCompilerType().IsNullPtrType()))
+ lhs_type = lhs_child->GetCompilerType();
+ }
+ if (!rhs_nullptr_or_zero && !rhs_type.IsPointerType() &&
+ !rhs_type.IsIntegerOrEnumerationType(is_signed)) {
+ // rhs is not a nullptr, pointer, enum or integer. Check to see if its
+ // first child could be a pointer. If so, update rhs_type accordingly.
+ rhs_child = rhs->GetChildAtIndex(0);
+ if (rhs_child && (rhs_child->IsPointerType() ||
+ rhs_child->GetCompilerType().IsNullPtrType()))
+ rhs_type = rhs_child->GetCompilerType();
+ }
+
+ if ((lhs_type != orig_lhs_type) || (rhs_type != orig_rhs_type)) {
+ if (lhs_type.IsNullPtrType() || rhs_type.IsNullPtrType())
+ return llvm::Error::success();
+
+ // May be an integer or enum.
+ if (!lhs_type.IsPointerType() || !rhs_type.IsPointerType())
+ return llvm::Error::success();
+
+ CompilerType lhs_unqualified =
+ lhs_type.GetCanonicalType().GetFullyUnqualifiedType();
+ CompilerType rhs_unqualified =
+ rhs_type.GetCanonicalType().GetFullyUnqualifiedType();
+
+ if (lhs_unqualified.IsPointerToVoid() || rhs_unqualified.IsPointerToVoid())
+ return llvm::Error::success();
+
+ // We have two pointers, neither of which is nullptr or void *. Make
+ // sure their types are compatible.
+ bool comparable = lhs_unqualified.CompareTypes(rhs_unqualified);
+ if (comparable)
+ return llvm::Error::success();
+
+ std::string errMsg = llvm::formatv(
+ "comparison of distinct pointer types ({0} and {1})",
+ orig_lhs_type.TypeDescription(), orig_rhs_type.TypeDescription());
+ return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location);
+ }
+
+ if (!is_ordered && ((orig_lhs_type.IsNullPtrType() && rhs_nullptr_or_zero) ||
+ (lhs_nullptr_or_zero && orig_rhs_type.IsNullPtrType())))
+ return llvm::Error::success();
+
+ // If the operands has arithmetic or enumeration type (scoped or unscoped),
+ // usual arithmetic conversions are performed on both operands following the
+ // rules for arithmetic operators.
+ auto type_or_err = ArithmeticConversion(lhs, rhs, location);
+ if (!type_or_err)
+ return type_or_err.takeError();
+
+ lhs_type = lhs->GetCompilerType();
+ rhs_type = rhs->GetCompilerType();
+ if (lhs_type.IsScalarOrUnscopedEnumerationType() &&
+ rhs_type.IsScalarOrUnscopedEnumerationType())
+ return llvm::Error::success();
+
+ // Scoped enums can be compared only to the instances of the same type.
+ if (lhs_type.IsScopedEnumerationType() ||
+ rhs_type.IsScopedEnumerationType()) {
+ if (lhs_type.CompareTypes(rhs_type))
+ return llvm::Error::success();
+ std::string errMsg = llvm::formatv(
+ "invalid operands to binary expression ({0} and {1})",
+ orig_lhs_type.TypeDescription(), orig_rhs_type.TypeDescription());
+ return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location);
+ }
+
+ // Check if the value can be compared to a pointer. We allow all pointers,
+ // integers, unscoped enumerations and a nullptr literal if it's an
+ // equality/inequality comparison. For "pointer <-> integer" C++ allows only
+ // equality/inequality comparison against literal zero and nullptr. However in
+ // the debugger context it's often useful to compare a pointer with an integer
+ // representing an address. That said, this also allows comparing nullptr and
+ // any integer, not just literal zero, e.g. "nullptr == 1 -> false". C++
+ // doesn't allow it, but we implement this for convenience.
+ auto comparable_to_pointer = [&](CompilerType t) {
+ return t.IsPointerType() || t.IsInteger() ||
+ t.IsUnscopedEnumerationType() || (!is_ordered && t.IsNullPtrType());
+ };
+
+ if ((lhs_type.IsPointerType() && comparable_to_pointer(rhs_type)) ||
+ (comparable_to_pointer(lhs_type) && rhs_type.IsPointerType())) {
+ // If both are pointers, check if they have comparable types.
+ if ((lhs_type.IsPointerType() && !lhs_type.IsPointerToVoid()) &&
+ (rhs_type.IsPointerType() && !rhs_type.IsPointerToVoid())) {
+ // Compare canonical unqualified pointer types.
+ CompilerType lhs_unqualified_type =
+ lhs_type.GetCanonicalType().GetFullyUnqualifiedType();
+ CompilerType rhs_unqualified_type =
+ rhs_type.GetCanonicalType().GetFullyUnqualifiedType();
+ bool comparable = lhs_unqualified_type.CompareTypes(rhs_unqualified_type);
+
+ if (!comparable) {
+
+ std::string errMsg = llvm::formatv(
+ "comparison of distinct pointer types ({0} and {1})",
+ orig_lhs_type.TypeDescription(), orig_rhs_type.TypeDescription());
+ return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location);
+ }
+ }
+ // Comparing pointers to void is always allowed.
+ return llvm::Error::success();
+ }
+
+ std::string errMsg = llvm::formatv(
+ "invalid operands to binary expression ({0} and {1})",
+ orig_lhs_type.TypeDescription(), orig_rhs_type.TypeDescription());
+ return llvm::make_error<DILDiagnosticError>(m_expr, errMsg, location);
+}
+
+llvm::Expected<lldb::ValueObjectSP>
+Interpreter::EvaluateComparison(BinaryOpKind kind, lldb::ValueObjectSP lhs,
+ lldb::ValueObjectSP rhs, bool lhs_is_literal,
+ bool rhs_is_literal, uint32_t location) {
+ // Comparison works for:
+ // nullptr_t <-> {nullptr_t,integer} (if integer is literal zero)
+ // {nullptr_t,integer} <-> nullptr_t (if integer is literal zero)
+ // {scalar,unscoped_enum} <-> {scalar,unscoped_enum}
+ // scoped_enum <-> scoped_enum (if the same type)
+ // pointer <-> pointer (if pointee types are compatible)
+ // pointer <-> {integer,unscoped_enum,nullptr_t}
+ // {integer,unscoped_enum,nullptr_t} <-> pointer
+ if (auto error = ValidateComparison(kind, lhs, rhs, lhs_is_literal,
+ rhs_is_literal, location))
+ return error;
+
+ CompilerType lhs_type = lhs->GetCompilerType();
+ CompilerType rhs_type = rhs->GetCompilerType();
+
+ llvm::Expected<lldb::TypeSystemSP> type_system =
+ GetTypeSystemFromCU(m_stack_frame);
+ if (!type_system)
+ return type_system.takeError();
+ CompilerType boolean_type = GetBasicType(*type_system, lldb::eBasicTypeBool);
+
+ return EvaluateScalarOp(kind, lhs, rhs, boolean_type, location);
+}
+
llvm::Expected<lldb::ValueObjectSP>
Interpreter::EvaluateBinaryShift(BinaryOpKind kind, lldb::ValueObjectSP lhs,
lldb::ValueObjectSP rhs, uint32_t location) {
@@ -957,6 +1149,8 @@ Interpreter::Visit(const BinaryOpNode &node) {
return rhs_or_err;
lldb::ValueObjectSP rhs = *rhs_or_err;
+ bool lhs_is_literal = node.GetLHS().IsConstLiteral();
+ bool rhs_is_literal = node.GetRHS().IsConstLiteral();
lldb::TypeSystemSP lhs_system =
lhs->GetCompilerType().GetTypeSystem().GetSharedPointer();
lldb::TypeSystemSP rhs_system =
@@ -995,6 +1189,14 @@ Interpreter::Visit(const BinaryOpNode &node) {
case BinaryOpKind::Shl:
case BinaryOpKind::Shr:
return EvaluateBinaryShift(node.GetKind(), lhs, rhs, node.GetLocation());
+ case BinaryOpKind::EQ:
+ case BinaryOpKind::NE:
+ case BinaryOpKind::LT:
+ case BinaryOpKind::LE:
+ case BinaryOpKind::GT:
+ case BinaryOpKind::GE:
+ return EvaluateComparison(node.GetKind(), lhs, rhs, lhs_is_literal,
+ rhs_is_literal, node.GetLocation());
}
return llvm::make_error<DILDiagnosticError>(
diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp
index 997ba6b09f872..0aed547ebb0aa 100644
--- a/lldb/source/ValueObject/DILLexer.cpp
+++ b/lldb/source/ValueObject/DILLexer.cpp
@@ -32,8 +32,16 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
return "equal";
case Kind::eof:
return "eof";
+ case Kind::equalequal:
+ return "equalequal";
+ case Kind::exclaimequal:
+ return "exclaimequal";
case Kind::float_constant:
return "float_constant";
+ case Kind::greater:
+ return "greater";
+ case Kind::greaterequal:
+ return "greaterequal";
case Kind::greatergreater:
return "greatergreater";
case Kind::identifier:
@@ -48,6 +56,10 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
return "l_paren";
case Kind::l_square:
return "l_square";
+ case Kind::less:
+ return "less";
+ case Kind::lessequal:
+ return "lessequal";
case Kind::lessless:
return "lessless";
case Kind::minus:
@@ -198,24 +210,18 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr,
// be ordered longest-to-shortest in the list below. E.g. '::' must come
// before ':', and '+=' must come before '+'.
constexpr std::pair<Token::Kind, const char *> operators[] = {
- {Token::arrow, "->"},
- {Token::coloncolon, "::"},
- {Token::greatergreater, ">>"},
- {Token::lessless, "<<"},
- {Token::minusequal, "-="},
- {Token::plusequal, "+="},
- {Token::amp, "&"},
- {Token::colon, ":"},
- {Token::equal, "="},
- {Token::l_paren, "("},
- {Token::l_square, "["},
- {Token::minus, "-"},
- {Token::percent, "%"},
- {Token::period, "."},
- {Token::plus, "+"},
- {Token::r_paren, ")"},
- {Token::r_square, "]"},
- {Token::slash, "/"},
+ {Token::arrow, "->"}, {Token::coloncolon, "::"},
+ {Token::equalequal, "=="}, {Token::exclaimequal, "!="},
+ {Token::greaterequal, ">="}, {Token::greatergreater, ">>"},
+ {Token::lessequal, "<="}, {Token::lessless, "<<"},
+ {Token::minusequal, "-="}, {Token::plusequal, "+="},
+ {Token::amp, "&"}, {Token::colon, ":"},
+ {Token::equal, "="}, {Token::greater, ">"},
+ {Token::l_paren, "("}, {Token::l_square, "["},
+ {Token::less, "<"}, {Token::minus, "-"},
+ {Token::percent, "%"}, {Token::period, "."},
+ {Token::plus, "+"}, {Token::r_paren, ")"},
+ {Token::r_square, "]"}, {Token::slash, "/"},
{Token::star, "*"},
};
for (auto [kind, str] : operators) {
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index b55b12a2bc42a..a6afff57734e8 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -143,7 +143,7 @@ ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); }
// "-="
//
ASTNodeUP DILParser::ParseAssignmentExpression() {
- auto lhs = ParseShiftExpression();
+ auto lhs = ParseEqualityExpression();
assert(lhs && "ASTNodeUP must not contain a nullptr");
// Check if it's an assignment expression.
@@ -160,6 +160,55 @@ ASTNodeUP DILParser::ParseAssignmentExpression() {
return lhs;
}
+// Parse an equality_expression.
+//
+// equality_expression:
+// relational_expression {"==" relational_expression}
+// relational_expression {"!=" relational_expression}
+//
+ASTNodeUP DILParser::ParseEqualityExpression() {
+ auto lhs = ParseRelationalExpression();
+ assert(lhs && "ASTNodeUP must not contain a nullptr");
+
+ while (CurToken().IsOneOf({Token::equalequal, Token::exclaimequal})) {
+ Token token = CurToken();
+ m_dil_lexer.Advance();
+ auto rhs = ParseRelationalExpression();
+ assert(lhs && "ASTNodeUP must not contain a nullptr");
+ lhs = std::make_unique<BinaryOpNode>(
+ token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()),
+ std::move(lhs), std::move(rhs));
+ }
+
+ return lhs;
+}
+
+// Parse a relational_expression.
+//
+// relational_expression:
+// shift_expression {"<" shift_expression}
+// shift_expression {">" shift_expression}
+// shift_expression {"<=" shift_expression}
+// shift_expression {">=" shift_expression}
+//
+ASTNodeUP DILParser::ParseRelationalExpression() {
+ auto lhs = ParseShiftExpression();
+ assert(lhs && "ASTNodeUP must not contain a nullptr");
+
+ while (CurToken().IsOneOf(
+ {Token::less, Token::greater, Token::lessequal, Token::greaterequal})) {
+ Token token = CurToken();
+ m_dil_lexer.Advance();
+ auto rhs = ParseShiftExpression();
+ assert(lhs && "ASTNodeUP must not contain a nullptr");
+ lhs = std::make_unique<BinaryOpNode>(
+ token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()),
+ std::move(lhs), std::move(rhs));
+ }
+
+ return lhs;
+}
+
// Parse a shift_expression.
//
// shift_expression:
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Comparison/Makefile b/lldb/test/API/commands/frame/var-dil/expr/Comparison/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/expr/Comparison/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Comparison/TestFrameVarDILExprComparison.py b/lldb/test/API/commands/frame/var-dil/expr/Comparison/TestFrameVarDILExprComparison.py
new file mode 100644
index 0000000000000..f5aa61e15ecbf
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/expr/Comparison/TestFrameVarDILExprComparison.py
@@ -0,0 +1,173 @@
+"""
+Test DIL comparison operators.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+
+class TestFrameVarComparison(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def test_comparison(self):
+ self.build()
+ lldbutil.run_to_source_breakpoint(
+ self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
+ )
+
+ self.runCmd("settings set target.experimental.use-DIL true")
+
+ # Check arithmetic comparison
+ self.expect_var_path("1 == 1", value="true")
+ self.expect_var_path("1 == 1.0", value="true")
+ self.expect_var_path("i == 1", value="true")
+ self.expect_var_path("iref == i", value="true")
+ self.expect_var_path("array[0] == i", value="true")
+ self.expect_var_path("trueVar == true", value="true")
+ self.expect_var_path("1 == true", value="true")
+ self.expect_var_path("array[0] != array[1]", value="true")
+ self.expect_var_path("1 != 2 == true", value="true")
+ self.expect_var_path("true != 2 < 3", value="false")
+ self.expect_var_path("ScopedEnum::kZeroS < ScopedEnum::kOneS", value="true")
+ self.expect_var_path("1 > 2", value="false")
+ self.expect_var_path("1 > 0.1", value="true")
+ self.expect_var_path("1 >= 2", value="false")
+ self.expect_var_path("2 >= 2", value="true")
+ self.expect_var_path("1.0 <= 1.25", value="true")
+ self.expect_var_path("1.25f <= 1.0", value="false")
+ self.expect_var_path("1.0 <= 1.0", value="true")
+
+ self.expect(
+ "frame var -- 'ScopedEnum::kZeroS < ScopedEnumInt8::kOneS8'",
+ error=True,
+ substrs=[
+ "invalid operands to binary expression "
+ "('ScopedEnum' and 'ScopedEnumInt8')"
+ ],
+ )
+
+ self.expect(
+ "frame var -- 's < 4",
+ error=True,
+ substrs=["invalid operands to binary expression ('S' and 'int')"],
+ )
+
+ # Check pointer comparison
+ self.expect_var_path("p_void == p_void", value="true")
+ self.expect_var_path("p_void == p_char1", value="true")
+ self.expect_var_path("p_void != p_char1", value="false")
+ self.expect_var_path("p_void > p_char1", value="false")
+ self.expect_var_path("p_void >= p_char1", value="true")
+ self.expect_var_path("p_void < (p_char1 + 1)", value="true")
+ self.expect_var_path("pp_void0 + 1 == pp_void1", value="true")
+
+ self.expect_var_path("(void*)1 == (void*)1", value="true")
+ self.expect_var_path("(void*)1 != (void*)1", value="false")
+ self.expect_var_path("(void*)2 > (void*)1", value="true")
+ self.expect_var_path("(void*)2 < (void*)1", value="false")
+
+ self.expect_var_path("(void*)1 == (char*)1", value="true")
+ self.expect_var_path("(char*)1 != (void*)1", value="false")
+ self.expect_var_path("(void*)2 > (char*)1", value="true")
+ self.expect_var_path("(char*)2 < (void*)1", value="false")
+
+ self.expect_var_path("(void*)0 == 0", value="true")
+ self.expect_var_path("0 != (void*)0", value="false")
+
+ self.expect_var_path("(void*)0 == nullptr", value="true")
+ self.expect_var_path("(void*)0 != nullptr", value="false")
+ self.expect_var_path("nullptr == (void*)1", value="false")
+ self.expect_var_path("nullptr != (void*)1", value="true")
+
+ self.expect_var_path("nullptr == nullptr", value="true")
+ self.expect_var_path("nullptr != nullptr", value="false")
+
+ self.expect_var_path("nullptr == 0", value="true")
+ self.expect_var_path("0 != nullptr", value="false")
+ self.expect_var_path("nullptr == 0U", value="true")
+ self.expect_var_path("0L != nullptr", value="false")
+ self.expect_var_path("nullptr == 0UL", value="true")
+ self.expect_var_path("0ULL != nullptr", value="false")
+ self.expect_var_path("nullptr == 0x0", value="true")
+ self.expect_var_path("0b0 != nullptr", value="false")
+ self.expect_var_path("nullptr == 00", value="true")
+ self.expect_var_path("0x0LLU != nullptr", value="false")
+
+ self.expect_var_path("0 == std_nullptr_t", value="true")
+ self.expect_var_path("std_nullptr_t != 0", value="false")
+
+ self.expect_var_path("array == p_int0", value="true")
+ self.expect_var_path("p_int0 == array", value="true")
+ self.expect_var_path("array < p_int1", value="true")
+ self.expect_var_path("array == nullptr", value="false")
+
+ # These are not allowed by C++, but DIL supports these for convenience.
+ self.expect_var_path("(void*)1 == 1", value="true")
+ self.expect_var_path("(void*)1 == 0", value="false")
+ self.expect_var_path("(void*)1 > 0", value="true")
+ self.expect_var_path("(void*)1 < 0", value="false")
+ self.expect_var_path("1 > (void*)0", value="true")
+ self.expect_var_path("2 < (void*)3", value="true")
+
+ # Integer is converted to uintptr_t, so negative numbers because large
+ # positive numbers.
+ self.expect_var_path("(void*)-1 == -1", value="true")
+ self.expect_var_path("(void*)1 > -1", value="false")
+
+ self.expect(
+ "frame var -- '(void*)0 > nullptr'",
+ error=True,
+ substrs=[
+ "invalid operands to binary expression ('void *' and 'std::nullptr_t')"
+ ],
+ )
+
+ self.expect(
+ "frame var -- 'nullptr > 0'",
+ error=True,
+ substrs=[
+ "invalid operands to binary expression ('std::nullptr_t' and 'int')"
+ ],
+ )
+
+ self.expect(
+ "frame var -- '1 == nullptr'",
+ error=True,
+ substrs=[
+ "invalid operands to binary expression ('int' and 'std::nullptr_t')"
+ ],
+ )
+
+ self.expect(
+ "frame var -- 'nullptr == (int)0'",
+ error=True,
+ substrs=[
+ "invalid operands to binary expression ('std::nullptr_t' and 'int')"
+ ],
+ )
+
+ self.expect(
+ "frame var -- 'false == nullptr'",
+ error=True,
+ substrs=[
+ "invalid operands to binary expression ('bool' and 'std::nullptr_t')"
+ ],
+ )
+
+ self.expect(
+ "frame var -- 'p_int0 > p_char1'",
+ error=True,
+ substrs=[
+ "comparison of distinct pointer types ('int *' and 'const char *')"
+ ],
+ )
+
+ self.expect(
+ "frame var -- 'pp_void0 == p_char1'",
+ error=True,
+ substrs=[
+ "comparison of distinct pointer types ('void **' and 'const char *')"
+ ],
+ )
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp
new file mode 100644
index 0000000000000..982df688a88ca
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp
@@ -0,0 +1,34 @@
+#include <cstdint>
+#include <cstddef>
+
+enum class ScopedEnum { kZeroS, kOneS };
+enum class ScopedEnumInt8 : int8_t { kZeroS8, kOneS8 };
+
+void stop() {}
+
+int main(int argc, char **argv) {
+ auto enum_one = ScopedEnum::kOneS;
+ auto enum_one_8 = ScopedEnumInt8::kOneS8;
+
+ bool trueVar = true;
+
+ int i = 1;
+ int j = 2;
+ int &iref = i;
+ int array[2] = {1, 2};
+
+ struct S {
+ } s;
+
+ int *p_int0 = &array[0];
+ int *p_int1 = &array[1];
+ const char *p_char1 = "hello";
+ void *p_void = (void *)p_char1;
+ void **pp_void0 = &p_void;
+ void **pp_void1 = pp_void0 + 1;
+
+ std::nullptr_t std_nullptr_t = nullptr;
+
+ stop(); // Set a breakpoint here
+ return 0;
+}
>From 0a373725f76060ee9c2b05fb2bdb3cc144f3beae Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Tue, 14 Jul 2026 21:05:25 +0500
Subject: [PATCH 2/3] Fix formatting
---
lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp
index 982df688a88ca..4ff21bb8f982f 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp
+++ b/lldb/test/API/commands/frame/var-dil/expr/Comparison/main.cpp
@@ -1,5 +1,5 @@
-#include <cstdint>
#include <cstddef>
+#include <cstdint>
enum class ScopedEnum { kZeroS, kOneS };
enum class ScopedEnumInt8 : int8_t { kZeroS8, kOneS8 };
>From e3a2d8443528d2de640eb46e5b880940a0daafff Mon Sep 17 00:00:00 2001
From: Ilia Kuklin <ikuklin at accesssoftek.com>
Date: Wed, 15 Jul 2026 19:25:33 +0500
Subject: [PATCH 3/3] Fix asserts
---
lldb/source/ValueObject/DILParser.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index a6afff57734e8..d193e7d7a5f1f 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -174,7 +174,7 @@ ASTNodeUP DILParser::ParseEqualityExpression() {
Token token = CurToken();
m_dil_lexer.Advance();
auto rhs = ParseRelationalExpression();
- assert(lhs && "ASTNodeUP must not contain a nullptr");
+ assert(rhs && "ASTNodeUP must not contain a nullptr");
lhs = std::make_unique<BinaryOpNode>(
token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()),
std::move(lhs), std::move(rhs));
@@ -200,7 +200,7 @@ ASTNodeUP DILParser::ParseRelationalExpression() {
Token token = CurToken();
m_dil_lexer.Advance();
auto rhs = ParseShiftExpression();
- assert(lhs && "ASTNodeUP must not contain a nullptr");
+ assert(rhs && "ASTNodeUP must not contain a nullptr");
lhs = std::make_unique<BinaryOpNode>(
token.GetLocation(), GetBinaryOpKindFromToken(token.GetKind()),
std::move(lhs), std::move(rhs));
More information about the lldb-commits
mailing list