Skip to content

[lldb] Add bitwise operators to DIL#209768

Open
kuilpd wants to merge 3 commits into
llvm:mainfrom
kuilpd:DIL-upstream-add-bitwise-operators
Open

[lldb] Add bitwise operators to DIL#209768
kuilpd wants to merge 3 commits into
llvm:mainfrom
kuilpd:DIL-upstream-add-bitwise-operators

Conversation

@kuilpd

@kuilpd kuilpd commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Add binary bitwise operators |, ^, &, and unary bitwise negation ~.

@kuilpd
kuilpd requested a review from JDevlieghere as a code owner July 15, 2026 14:20
@kuilpd kuilpd added the lldb label Jul 15, 2026
@kuilpd
kuilpd force-pushed the DIL-upstream-add-bitwise-operators branch from 21fbec6 to 3e59893 Compare July 15, 2026 14:22
@llvmorg-github-actions

Copy link
Copy Markdown

@llvm/pr-subscribers-lldb

Author: Ilia Kuklin (kuilpd)

Changes

Add binary bitwise operators |, ^, &, and unary bitwise negation ~.


Full diff: https://github.com/llvm/llvm-project/pull/209768.diff

11 Files Affected:

  • (modified) lldb/docs/dil-expr-lang.ebnf (+8-2)
  • (modified) lldb/include/lldb/ValueObject/DILAST.h (+4)
  • (modified) lldb/include/lldb/ValueObject/DILEval.h (+3)
  • (modified) lldb/include/lldb/ValueObject/DILLexer.h (+3)
  • (modified) lldb/include/lldb/ValueObject/DILParser.h (+3)
  • (modified) lldb/source/ValueObject/DILAST.cpp (+6)
  • (modified) lldb/source/ValueObject/DILEval.cpp (+60)
  • (modified) lldb/source/ValueObject/DILLexer.cpp (+9)
  • (modified) lldb/source/ValueObject/DILParser.cpp (+79-4)
  • (modified) lldb/test/API/commands/frame/var-dil/expr/Bitwise/TestFrameVarDILBitwise.py (+57)
  • (modified) lldb/test/API/commands/frame/var-dil/expr/Bitwise/main.cpp (+9)
diff --git a/lldb/docs/dil-expr-lang.ebnf b/lldb/docs/dil-expr-lang.ebnf
index fce8f4461b46f..ec4687ab05389 100644
--- a/lldb/docs/dil-expr-lang.ebnf
+++ b/lldb/docs/dil-expr-lang.ebnf
@@ -5,13 +5,19 @@
 
 expression = assignment_expression ;
 
-assignment_expression = shift_expression
-                      | shift_expression assignment_operator assignment_expression ;
+assignment_expression = inclusive_or_expression
+                      | inclusive_or_expression assignment_operator assignment_expression ;
 
 assignment_operator = "="
                     | "+="
                     | "-=" ;
 
+inclusive_or_expression = exclusive_or_expression {"|" exclusive_or_expression} ;
+
+exclusive_or_expression = and_expression {"^" and_expression} ;
+
+and_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..a2b02bb4a9da8 100644
--- a/lldb/include/lldb/ValueObject/DILAST.h
+++ b/lldb/include/lldb/ValueObject/DILAST.h
@@ -38,6 +38,7 @@ enum class UnaryOpKind {
   Deref,  ///< "*"
   Minus,  ///< "-"
   Plus,   ///< "+"
+  Not,    ///< "~"
 };
 
 /// The binary operators recognized by DIL.
