Skip to content

Conversation

kuilpd
Copy link
Contributor

@kuilpd kuilpd commented Aug 27, 2025

This patch adds unary nodes plus and minus, and introduces unary type conversions and integral promotion.

@llvmbot
Copy link
Member

llvmbot commented Aug 27, 2025

@llvm/pr-subscribers-lldb

Author: Ilia Kuklin (kuilpd)

Changes

This patch adds unary nodes plus and minus, and introduces unary type conversions and integral promotion.


Patch is 22.54 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/155617.diff

13 Files Affected:

  • (modified) lldb/include/lldb/Symbol/TypeSystem.h (+7)
  • (modified) lldb/include/lldb/ValueObject/DILAST.h (+2)
  • (modified) lldb/include/lldb/ValueObject/DILEval.h (+2)
  • (modified) lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp (+96)
  • (modified) lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h (+8)
  • (modified) lldb/source/Symbol/CompilerType.cpp (+4-24)
  • (modified) lldb/source/Symbol/TypeSystem.cpp (+10)
  • (modified) lldb/source/ValueObject/DILEval.cpp (+151-32)
  • (modified) lldb/source/ValueObject/DILLexer.cpp (-1)
  • (modified) lldb/source/ValueObject/DILParser.cpp (+8-2)
  • (added) lldb/test/API/commands/frame/var-dil/expr/Arithmetic/Makefile (+3)
  • (added) lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py (+51)
  • (added) lldb/test/API/commands/frame/var-dil/expr/Arithmetic/main.cpp (+25)
diff --git a/lldb/include/lldb/Symbol/TypeSystem.h b/lldb/include/lldb/Symbol/TypeSystem.h
index 16a2e0b5a52fb..d74e6a0e05625 100644
--- a/lldb/include/lldb/Symbol/TypeSystem.h
+++ b/lldb/include/lldb/Symbol/TypeSystem.h
@@ -412,6 +412,13 @@ class TypeSystem : public PluginInterface,
   GetIntegralTemplateArgument(lldb::opaque_compiler_type_t type, size_t idx,
                               bool expand_pack);
 
+  // DIL
+
+  virtual bool IsPromotableIntegerType(lldb::opaque_compiler_type_t type);
+
+  virtual llvm::Expected<CompilerType>
+  DoIntegralPromotion(CompilerType from, ExecutionContextScope *exe_scope);
+
   // Dumping types
 
 #ifndef NDEBUG
diff --git a/lldb/include/lldb/ValueObject/DILAST.h b/lldb/include/lldb/ValueObject/DILAST.h
index 1d10755c46e39..900eb8a792a1e 100644
--- a/lldb/include/lldb/ValueObject/DILAST.h
+++ b/lldb/include/lldb/ValueObject/DILAST.h
@@ -32,6 +32,8 @@ enum class NodeKind {
 enum class UnaryOpKind {
   AddrOf, // "&"
   Deref,  // "*"
+  Minus,  // "-"
+  Plus,   // "+"
 };
 
 /// Forward declaration, for use in DIL AST nodes. Definition is at the very
diff --git a/lldb/include/lldb/ValueObject/DILEval.h b/lldb/include/lldb/ValueObject/DILEval.h
index 5a48c2c989f4d..11f44336cbf55 100644
--- a/lldb/include/lldb/ValueObject/DILEval.h
+++ b/lldb/include/lldb/ValueObject/DILEval.h
@@ -59,6 +59,8 @@ class Interpreter : Visitor {
   llvm::Expected<lldb::ValueObjectSP>
   Visit(const FloatLiteralNode *node) override;
 
+  llvm::Expected<lldb::ValueObjectSP>
+  UnaryConversion(lldb::ValueObjectSP valobj);
   llvm::Expected<CompilerType>
   PickIntegerType(lldb::TypeSystemSP type_system,
                   std::shared_ptr<ExecutionContextScope> ctx,
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
index 39aacdb58e694..4d4ca522e052a 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.cpp
@@ -7320,6 +7320,102 @@ CompilerType TypeSystemClang::GetTypeForFormatters(void *type) {
   return CompilerType();
 }
 
+bool TypeSystemClang::IsPromotableIntegerType(
+    lldb::opaque_compiler_type_t type) {
+  // Unscoped enums are always considered as promotable, even if their
+  // underlying type does not need to be promoted (e.g. "int").
+  bool is_signed = false;
+  bool isUnscopedEnumerationType =
+      IsEnumerationType(type, is_signed) && !IsScopedEnumerationType(type);
+  if (isUnscopedEnumerationType)
+    return true;
+
+  switch (GetBasicTypeEnumeration(type)) {
+  case lldb::eBasicTypeBool:
+  case lldb::eBasicTypeChar:
+  case lldb::eBasicTypeSignedChar:
+  case lldb::eBasicTypeUnsignedChar:
+  case lldb::eBasicTypeShort:
+  case lldb::eBasicTypeUnsignedShort:
+  case lldb::eBasicTypeWChar:
+  case lldb::eBasicTypeSignedWChar:
+  case lldb::eBasicTypeUnsignedWChar:
+  case lldb::eBasicTypeChar16:
+  case lldb::eBasicTypeChar32:
+    return true;
+
+  default:
+    return false;
+  }
+
+  llvm_unreachable("All cases handled above.");
+}
+
+llvm::Expected<CompilerType>
+TypeSystemClang::DoIntegralPromotion(CompilerType from,
+                                     ExecutionContextScope *exe_scope) {
+  if (!from.IsInteger() && !from.IsUnscopedEnumerationType())
+    return from;
+
+  if (!from.IsPromotableIntegerType())
+    return from;
+
+  if (from.IsUnscopedEnumerationType()) {
+    EnumDecl *enum_decl = GetAsEnumDecl(from);
+    CompilerType promotion_type = GetType(enum_decl->getPromotionType());
+    return DoIntegralPromotion(promotion_type, exe_scope);
+  }
+
+  lldb::BasicType builtin_type =
+      from.GetCanonicalType().GetBasicTypeEnumeration();
+  uint64_t from_size = 0;
+  if (builtin_type == lldb::eBasicTypeWChar ||
+      builtin_type == lldb::eBasicTypeSignedWChar ||
+      builtin_type == lldb::eBasicTypeUnsignedWChar ||
+      builtin_type == lldb::eBasicTypeChar16 ||
+      builtin_type == lldb::eBasicTypeChar32) {
+    // Find the type that can hold the entire range of values for our type.
+    bool is_signed = from.IsSigned();
+    llvm::Expected<uint64_t> from_size = from.GetByteSize(exe_scope);
+    if (!from_size)
+      return from_size.takeError();
+    CompilerType promote_types[] = {
+        GetBasicTypeFromAST(lldb::eBasicTypeInt),
+        GetBasicTypeFromAST(lldb::eBasicTypeUnsignedInt),
+        GetBasicTypeFromAST(lldb::eBasicTypeLong),
+        GetBasicTypeFromAST(lldb::eBasicTypeUnsignedLong),
+        GetBasicTypeFromAST(lldb::eBasicTypeLongLong),
+        GetBasicTypeFromAST(lldb::eBasicTypeUnsignedLongLong),
+    };
+    for (CompilerType &type : promote_types) {
+      llvm::Expected<uint64_t> byte_size = type.GetByteSize(exe_scope);
+      if (!byte_size)
+        return byte_size.takeError();
+      if (*from_size < *byte_size ||
+          (*from_size == *byte_size && is_signed == type.IsSigned())) {
+        return type;
+      }
+    }
+    llvm_unreachable("char type should fit into long long");
+  }
+
+  // Here we can promote only to "int" or "unsigned int".
+  CompilerType int_type = GetBasicTypeFromAST(lldb::eBasicTypeInt);
+  llvm::Expected<uint64_t> int_byte_size = int_type.GetByteSize(exe_scope);
+  if (!int_byte_size)
+    return int_byte_size.takeError();
+
+  // Signed integer types can be safely promoted to "int".
+  if (from.IsSigned()) {
+    return int_type;
+  }
+  // Unsigned integer types are promoted to "unsigned int" if "int" cannot hold
+  // their entire value range.
+  return (from_size == *int_byte_size)
+             ? GetBasicTypeFromAST(lldb::eBasicTypeUnsignedInt)
+             : int_type;
+}
+
 clang::EnumDecl *TypeSystemClang::GetAsEnumDecl(const CompilerType &type) {
   const clang::EnumType *enutype =
       llvm::dyn_cast<clang::EnumType>(ClangUtil::GetCanonicalQualType(type));
diff --git a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
index 709f89590ba3b..0ca1a1e2578b3 100644
--- a/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
+++ b/lldb/source/Plugins/TypeSystem/Clang/TypeSystemClang.h
@@ -939,6 +939,14 @@ class TypeSystemClang : public TypeSystem {
 
   CompilerType GetTypeForFormatters(void *type) override;
 
+  // DIL
+
+  bool IsPromotableIntegerType(lldb::opaque_compiler_type_t type) override;
+
+  llvm::Expected<CompilerType>
+  DoIntegralPromotion(CompilerType from,
+                      ExecutionContextScope *exe_scope) override;
+
 #define LLDB_INVALID_DECL_LEVEL UINT32_MAX
   // LLDB_INVALID_DECL_LEVEL is returned by CountDeclLevels if child_decl_ctx
   // could not be found in decl_ctx.
diff --git a/lldb/source/Symbol/CompilerType.cpp b/lldb/source/Symbol/CompilerType.cpp
index 62c0ddf51c012..a22e817f818ce 100644
--- a/lldb/source/Symbol/CompilerType.cpp
+++ b/lldb/source/Symbol/CompilerType.cpp
@@ -373,30 +373,10 @@ bool CompilerType::IsScalarOrUnscopedEnumerationType() const {
 }
 
 bool CompilerType::IsPromotableIntegerType() const {
-  // Unscoped enums are always considered as promotable, even if their
-  // underlying type does not need to be promoted (e.g. "int").
-  if (IsUnscopedEnumerationType())
-    return true;
-
-  switch (GetBasicTypeEnumeration()) {
-  case lldb::eBasicTypeBool:
-  case lldb::eBasicTypeChar:
-  case lldb::eBasicTypeSignedChar:
-  case lldb::eBasicTypeUnsignedChar:
-  case lldb::eBasicTypeShort:
-  case lldb::eBasicTypeUnsignedShort:
-  case lldb::eBasicTypeWChar:
-  case lldb::eBasicTypeSignedWChar:
-  case lldb::eBasicTypeUnsignedWChar:
-  case lldb::eBasicTypeChar16:
-  case lldb::eBasicTypeChar32:
-    return true;
-
-  default:
-    return false;
-  }
-
-  llvm_unreachable("All cases handled above.");
+  if (IsValid())
+    if (auto type_system_sp = GetTypeSystem())
+      return type_system_sp->IsPromotableIntegerType(m_type);
+  return false;
 }
 
 bool CompilerType::IsPointerToVoid() const {
diff --git a/lldb/source/Symbol/TypeSystem.cpp b/lldb/source/Symbol/TypeSystem.cpp
index f7d634ffa2dec..7df049084e6fb 100644
--- a/lldb/source/Symbol/TypeSystem.cpp
+++ b/lldb/source/Symbol/TypeSystem.cpp
@@ -123,6 +123,16 @@ CompilerType TypeSystem::GetTypeForFormatters(void *type) {
   return CompilerType(weak_from_this(), type);
 }
 
+bool TypeSystem::IsPromotableIntegerType(lldb::opaque_compiler_type_t type) {
+  return false;
+}
+
+llvm::Expected<CompilerType>
+TypeSystem::DoIntegralPromotion(CompilerType from,
+                                ExecutionContextScope *exe_scope) {
+  return CompilerType();
+}
+
 bool TypeSystem::IsTemplateType(lldb::opaque_compiler_type_t type) {
   return false;
 }
diff --git a/lldb/source/ValueObject/DILEval.cpp b/lldb/source/ValueObject/DILEval.cpp
index c6cf41ee9e9ee..c31b90ce83663 100644
--- a/lldb/source/ValueObject/DILEval.cpp
+++ b/lldb/source/ValueObject/DILEval.cpp
@@ -21,6 +21,104 @@
 
 namespace lldb_private::dil {
 
+static llvm::Expected<lldb::TypeSystemSP>
+GetTypeSystemFromCU(std::shared_ptr<ExecutionContextScope> ctx) {
+  auto stack_frame = ctx->CalculateStackFrame();
+  if (!stack_frame)
+    return llvm::createStringError("no stack frame in this context");
+  SymbolContext symbol_context =
+      stack_frame->GetSymbolContext(lldb::eSymbolContextCompUnit);
+  lldb::LanguageType language = symbol_context.comp_unit->GetLanguage();
+
+  symbol_context = stack_frame->GetSymbolContext(lldb::eSymbolContextModule);
+  return symbol_context.module_sp->GetTypeSystemForLanguage(language);
+}
+
+static CompilerType GetBasicType(lldb::TypeSystemSP type_system,
+                                 lldb::BasicType basic_type) {
+  if (type_system)
+    return type_system.get()->GetBasicTypeFromAST(basic_type);
+
+  return CompilerType();
+}
+
+static lldb::ValueObjectSP
+ArrayToPointerConversion(lldb::ValueObjectSP valobj,
+                         std::shared_ptr<ExecutionContextScope> ctx) {
+  assert(valobj->IsArrayType() &&
+         "an argument to array-to-pointer conversion must be an array");
+
+  uint64_t addr = valobj->GetLoadAddress();
+  ExecutionContext exe_ctx;
+  ctx->CalculateExecutionContext(exe_ctx);
+  return ValueObject::CreateValueObjectFromAddress(
+      "result", addr, exe_ctx,
+      valobj->GetCompilerType().GetArrayElementType(ctx.get()).GetPointerType(),
+      /* do_deref */ false);
+}
+
+llvm::Expected<lldb::ValueObjectSP>
+Interpreter::UnaryConversion(lldb::ValueObjectSP valobj) {
+  // Perform usual conversions for unary operators. At the moment this includes
+  // array-to-pointer and the integral promotion for eligible types.
+  llvm::Expected<lldb::TypeSystemSP> type_system =
+      GetTypeSystemFromCU(m_exe_ctx_scope);
+  if (!type_system)
+    return type_system.takeError();
+  CompilerType in_type = valobj->GetCompilerType();
+  CompilerType result_type;
+  if (valobj->IsBitfield()) {
+    // Promote bitfields. If `int` can represent the bitfield value, it is
+    // converted to `int`. Otherwise, if `unsigned int` can represent it, it
+    // is converted to `unsigned int`. Otherwise, it is treated as its
+    // underlying type.
+    uint32_t bitfield_size = valobj->GetBitfieldBitSize();
+    // Some bitfields have undefined size (e.g. result of ternary operation).
+    // The AST's `bitfield_size` of those is 0, and no promotion takes place.
+    if (bitfield_size > 0 && in_type.IsInteger()) {
+      CompilerType int_type = GetBasicType(*type_system, lldb::eBasicTypeInt);
+      CompilerType uint_type =
+          GetBasicType(*type_system, lldb::eBasicTypeUnsignedInt);
+      llvm::Expected<uint64_t> int_bit_size =
+          int_type.GetBitSize(m_exe_ctx_scope.get());
+      if (!int_bit_size)
+        return int_bit_size.takeError();
+      llvm::Expected<uint64_t> uint_bit_size =
+          uint_type.GetBitSize(m_exe_ctx_scope.get());
+      if (!uint_bit_size)
+        return int_bit_size.takeError();
+      if (bitfield_size < *int_bit_size ||
+          (in_type.IsSigned() && bitfield_size == *int_bit_size))
+        return valobj->CastToBasicType(int_type);
+      if (bitfield_size <= *uint_bit_size)
+        return valobj->CastToBasicType(uint_type);
+      // Re-create as a const value with the same underlying type
+      Scalar scalar;
+      bool resolved = valobj->ResolveValue(scalar);
+      if (!resolved)
+        return llvm::createStringError("invalid scalar value");
+      return ValueObject::CreateValueObjectFromScalar(m_target, scalar, in_type,
+                                                      "result");
+    }
+  }
+
+  if (in_type.IsArrayType())
+    valobj = ArrayToPointerConversion(valobj, m_exe_ctx_scope);
+
+  if (valobj->GetCompilerType().IsInteger() ||
+      valobj->GetCompilerType().IsUnscopedEnumerationType()) {
+    llvm::Expected<CompilerType> promoted_type =
+        type_system.get()->DoIntegralPromotion(valobj->GetCompilerType(),
+                                               m_exe_ctx_scope.get());
+    if (!promoted_type)
+      return promoted_type.takeError();
+    if (!promoted_type->CompareTypes(valobj->GetCompilerType()))
+      return valobj->CastToBasicType(*promoted_type);
+  }
+
+  return valobj;
+}
+
 static lldb::VariableSP DILFindVariable(ConstString name,
                                         VariableList &variable_list) {
   lldb::VariableSP exact_match;
@@ -175,21 +273,21 @@ Interpreter::Visit(const IdentifierNode *node) {
 llvm::Expected<lldb::ValueObjectSP>
 Interpreter::Visit(const UnaryOpNode *node) {
   Status error;
-  auto rhs_or_err = Evaluate(node->GetOperand());
-  if (!rhs_or_err)
-    return rhs_or_err;
+  auto op_or_err = Evaluate(node->GetOperand());
+  if (!op_or_err)
+    return op_or_err;
 
-  lldb::ValueObjectSP rhs = *rhs_or_err;
+  lldb::ValueObjectSP operand = *op_or_err;
 
   switch (node->GetKind()) {
   case UnaryOpKind::Deref: {
-    lldb::ValueObjectSP dynamic_rhs = rhs->GetDynamicValue(m_use_dynamic);
-    if (dynamic_rhs)
-      rhs = dynamic_rhs;
+    lldb::ValueObjectSP dynamic_op = operand->GetDynamicValue(m_use_dynamic);
+    if (dynamic_op)
+      operand = dynamic_op;
 
-    lldb::ValueObjectSP child_sp = rhs->Dereference(error);
+    lldb::ValueObjectSP child_sp = operand->Dereference(error);
     if (!child_sp && m_use_synthetic) {
-      if (lldb::ValueObjectSP synth_obj_sp = rhs->GetSyntheticValue()) {
+      if (lldb::ValueObjectSP synth_obj_sp = operand->GetSyntheticValue()) {
         error.Clear();
         child_sp = synth_obj_sp->Dereference(error);
       }
@@ -202,18 +300,57 @@ Interpreter::Visit(const UnaryOpNode *node) {
   }
   case UnaryOpKind::AddrOf: {
     Status error;
-    lldb::ValueObjectSP value = rhs->AddressOf(error);
+    lldb::ValueObjectSP value = operand->AddressOf(error);
     if (error.Fail())
       return llvm::make_error<DILDiagnosticError>(m_expr, error.AsCString(),
                                                   node->GetLocation());
 
     return value;
   }
+  case UnaryOpKind::Minus: {
+    llvm::Expected<lldb::ValueObjectSP> conv_op = UnaryConversion(operand);
+    if (!conv_op)
+      return conv_op;
+    operand = *conv_op;
+    CompilerType operand_type = operand->GetCompilerType();
+    if (!operand_type.IsScalarType()) {
+      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)
+      break;
+
+    bool negated = scalar.UnaryNegate();
+    if (negated)
+      return ValueObject::CreateValueObjectFromScalar(
+          m_target, scalar, operand->GetCompilerType(), "result");
+    break;
   }
-
-  // Unsupported/invalid operation.
-  return llvm::make_error<DILDiagnosticError>(
-      m_expr, "invalid ast: unexpected binary operator", node->GetLocation());
+  case UnaryOpKind::Plus: {
+    llvm::Expected<lldb::ValueObjectSP> conv_op = UnaryConversion(operand);
+    if (!conv_op)
+      return conv_op;
+    operand = *conv_op;
+    CompilerType operand_type = operand->GetCompilerType();
+    if (!operand_type.IsScalarType() &&
+        // Unary plus is allowed for pointers.
+        !operand_type.IsPointerType()) {
+      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());
+    }
+    return operand;
+  }
+  }
+  return llvm::make_error<DILDiagnosticError>(m_expr, "invalid unary operation",
+                                              node->GetLocation());
 }
 
 llvm::Expected<lldb::ValueObjectSP>
@@ -499,24 +636,6 @@ Interpreter::Visit(const BitFieldExtractionNode *node) {
   return child_valobj_sp;
 }
 
-static llvm::Expected<lldb::TypeSystemSP>
-GetTypeSystemFromCU(std::shared_ptr<StackFrame> ctx) {
-  SymbolContext symbol_context =
-      ctx->GetSymbolContext(lldb::eSymbolContextCompUnit);
-  lldb::LanguageType language = symbol_context.comp_unit->GetLanguage();
-
-  symbol_context = ctx->GetSymbolContext(lldb::eSymbolContextModule);
-  return symbol_context.module_sp->GetTypeSystemForLanguage(language);
-}
-
-static CompilerType GetBasicType(lldb::TypeSystemSP type_system,
-                                 lldb::BasicType basic_type) {
-  if (type_system)
-    return type_system.get()->GetBasicTypeFromAST(basic_type);
-
-  return CompilerType();
-}
-
 llvm::Expected<CompilerType>
 Interpreter::PickIntegerType(lldb::TypeSystemSP type_system,
                              std::shared_ptr<ExecutionContextScope> ctx,
diff --git a/lldb/source/ValueObject/DILLexer.cpp b/lldb/source/ValueObject/DILLexer.cpp
index 0b2288a9d9230..77879f50cc9ad 100644
--- a/lldb/source/ValueObject/DILLexer.cpp
+++ b/lldb/source/ValueObject/DILLexer.cpp
@@ -42,7 +42,6 @@ llvm::StringRef Token::GetTokenName(Kind kind) {
     return "minus";
   case Kind::period:
     return "period";
-    return "l_square";
   case Kind::plus:
     return "plus";
   case Kind::r_paren:
diff --git a/lldb/source/ValueObject/DILParser.cpp b/lldb/source/ValueObject/DILParser.cpp
index 8c4f7fdb25bea..b88345d18a6d4 100644
--- a/lldb/source/ValueObject/DILParser.cpp
+++ b/lldb/source/ValueObject/DILParser.cpp
@@ -95,7 +95,8 @@ ASTNodeUP DILParser::ParseExpression() { return ParseUnaryExpression(); }
 //    "*"
 //
 ASTNodeUP DILParser::ParseUnaryExpression() {
-  if (CurToken().IsOneOf({Token::amp, Token::star})) {
+  if (CurToken().IsOneOf(
+          {Token::amp, Token::star, Token::minus, Token::plus})) {
     Token token = CurToken();
     uint32_t loc = token.GetLocation();
     m_dil_lexer.Advance();
@@ -107,7 +108,12 @@ ASTNodeUP DILParser::ParseUnaryExpression() {
     case Token::amp:
       return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::AddrOf,
                                            std::move(rhs));
-
+    case Token::minus:
+      return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Minus,
+                                           std::move(rhs));
+    case Token::plus:
+      return std::make_unique<UnaryOpNode>(loc, UnaryOpKind::Plus,
+                                           std::move(rhs));
     default:
       llvm_unreachable("invalid token kind");
     }
diff --git a/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/Makefile b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/Makefile
new file mode 100644
index 0000000000000..99998b20bcb05
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/Makefile
@@ -0,0 +1,3 @@
+CXX_SOURCES := main.cpp
+
+include Makefile.rules
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
new file mode 100644
index 0000000000000..76edc5837cf2b
--- /dev/null
+++ b/lldb/test/API/commands/frame/var-dil/expr/Arithmetic/TestFrameVarDILArithmetic.py
@@ -0,0 +1,51 @@
+"""
+Test DIL arithmetic.
+"""
+
+import lldb
+from lldbsuite.test.lldbtest import *
+from lldbsuite.test.decorators import *
+from lldbsuite.test import lldbutil
+
+
+class TestFrameVarDILArithmetic(TestBase):
+    NO_DEBUG_INFO_TESTCASE = True
+
+    def test_arithmetic(self):
+        self.build...
[truncated]

@kuilpd
Copy link
Contributor Author

kuilpd commented Aug 27, 2025

I'm not sure if we want to create a separate plugin for DIL-related code to be implemented for different languages. For now, I put the code for integral promotion into TypeSystem plugin, it feels like it belongs there anyway. Plus I was able to directly use info from EnumDecl to correctly promote enumerations, without having to make enum's getPromotionType available elsewhere.

@kuilpd kuilpd requested a review from adrian-prantl September 3, 2025 16:38
@kuilpd
Copy link
Contributor Author

kuilpd commented Sep 3, 2025

Wanted to add: this patch is more about what to put away into a plugin and which plugin to choose. For now, I put only integral promotion into the TypeSystem plugin. In DIL I kept the code for array to pointer conversion, which as far as I understand should work with Swift, and bitfield conversion, since the check will only pass with C++.

Again, a more broad question: should we create a separate plugin for DIL-related code or find a spot for such code in existing related plugins, like integral promotion in TypeSystem?

@cmtice
Copy link
Contributor

cmtice commented Sep 8, 2025

Wanted to add: this patch is more about what to put away into a plugin and which plugin to choose. For now, I put only integral promotion into the TypeSystem plugin. In DIL I kept the code for array to pointer conversion, which as far as I understand should work with Swift, and bitfield conversion, since the check will only pass with C++.

Again, a more broad question: should we create a separate plugin for DIL-related code or find a spot for such code in existing related plugins, like integral promotion in TypeSystem?

@JDevlieghere , @jimingham , @adrian-prantl Do any of you have any thoughts or preferences about Ilya's questions re the TypeSystem plugin(s) and where's the right place to implement this or put this code?

@jimingham
Copy link
Collaborator

Do we ever want to support - in the DIL - something like:

swift_integer = c_integer + rust_integer

We do know how to do that (variations on the details of integer promotion and the like aside) and given these are data objects we can query for their integer values it seems somewhat artificial to disallow it. But in that case, whose promotion rules should we use?

That's why I've been arguing that we should have a DIL set of promotion rules where integers are not sized. That way all these sort of computations can be done naturally. So I would argue that the DIL code should be its own plugin, not trying to either be C++ or something in the background, nor trying to figure out based on the expression what "Real" Typesystem we should dispatch to.

When people want to answer the question "what would this expression do at this point in my code" they should be using the expression evaluator for that language. The DIL is there to support the widest range of data introspection tasks we can offer on generic structured data objects.

@labath
Copy link
Collaborator

labath commented Sep 9, 2025

Do we ever want to support - in the DIL - something like:

swift_integer = c_integer + rust_integer

We do know how to do that (variations on the details of integer promotion and the like aside) and given these are data objects we can query for their integer values it seems somewhat artificial to disallow it. But in that case, whose promotion rules should we use?

That's why I've been arguing that we should have a DIL set of promotion rules where integers are not sized. That way all these sort of computations can be done naturally.

I agree with that.

So I would argue that the DIL code should be its own plugin, not trying to either be C++ or something in the background, nor trying to figure out based on the expression what "Real" Typesystem we should dispatch to.

.. but I'm not sure what you meant by "its own plugin". Can you elaborate on that? If DIL is going to be language agnostic, then I don't think it needs to be a plugin. What I can imagine is having some sort of a DILTypeSystem plugin(*), which would be "The type system used in DIL expressions". Something like that might be necessary if we want to create ValueObjects representing DIL values (since ValueObjects require types, and types require type systems). OTOH, type systems deal with a lot more than integral types, so I can also imagine not doing that, and either having the DIL return something other than a ValueObject (for the cases where the result of evaluation is not a program value) or by teaching value objects to not require a type system.

(*) Technically, that also shouldn't be, as it's depended on by core functionality (the DIL), which would break if its plugged "out". However, it would be an implementation of the TypeSystem interface.

@kuilpd
Copy link
Contributor Author

kuilpd commented Sep 9, 2025

Do we ever want to support - in the DIL - something like:

swift_integer = c_integer + rust_integer

We do know how to do that (variations on the details of integer promotion and the like aside) and given these are data objects we can query for their integer values it seems somewhat artificial to disallow it. But in that case, whose promotion rules should we use?

We can do this, because the arithmetic itself is not typed. Math is implemented through Scalar class, which only operates on numbers itself (integer, IEEE float, IEEE double, etc). So we can retrieve the scalars from c_integer and rust_integer and add them together as scalars, then pick whatever compiler type we want the result to be. In this case, Int from Swift. I will show more of this logic once we get to binary operands.

That's why I've been arguing that we should have a DIL set of promotion rules where integers are not sized. That way all these sort of computations can be done naturally. So I would argue that the DIL code should be its own plugin, not trying to either be C++ or something in the background, nor trying to figure out based on the expression what "Real" Typesystem we should dispatch to.

Basically, the promotion rules of DIL math are from Scalar class. The language specific promotion rules I'm implementing now are for picking the resulting type (plus converting language-specific types to integer, e.g enum to int), and it doesn't really influence the math itself. It's so that the result can be a value ValueObject with a compiler type, which can be used wherever DIL was called from. By implementing the promotion rules in the respective type system we get the result closer to an actual language. It's just right now the type system for the result is taken from the current execution context, but that can be changed once we start implementing assignment operators.

When people want to answer the question "what would this expression do at this point in my code" they should be using the expression evaluator for that language. The DIL is there to support the widest range of data introspection tasks we can offer on generic structured data objects.

For the reason above, I don't think we have to sacrifice the correctness of result types to achieve the main goal of DIL. Every language will have to implement something in their type system anyway for DIL to work with it. If it doesn't matter to the language, it can just return a default type, like Int for Swift.

OTOH, type systems deal with a lot more than integral types, so I can also imagine not doing that, and either having the DIL return something other than a ValueObject (for the cases where the result of evaluation is not a program value) or by teaching value objects to not require a type system.

I would really like DIL to be just compatible with the rest of LLDB as is. I don't see much of a problem with picking a type for the result. Other than primitive types, we have to have a type anyway for the result to be usable.

@github-actions
Copy link

github-actions bot commented Sep 9, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

static lldb::ValueObjectSP
ArrayToPointerConversion(lldb::ValueObjectSP valobj,
std::shared_ptr<ExecutionContextScope> ctx) {
assert(valobj->IsArrayType() &&
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we should assert on errors in DIL.

Copy link
Collaborator

@adrian-prantl adrian-prantl Oct 16, 2025

Choose a reason for hiding this comment

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

Yes, the correct thing to do would be to return a ValueObject wrapping an Error, although it might be even nicer to return an Expected<ValueObjectSP>.

@cmtice
Copy link
Contributor

cmtice commented Sep 29, 2025

Have you thought about adding any of the Bitwise tests from lldb-eval? Some of them might be appropriate for this PR...

@cmtice
Copy link
Contributor

cmtice commented Sep 29, 2025

Ping? Could somebody else please review this PR? (As one of the main DIL authors, I don't feel right approving it myself...)

@kuilpd
Copy link
Contributor Author

kuilpd commented Sep 29, 2025

Have you thought about adding any of the Bitwise tests from lldb-eval? Some of them might be appropriate for this PR...

But this PR has only + and -, no bitwise operators. ~ will be added later. Or are you talking about some other tests?

@cmtice
Copy link
Contributor

cmtice commented Sep 30, 2025

Have you thought about adding any of the Bitwise tests from lldb-eval? Some of them might be appropriate for this PR...

But this PR has only + and -, no bitwise operators. ~ will be added later. Or are you talking about some other tests?

Oh, you're right. I was glancing at the tests and mistook the '~' for '-'. I apologize for the confusion.

@cmtice cmtice requested review from kastiglione and werat October 10, 2025 15:04
Copy link
Collaborator

@adrian-prantl adrian-prantl left a comment

Choose a reason for hiding this comment

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

@kastiglione Do you think the unary +/- could create any parsing ambiguity with ObjC method names?
For example, does image lookup -a take a DIL expression and someone could write image lookup -a +[MyClass myselector]?

int (&array_ref)[10] = array;
int *p_int0 = &array[0];

return 0; // Set a breakpoint here
Copy link
Collaborator

Choose a reason for hiding this comment

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

Generally, it is more reliable to set a breakpoint on a function call, since the return statement itself doesn't always produce an instruction with a line table entry that is outside the epilogue. So it's better to either write something like

void stop() {}

...

stop(); // break here
return 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I haven't been doing this for other tests, should I make a separate commit changing this in all DIL tests?


// DIL

virtual bool IsPromotableIntegerType(lldb::opaque_compiler_type_t type);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can you add a Doxygen comment explaining the semantics of this function?

virtual bool IsPromotableIntegerType(lldb::opaque_compiler_type_t type);

virtual llvm::Expected<CompilerType>
DoIntegralPromotion(CompilerType from, ExecutionContextScope *exe_scope);
Copy link
Collaborator

Choose a reason for hiding this comment

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

same here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not really sure what to write here, since the semantics is dependent on the type system. I wrote a general idea for C++, but I suspect that for Swift this might do opposite things, like not promoting integers at all and returning an error for Bool.

@jimingham
Copy link
Collaborator

jimingham commented Oct 16, 2025

@kastiglione Do you think the unary +/- could create any parsing ambiguity with ObjC method names? For example, does image lookup -a take a DIL expression and someone could write image lookup -a +[MyClass myselector]?

The <address-expression> option type runs expressions with the expression parser, and if they fail, fall back to name lookup or name lookup with offset. They don't run through the DIL. That's also a name, not an expression, and you "should" use -n for that. So I don't think <address-expressions>s would care about this.

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.

6 participants