[LLDB] Add unary plus and minus to DIL (#155617)

This patch adds unary nodes plus and minus, introduces unary type
conversions, and adds integral promotion to the type system.
This commit is contained in:
Ilia Kuklin
2025-11-24 19:08:53 +05:00
committed by GitHub
parent 83765f435d
commit d5927a6172
16 changed files with 427 additions and 59 deletions

View File

@@ -8,7 +8,7 @@ expression = unary_expression ;
unary_expression = postfix_expression
| unary_operator expression ;
unary_operator = "*" | "&" ;
unary_operator = "*" | "&" | "+" | "-";
postfix_expression = primary_expression
| postfix_expression "[" integer_literal "]"

View File

@@ -411,6 +411,18 @@ public:
GetIntegralTemplateArgument(lldb::opaque_compiler_type_t type, size_t idx,
bool expand_pack);
// DIL
/// Checks if the type is eligible for integral promotion.
virtual bool IsPromotableIntegerType(lldb::opaque_compiler_type_t type);
/// Perform integral promotion on a given type.
/// This promotes eligible types (boolean, integers, unscoped enumerations)
/// to a larger integer type according to type system rules.
/// \returns Promoted type.
virtual llvm::Expected<CompilerType>
DoIntegralPromotion(CompilerType from, ExecutionContextScope *exe_scope);
// Dumping types
#ifndef NDEBUG

View File

@@ -33,6 +33,8 @@ enum class NodeKind {
enum class UnaryOpKind {
AddrOf, // "&"
Deref, // "*"
Minus, // "-"
Plus, // "+"
};
/// Forward declaration, for use in DIL AST nodes. Definition is at the very

View File

@@ -61,6 +61,10 @@ private:
llvm::Expected<lldb::ValueObjectSP>
Visit(const BooleanLiteralNode *node) override;
/// Perform usual unary conversions on a value. At the moment this
/// includes array-to-pointer and integral promotion for eligible types.
llvm::Expected<lldb::ValueObjectSP>
UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location);
llvm::Expected<CompilerType>
PickIntegerType(lldb::TypeSystemSP type_system,
std::shared_ptr<ExecutionContextScope> ctx,

View File

@@ -7346,6 +7346,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));

View File

@@ -938,6 +938,14 @@ public:
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.

View File

@@ -370,30 +370,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 {

View File

@@ -123,6 +123,17 @@ 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 llvm::createStringError(
"Integral promotion is not implemented for this TypeSystem");
}
bool TypeSystem::IsTemplateType(lldb::opaque_compiler_type_t type) {
return false;
}

View File

@@ -21,6 +21,101 @@
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(ValueObject &valobj, ExecutionContextScope &ctx) {
uint64_t addr = valobj.GetLoadAddress();
ExecutionContext exe_ctx;
ctx.CalculateExecutionContext(exe_ctx);
return ValueObject::CreateValueObjectFromAddress(
"result", addr, exe_ctx,
valobj.GetCompilerType().GetArrayElementType(&ctx).GetPointerType(),
/* do_deref */ false);
}
llvm::Expected<lldb::ValueObjectSP>
Interpreter::UnaryConversion(lldb::ValueObjectSP valobj, uint32_t location) {
if (!valobj)
return llvm::make_error<DILDiagnosticError>(m_expr, "invalid value object",
location);
llvm::Expected<lldb::TypeSystemSP> type_system =
GetTypeSystemFromCU(m_exe_ctx_scope);
if (!type_system)
return type_system.takeError();
CompilerType in_type = valobj->GetCompilerType();
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;
@@ -147,6 +242,10 @@ Interpreter::Interpreter(lldb::TargetSP target, llvm::StringRef expr,
llvm::Expected<lldb::ValueObjectSP> Interpreter::Evaluate(const ASTNode *node) {
// Evaluate an AST.
auto value_or_error = node->Accept(this);
// Convert SP with a nullptr to an error.
if (value_or_error && !*value_or_error)
return llvm::make_error<DILDiagnosticError>(m_expr, "invalid value object",
node->GetLocation());
// Return the computed value-or-error. The caller is responsible for
// checking if an error occured during the evaluation.
return value_or_error;
@@ -175,21 +274,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 +301,69 @@ 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: {
if (operand->GetCompilerType().IsReferenceType()) {
operand = operand->Dereference(error);
if (error.Fail())
return error.ToError();
}
llvm::Expected<lldb::ValueObjectSP> conv_op =
UnaryConversion(operand, node->GetOperand()->GetLocation());
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;
// Unsupported/invalid operation.
return llvm::make_error<DILDiagnosticError>(
m_expr, "invalid ast: unexpected binary operator", node->GetLocation());
bool negated = scalar.UnaryNegate();
if (negated)
return ValueObject::CreateValueObjectFromScalar(
m_target, scalar, operand->GetCompilerType(), "result");
break;
}
case UnaryOpKind::Plus: {
if (operand->GetCompilerType().IsReferenceType()) {
operand = operand->Dereference(error);
if (error.Fail())
return error.ToError();
}
llvm::Expected<lldb::ValueObjectSP> conv_op =
UnaryConversion(operand, node->GetOperand()->GetLocation());
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 +649,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,

View File

@@ -93,9 +93,12 @@ ASTNodeUP DILParser::ParseExpression() { return ParseUnaryExpression(); }
// unary_operator:
// "&"
// "*"
// "+"
// "-"
//
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 +110,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");
}

View File

@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@@ -0,0 +1,46 @@
"""
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()
lldbutil.run_to_source_breakpoint(
self, "Set a breakpoint here", lldb.SBFileSpec("main.cpp")
)
self.runCmd("settings set target.experimental.use-DIL true")
# Check unary results and integral promotion
self.expect_var_path("+0", value="0")
self.expect_var_path("-0", value="0")
self.expect_var_path("+1", value="1")
self.expect_var_path("-1", value="-1")
self.expect_var_path("-9223372036854775808", value="9223372036854775808")
self.expect_var_path("s", value="10", type="short")
self.expect_var_path("+s", value="10", type="int")
self.expect_var_path("-s", value="-10", type="int")
self.expect_var_path("+us", value="1", type="int")
self.expect_var_path("-us", value="-1", type="int")
self.expect_var_path("+ref", value="2", type="int")
self.expect_var_path("-ref", value="-2", type="int")
self.expect_var_path("+0.0", value="0")
self.expect_var_path("-0.0", value="-0")
self.expect_var_path("+enum_one", value="1")
self.expect_var_path("-enum_one", value="-1")
self.expect_var_path("+wchar", value="1")
self.expect_var_path("+char16", value="2")
self.expect_var_path("+char32", value="3")
self.expect_var_path("-bitfield.a", value="-1", type="int")
self.expect_var_path("+bitfield.a", value="1", type="int")
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")

View File

@@ -0,0 +1,23 @@
#include <cstdint>
int main(int argc, char **argv) {
short s = 10;
unsigned short us = 1;
int x = 2;
int &ref = x;
enum Enum { kZero, kOne } enum_one = kOne;
wchar_t wchar = 1;
char16_t char16 = 2;
char32_t char32 = 3;
struct BitFieldStruct {
char a : 4;
int b : 32;
unsigned int c : 32;
uint64_t d : 48;
};
BitFieldStruct bitfield = {1, 2, 3, 4};
return 0; // Set a breakpoint here
}

View File

@@ -0,0 +1,3 @@
CXX_SOURCES := main.cpp
include Makefile.rules

View File

@@ -0,0 +1,29 @@
"""
Test DIL pointer arithmetic.
"""
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
from lldbsuite.test import lldbutil
class TestFrameVarDILPointerArithmetic(TestBase):
NO_DEBUG_INFO_TESTCASE = True
def test_pointer_arithmetic(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")
self.expect_var_path("+array", type="int *")
self.expect_var_path("+array_ref", type="int *")
self.expect_var_path("+p_int0", type="int *")
self.expect(
"frame var -- '-p_int0'",
error=True,
substrs=["invalid argument type 'int *' to unary expression"],
)

View File

@@ -0,0 +1,11 @@
void stop() {}
int main(int argc, char **argv) {
int array[10];
array[0] = 0;
int (&array_ref)[10] = array;
int *p_int0 = &array[0];
stop(); // Set a breakpoint here
return 0;
}