@@ -48,6 +49,9 @@ enum class BinaryOpKind {
   Div,       ///< "/"
   Mul,       ///< "*"
   Rem,       ///< "%"
+  And,       ///< "&"
+  Xor,       ///< "^"
+  Or,        ///< "|"
   Shl,       ///< "<<"
   Shr,       ///< ">>"
   Sub,       ///< "-"
diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h
index 35784ea9987f9..59fe465b2dd88 100644
--- a/lldb/include/lldb/ValueObject/DILEval.h
+++ b/lldb/include/lldb/ValueObject/DILEval.h
@@ -126,6 +126,9 @@ class Interpreter : Visitor {
   llvm::Expected<lldb::ValueObjectSP>
   EvaluateBinaryRemainder(lldb::ValueObjectSP lhs, lldb::ValueObjectSP rhs,
                           uint32_t location);
+  llvm::Expected<lldb::ValueObjectSP>
+  EvaluateBinaryBitwise(BinaryOpKind kind, lldb::ValueObjectSP lhs,
+                        lldb::ValueObjectSP rhs, uint32_t location);
   llvm::Expected<CompilerType>
   PickIntegerType(lldb::TypeSystemSP type_system, ExecutionContextScope &ctx,
                   const IntegerLiteralNode &literal);
diff --git a/lldb/include/lldb/ValueObject/DILLexer.h b/lldb/include/lldb/ValueObject/DILLexer.h
index f9f42dc59b311..8b58bbcc84547 100644
--- a/lldb/include/lldb/ValueObject/DILLexer.h
+++ b/lldb/include/lldb/ValueObject/DILLexer.h
@@ -27,6 +27,7 @@ class Token {
   enum Kind {
     amp,
     arrow,
+    caret,
     colon,
     coloncolon,
     eof,
@@ -44,12 +45,14 @@ class Token {
     minusequal,
     percent,
     period,
+    pipe,
     plus,
     plusequal,
     r_paren,
     r_square,
     slash,
     star,
+    tilde,
   };
 
   Token(Kind kind, std::string spelling, uint32_t start)
diff --git a/lldb/include/lldb/ValueObject/DILParser.h b/lldb/include/lldb/ValueObject/DILParser.h
index 9e2bbff4b6614..3e58620875a6a 100644
--- a/lldb/include/lldb/ValueObject/DILParser.h
+++ b/lldb/include/lldb/ValueObject/DILParser.h
@@ -84,6 +84,9 @@ class DILParser {
   ASTNodeUP ParseExpression();
 
   ASTNodeUP ParseAssignmentExpression();
+  ASTNodeUP ParseInclusiveOrExpression();
+  ASTNodeUP ParseExclusiveOrExpression();
+  ASTNodeUP ParseAndExpression();
   ASTNodeUP ParseShiftExpression();
   ASTNodeUP ParseAdditiveExpression();
   ASTNodeUP ParseMultiplicativeExpression();
diff --git a/lldb/source/ValueObject/DILAST.cpp b/lldb/source/ValueObject/DILAST.cpp
index 40bf07bdd5aab..bb018712a1ba4 100644
--- a/lldb/source/ValueObject/DILAST.cpp
+++ b/lldb/source/ValueObject/DILAST.cpp
@@ -29,6 +29,12 @@ BinaryOpKind GetBinaryOpKindFromToken(Token::Kind token_kind) {
     return BinaryOpKind::Div;
   case Token::percent:
     return BinaryOpKind::Rem;
+  case Token::amp:
+    return BinaryOpKind::And;
+  case Token::caret:
+    return BinaryOpKind::Xor;
+  case Token::pipe:
+    return BinaryOpKind::Or;
   case Token::lessless:
     return BinaryOpKind::Shl;
   case Token::greatergreater:
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index 5fc2376be0e75..5d4d520d233f4 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -559,6 +559,34 @@ Interpreter::Visit(const UnaryOpNode &node) {
     }
     return operand;
   }
+  case UnaryOpKind::Not: {
+    llvm::Expected<lldb::ValueObjectSP> conv_op =
+        UnaryConversion(operand, node.GetLocation());
+    if (!conv_op)
+      return conv_op;
+    operand = *conv_op;
+    CompilerType operand_type = operand->GetCompilerType();
+    if (!operand_type.IsInteger()) {
+      std::string errMsg =
+          llvm::formatv("invalid argument type '{0}' to unary expression",
+                        operand_type.GetTypeName());
+      return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                  node.GetLocation());
+    }
+    Scalar scalar;
+    bool resolved = operand->ResolveValue(scalar);
+    if (!resolved) {
+      std::string errMsg = llvm::formatv("invalid operand value: {0}",
+                                         operand->GetError().AsCString());
+      return llvm::make_error<DILDiagnosticError>(m_expr, errMsg,
+                                                  node.GetLocation());
+    }
+
+    bool flipped = scalar.OnesComplement();
+    if (flipped)
+      return ValueObject::CreateValueObjectFromScalar(
+          m_stack_frame, scalar, operand->GetCompilerType(), "result");
+  }
   }
   return llvm::make_error<DILDiagnosticError>(m_expr, "invalid unary operation",
                                               node.GetLocation());
@@ -634,6 +662,12 @@ Interpreter::EvaluateScalarOp(BinaryOpKind kind, lldb::ValueObjectSP lhs,
     return value_object(l / r);
   case BinaryOpKind::Rem:
     return value_object(l % r);
+  case BinaryOpKind::And:
+    return value_object(l & r);
+  case BinaryOpKind::Xor:
+    return value_object(l ^ r);
+  case BinaryOpKind::Or:
+    return value_object(l | r);
   case BinaryOpKind::Shl:
     return value_object(l << r);
   case BinaryOpKind::Shr:
@@ -891,6 +925,28 @@ Interpreter::EvaluateAssignment(lldb::ValueObjectSP lhs,
   return lhs;
 }
 
+llvm::Expected<lldb::ValueObjectSP>
+Interpreter::EvaluateBinaryBitwise(BinaryOpKind kind, lldb::ValueObjectSP lhs,
+                                   lldb::ValueObjectSP rhs, uint32_t location) {
+  // Operations {'&', '|', '^'} work for:
+  //  {integer,unscoped_enum} <-> {integer,unscoped_enum}
+  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.IsInteger()) {
+    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);
+  }
+
+  return EvaluateScalarOp(kind, lhs, rhs, result_type, location);
+}
+
 llvm::Expected<lldb::ValueObjectSP>
 Interpreter::EvaluateBinaryShift(BinaryOpKind kind, lldb::ValueObjectSP lhs,
                                  lldb::ValueObjectSP rhs, uint32_t location) {
@@ -977,6 +1033,10 @@ Interpreter::Visit(const BinaryOpNode &node) {
     return EvaluateBinaryDivision(lhs, rhs, node.GetLocation());
   case BinaryOpKind::Rem:
     return EvaluateBinaryRemainder(lhs, rhs, node.GetLocation());
+  case BinaryOpKind::And:
+  case BinaryOpKind::Xor:
+  case BinaryOpKind::Or:
+    return EvaluateBinaryBitwise(node.GetKind(), lhs, rhs, node.GetLocation());
   case BinaryOpKind::Shl:
   case BinaryOpKind::Shr:
     return EvaluateBinaryShift(node.GetKind(), lhs, rhs, node.GetLocation());
diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp
index 997ba6b09f872..813ab3bbcab3e 100644
--- a/lldb/source/ValueObject/DILLexer.cpp
+++ b/lldb/source/ValueObject/DILLexer.cpp
@@ -24,6 +24,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
     return "amp";
   case Kind::arrow:
     return "arrow";
+  case Kind::caret:
+    return "caret";
   case Kind::colon:
     return "colon";
   case Kind::coloncolon:
@@ -58,6 +60,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
     return "percent";
   case Kind::period:
     return "period";
+  case Kind::pipe:
+    return "pipe";
   case Kind::plus:
     return "plus";
   case Kind::plusequal:
@@ -70,6 +74,8 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
     return "slash";
   case Token::star:
     return "star";
+  case Token::tilde:
+    return "tilde";
   }
   llvm_unreachable("Unknown token name");
 }
@@ -205,6 +211,7 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr,
       {Token::minusequal, "-="},
       {Token::plusequal, "+="},
       {Token::amp, "&"},
+      {Token::caret, "^"},
       {Token::colon, ":"},
       {Token::equal, "="},
       {Token::l_paren, "("},
@@ -212,11 +219,13 @@ llvm::Expected<Token> DILLexer::Lex(llvm::StringRef expr,
       {Token::minus, "-"},
       {Token::percent, "%"},
       {Token::period, "."},
+      {Token::pipe, "|"},
       {Token::plus, "+"},
       {Token::r_paren, ")"},
       {Token::r_square, "]"},
       {Token::slash, "/"},
       {Token::star, "*"},
+      {Token::tilde, "~"},
   };
   for (auto [kind, str] : operators) {
     if (remainder.consume_front(str))
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index b55b12a2bc42a..3af14d31c2e8b 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -134,8 +134,8 @@ ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); }
 // Parse an assignment_expression
 //
 //  assignment_expression
-//    shift_expression
-//    shift_expression assignment_operator assignment_expression
+//    inclusive_or_expression
+//    inclusive_or_expression assignment_operator assignment_expression
 //
 //  assignment_operator:
 //    "="
@@ -143,7 +143,7 @@ ASTNodeUP DILParser::ParseExpression() { return ParseAssignmentExpression(); }
 //    "-="
 //
 ASTNodeUP DILParser::ParseAssignmentExpression() {
-  auto lhs = ParseShiftExpression();
+  auto lhs = ParseInclusiveOrExpression();
   assert(lhs && "ASTNodeUP must not contain a nullptr");
 
   // Check if it's an assignment expression.
@@ -160,6 +160,77 @@ ASTNodeUP DILParser::ParseAssignmentExpression() {
   return lhs;
 }
 
+// Parse an inclusive_or_expression.
+//
+//  inclusive_or_expression:
+//    exclusive_or_expression {"|" exclusive_or_expression}
+//
+ASTNodeUP DILParser::ParseInclusiveOrExpression() {
+  auto lhs = ParseExclusiveOrExpression();
+  assert(lhs && "ASTNodeUP must not contain a nullptr");
+
+  while (CurToken().Is(Token::pipe)) {
+    Token token = CurToken();
+    m_dil_lexer.Advance();
+    auto rhs = ParseExclusiveOrExpression();
+    assert(rhs && "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 an exclusive_or_expression.
+//
+//  exclusive_or_expression:
+//    and_expression {"^" and_expression}
+//
+ASTNodeUP DILParser::ParseExclusiveOrExpression() {
+  auto lhs = ParseAndExpression();
+  assert(lhs && "ASTNodeUP must not contain a nullptr");
+
+  while (CurToken().Is(Token::caret)) {
+    Token token = CurToken();
+    m_dil_lexer.Advance();
+    auto rhs = ParseAndExpression();
+    assert(rhs && "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 an and_expression.
+//
+//  and_expression:
+//    shift_expression {"&" shift_expression}
+//
+ASTNodeUP DILParser::ParseAndExpression() {
+  auto lhs = ParseShiftExpression();
+  assert(lhs && "ASTNodeUP must not contain a nullptr");
+
+  while (CurToken().Is(Token::amp)) {
+    Token token = CurToken();
+    if (token.Is(Token::amp) && m_mode != lldb::eDILModeFull) {
+      BailOut("bitwise and (&) is allowed only in DIL full mode",
+              token.GetLocation(), token.GetSpelling().length());
+      return std::make_unique<ErrorNode>();
+    }
+    m_dil_lexer.Advance();
+    auto rhs = ParseShiftExpression();
+    assert(rhs && "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:
@@ -293,10 +364,11 @@ ASTNodeUP DILParser::ParseCastExpression() {
 //    "*"
 //    "+"
 //    "-"
+//    "~"
 //
 ASTNodeUP DILParser::ParseUnaryExpression() {
   if (CurToken().IsOneOf(
-          {Token::amp, Token::star, Token::minus, Token::plus})) {
+          {Token::amp, Token::star, Token::minus, Token::plus, Token::tilde})) {
     Token token = CurToken();
     uint32_t loc = token.GetLocation();
     m_dil_lexer.Advance();
@@ -315,6 +387,9 @@ ASTNodeUP DILParser::ParseUnaryExpression() {
     case Token::plus:
       return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Plus,
                                            std::move(rhs));
+    case Token::tilde:
+      return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Not,
+                                           std::move(rhs));
     default:
       llvm_unreachable("invalid token kind");
     }
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Bitwise/TestFrameVarDILBitwise.py b/lldb/test/API/commands/frame/var-dil/expr/Bitwise/TestFrameVarDILBitwise.py
index 150a63d1ca975..a81b676070f45 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Bitwise/TestFrameVarDILBitwise.py
+++ b/lldb/test/API/commands/frame/var-dil/expr/Bitwise/TestFrameVarDILBitwise.py
@@ -19,6 +19,20 @@ def test_bitwise(self):
 
         self.runCmd("settings set target.experimental.use-DIL true")
 
+        # Check unary negation
+        self.expect_var_path("~(-1)", value="0")
+        self.expect_var_path("~~0", value="0")
+        self.expect_var_path("~0", value="-1")
+        self.expect_var_path("~1", value="-2")
+        self.expect_var_path("~0LL", value="-1")
+        self.expect_var_path("~1LL", value="-2")
+        self.expect_var_path("~true", value="-2")
+        self.expect_var_path("~false", value="-1")
+        self.expect_var_path("~var_true", value="-2")
+        self.expect_var_path("~var_false", value="-1")
+        self.expect_var_path("~ull_max", value="0")
+        self.expect_var_path("~0b1011", value="-12")
+
         # Check bitwise shifts
         self.expect_var_path("(1 << 5)", value="32")
         self.expect_var_path("(32 >> 2)", value="8")
@@ -33,7 +47,41 @@ def test_bitwise(self):
         self.expect_var_path("2 >> enum_one", value="1")
         self.expect_var_path("i64 << 63", type="uint64_t")
 
+        # Check And, Xor, Or
+        self.expect_var_path("0b1011 & 0xFF", value="11")
+        self.expect_var_path("0b1011 & mask_ff", value="11")
+        self.expect_var_path("0b1011 & 0b0111", value="3")
+        self.expect_var_path("0b1011 | 0b0111", value="15")
+        self.expect_var_path("-0b1011 | 0xFF", value="-1")
+        self.expect_var_path("-0b1011 | 0xFFu", value="4294967295")
+        self.expect_var_path("0b1011 ^ 0b0111", value="12")
+
         # Check errors
+        self.expect(
+            "frame var -- '~1.0'",
+            error=True,
+            substrs=["invalid argument type 'double' to unary expression"],
+        )
+        self.expect(
+            "frame var -- '~s'",
+            error=True,
+            substrs=["invalid argument type 'S' to unary expression"],
+        )
+        self.expect(
+            "frame var -- 's & 1.0'",
+            error=True,
+            substrs=["invalid operands to binary expression ('S' and 'double')"],
+        )
+        self.expect(
+            "frame var -- '1 ^ s'",
+            error=True,
+            substrs=["invalid operands to binary expression ('int' and 'S')"],
+        )
+        self.expect(
+            "frame var -- '1 | 1.0'",
+            error=True,
+            substrs=["invalid operands to binary expression ('int' and 'double')"],
+        )
         self.expect(
             "frame var -- '1 << 1.0'",
             error=True,
@@ -54,3 +102,12 @@ def test_bitwise(self):
             error=True,
             substrs=["invalid shift amount"],
         )
+
+        # Check that bitwise & is allowed only in full mode
+        frame = thread.GetFrameAtIndex(0)
+        simple = frame.GetValueForVariablePath("i64 & 1", lldb.eDILModeSimple)
+        legacy = frame.GetValueForVariablePath("i64 & 1", lldb.eDILModeLegacy)
+        full = frame.GetValueForVariablePath("i64 & 1", lldb.eDILModeFull)
+        self.assertFailure(simple.GetError())
+        self.assertFailure(legacy.GetError())
+        self.assertSuccess(full.GetError())
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Bitwise/main.cpp b/lldb/test/API/commands/frame/var-dil/expr/Bitwise/main.cpp
index 2978946b757b9..38236606db5b6 100644
--- a/lldb/test/API/commands/frame/var-dil/expr/Bitwise/main.cpp
+++ b/lldb/test/API/commands/frame/var-dil/expr/Bitwise/main.cpp
@@ -1,12 +1,21 @@
 #include <cstdint>
+#include <limits>
+
 enum UnscopedEnum { kZero, kOne };
 
 int main(int argc, char **argv) {
+  bool var_true = true;
+  bool var_false = false;
+
+  unsigned long long ull_max = std::numeric_limits<unsigned long long>::max();
+  unsigned long long ull_zero = 0;
+
   struct S {
   } s;
 
   auto enum_one = UnscopedEnum::kOne;
   uint64_t i64 = 1;
+  uint32_t mask_ff = 0xFF;
 
   return 0; // Set a breakpoint here
 }

Comment on lines +563 to +564
llvm::Expected<lldb::ValueObjectSP> conv_op =
UnaryConversion(operand, node.GetLocation());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we dereference reference operands before calling UnaryConversion here? The existing unary + and unary - branches already handle this by checking IsReferenceType() and calling Dereference() before the unary conversion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we dereference reference operands before calling UnaryConversion here? The existing unary + and unary - branches already handle this by checking IsReferenceType() and calling Dereference() before the unary conversion.

You're right, thank you for noticing!

Comment thread lldb/docs/dil-expr-lang.ebnf Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add ~ to unary_operator

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants