[llvm][Support] formatv: non-negative-plus for integral numbers (#185008)

The older `format()` allows you to print a `+` sign for non-negative    
integral numbers upon request.                                          
                                                                        
Examples:                                                               
                                                                        
```c++                                                                  
format("%+d", 255); // -> "+255"                                        
format("%+d", -12); // -> "-12"                                         
```                                                                     
                                                                        
This change adds the ability to do the same with `formatv()`:           
                                                                        
```c++                                                                  
formatv("{0:+d}", 255); // -> "+255"                                    
formatv("{0:+d}", -12); // -> "-12"                                     
```                                                                     
                                                                        
The default behaviour is not changed. That means, for a format specifier
like "{0:d}" we still print the positive integer without a "+" prefix.  
                                                                        
The special case of "+0" is exactly like it is handled with `format()`: 
                                                                        
```c++                                                                  
format("%+d", 0); // -> "+0"                                            
```                                                                     
                                                                        
This work is related to #35980.
This commit is contained in:
Konrad Kleine 2026-03-12 13:02:14 +01:00 committed by GitHub
parent 0c6bca6e77
commit bcd8e64884
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 96 additions and 35 deletions

View File

@ -112,6 +112,9 @@ protected:
/// | X+ / X | Hex + prefix, upper | 42 | 0x2A | Minimum # digits |
/// | N / n | Digit grouped number | 123456 | 123,456 | Ignored |
/// | D / d | Integer | 100000 | 100000 | Ignored |
/// |+D / +d | Integer with + prefix| 100000 | +100000 | Ignored |
/// | | for numbers => 0 | | | |
/// | + | Same as +D / +d | | | |
/// | (empty) | Same as D / d | | | |
/// ==========================================================================
///
@ -130,6 +133,10 @@ public:
return;
}
// A + prefix indicates that a plus sign shall be
// prefixed to non-negative numbers.
bool NonNegativePlus = Style.consume_front('+');
IntegerStyle IS = IntegerStyle::Integer;
if (Style.consume_front("N") || Style.consume_front("n"))
IS = IntegerStyle::Number;
@ -138,7 +145,11 @@ public:
Style.consumeInteger(10, Digits);
assert(Style.empty() && "Invalid integral format style!");
write_integer(Stream, V, Digits, IS);
// We currently only support the + for integer style numbers.
NonNegativePlus = NonNegativePlus && IS == IntegerStyle::Integer;
write_integer(Stream, V, Digits, IS, NonNegativePlus);
}
};

View File

@ -27,17 +27,18 @@ LLVM_ABI size_t getDefaultPrecision(FloatStyle Style);
LLVM_ABI bool isPrefixedHexStyle(HexPrintStyle S);
LLVM_ABI void write_integer(raw_ostream &S, unsigned int N, size_t MinDigits,
IntegerStyle Style);
IntegerStyle Style, bool NonNegativePlus = false);
LLVM_ABI void write_integer(raw_ostream &S, int N, size_t MinDigits,
IntegerStyle Style);
IntegerStyle Style, bool NonNegativePlus = false);
LLVM_ABI void write_integer(raw_ostream &S, unsigned long N, size_t MinDigits,
IntegerStyle Style);
IntegerStyle Style, bool NonNegativePlus = false);
LLVM_ABI void write_integer(raw_ostream &S, long N, size_t MinDigits,
IntegerStyle Style);
IntegerStyle Style, bool NonNegativePlus = false);
LLVM_ABI void write_integer(raw_ostream &S, unsigned long long N,
size_t MinDigits, IntegerStyle Style);
size_t MinDigits, IntegerStyle Style,
bool NonNegativePlus = false);
LLVM_ABI void write_integer(raw_ostream &S, long long N, size_t MinDigits,
IntegerStyle Style);
IntegerStyle Style, bool NonNegativePlus = false);
LLVM_ABI void write_hex(raw_ostream &S, uint64_t N, HexPrintStyle Style,
std::optional<size_t> Width = std::nullopt);

View File

@ -13,6 +13,7 @@
#include "llvm/Support/Compiler.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/raw_ostream.h"
#include <cassert>
#include <cinttypes>
@ -64,7 +65,7 @@ static void printOperand(raw_ostream &OS, const DIDumpOptions &DumpOpts,
// The offsets are all encoded in a unsigned form, but in practice
// consumers use them signed. It's most certainly legacy due to
// the lack of signed variants in the first Dwarf standards.
OS << format(" %+" PRId64, int64_t(Operand));
OS << formatv(" {0:+d}", int64_t(Operand));
break;
case CFIProgram::OT_FactoredCodeOffset: // Always Unsigned
if (P.codeAlign())

View File

@ -11,6 +11,7 @@
#include "llvm/DebugInfo/DWARF/DWARFUnit.h"
#include "llvm/DebugInfo/DWARF/LowLevel/DWARFExpression.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/FormatVariadic.h"
#include <cassert>
#include <cstdint>
@ -153,7 +154,7 @@ static bool printOp(const DWARFExpression::Operation *Op, raw_ostream &OS,
static_cast<uint8_t>(Expr->getData()[Offset++]));
} else {
if (Signed)
OS << format(" %+" PRId64, (int64_t)Op->getRawOperand(Operand));
OS << formatv(" {0:+d}", (int64_t)Op->getRawOperand(Operand));
else if (Op->getCode() != DW_OP_entry_value &&
Op->getCode() != DW_OP_GNU_entry_value)
OS << format(" 0x%" PRIx64, Op->getRawOperand(Operand));
@ -257,7 +258,7 @@ static bool printCompactDWARFExpr(
raw_svector_ostream S(Stack.emplace_back().String);
S << RegName;
if (Offset)
S << format("%+" PRId64, Offset);
S << formatv("{0:+d}", Offset);
break;
}
case dwarf::DW_OP_entry_value:
@ -310,7 +311,7 @@ static bool printCompactDWARFExpr(
raw_svector_ostream S(Stack.emplace_back().String);
S << RegName;
if (Offset)
S << format("%+" PRId64, Offset);
S << formatv("{0:+d}", Offset);
} else {
return UnknownOpcode(OS, Opcode, std::nullopt);
}
@ -364,7 +365,7 @@ bool prettyPrintRegisterOp(DWARFUnit *U, raw_ostream &OS,
if (!RegName.empty()) {
if ((Opcode >= DW_OP_breg0 && Opcode <= DW_OP_breg31) ||
Opcode == DW_OP_bregx || SubOpcode == DW_OP_LLVM_aspace_bregx)
OS << ' ' << RegName << format("%+" PRId64, Operands[OpNum]);
OS << ' ' << RegName << formatv("{0:+d}", int64_t(Operands[OpNum]));
else
OS << ' ' << RegName.data();

View File

@ -53,7 +53,8 @@ static void writeWithCommas(raw_ostream &S, ArrayRef<char> Buffer) {
template <typename T>
static void write_unsigned_impl(raw_ostream &S, T N, size_t MinDigits,
IntegerStyle Style, bool IsNegative) {
IntegerStyle Style, bool IsNegative,
bool NonNegativePlus) {
static_assert(std::is_unsigned_v<T>, "Value is not unsigned!");
char NumberBuffer[128];
@ -61,6 +62,8 @@ static void write_unsigned_impl(raw_ostream &S, T N, size_t MinDigits,
if (IsNegative)
S << '-';
else if (NonNegativePlus)
S << '+';
if (Len < MinDigits && Style != IntegerStyle::Number) {
for (size_t I = Len; I < MinDigits; ++I)
@ -76,59 +79,61 @@ static void write_unsigned_impl(raw_ostream &S, T N, size_t MinDigits,
template <typename T>
static void write_unsigned(raw_ostream &S, T N, size_t MinDigits,
IntegerStyle Style, bool IsNegative = false) {
IntegerStyle Style, bool IsNegative = false,
bool NonNegativePlus = false) {
// Output using 32-bit div/mod if possible.
if (N == static_cast<uint32_t>(N))
write_unsigned_impl(S, static_cast<uint32_t>(N), MinDigits, Style,
IsNegative);
IsNegative, NonNegativePlus);
else
write_unsigned_impl(S, N, MinDigits, Style, IsNegative);
write_unsigned_impl(S, N, MinDigits, Style, IsNegative, NonNegativePlus);
}
template <typename T>
static void write_signed(raw_ostream &S, T N, size_t MinDigits,
IntegerStyle Style) {
IntegerStyle Style, bool NonNegativePlus = false) {
static_assert(std::is_signed_v<T>, "Value is not signed!");
using UnsignedT = std::make_unsigned_t<T>;
if (N >= 0) {
write_unsigned(S, static_cast<UnsignedT>(N), MinDigits, Style);
write_unsigned(S, static_cast<UnsignedT>(N), MinDigits, Style, false,
NonNegativePlus);
return;
}
UnsignedT UN = -(UnsignedT)N;
write_unsigned(S, UN, MinDigits, Style, true);
write_unsigned(S, UN, MinDigits, Style, true, NonNegativePlus);
}
void llvm::write_integer(raw_ostream &S, unsigned int N, size_t MinDigits,
IntegerStyle Style) {
write_unsigned(S, N, MinDigits, Style);
IntegerStyle Style, bool NonNegativePlus) {
write_unsigned(S, N, MinDigits, Style, false, NonNegativePlus);
}
void llvm::write_integer(raw_ostream &S, int N, size_t MinDigits,
IntegerStyle Style) {
write_signed(S, N, MinDigits, Style);
IntegerStyle Style, bool NonNegativePlus) {
write_signed(S, N, MinDigits, Style, NonNegativePlus);
}
void llvm::write_integer(raw_ostream &S, unsigned long N, size_t MinDigits,
IntegerStyle Style) {
write_unsigned(S, N, MinDigits, Style);
IntegerStyle Style, bool NonNegativePlus) {
write_unsigned(S, N, MinDigits, Style, false, NonNegativePlus);
}
void llvm::write_integer(raw_ostream &S, long N, size_t MinDigits,
IntegerStyle Style) {
write_signed(S, N, MinDigits, Style);
IntegerStyle Style, bool NonNegativePlus) {
write_signed(S, N, MinDigits, Style, NonNegativePlus);
}
void llvm::write_integer(raw_ostream &S, unsigned long long N, size_t MinDigits,
IntegerStyle Style) {
write_unsigned(S, N, MinDigits, Style);
IntegerStyle Style, bool NonNegativePlus) {
write_unsigned(S, N, MinDigits, Style, false, NonNegativePlus);
}
void llvm::write_integer(raw_ostream &S, long long N, size_t MinDigits,
IntegerStyle Style) {
write_signed(S, N, MinDigits, Style);
IntegerStyle Style, bool NonNegativePlus) {
write_signed(S, N, MinDigits, Style, NonNegativePlus);
}
void llvm::write_hex(raw_ostream &S, uint64_t N, HexPrintStyle Style,

View File

@ -391,7 +391,7 @@ TEST(FormatVariadicTest, IntegralHexFormatting) {
}
template <typename FormatTy>
std::string printToString(unsigned MaxN, FormatTy &&Fmt) {
static std::string printToString(FormatTy &&Fmt, unsigned MaxN = 100) {
std::vector<char> Dst(MaxN + 2);
int N = Fmt.snprint(Dst.data(), Dst.size());
Dst.back() = 0;
@ -404,9 +404,8 @@ TEST(FormatAndFormatvTest, EquivalentHexFormatting) {
// Here's the old format() way of printing a hex number with
// dynamic width and precision, both being the same.
EXPECT_EQ(
"0x00000000ff",
printToString(100, format("0x%*.*" PRIx64, HexDigits, HexDigits, N)));
EXPECT_EQ("0x00000000ff",
printToString(format("0x%*.*" PRIx64, HexDigits, HexDigits, N)));
// Now, do the same with formatv()
EXPECT_EQ("0x00000000ff",
@ -414,6 +413,49 @@ TEST(FormatAndFormatvTest, EquivalentHexFormatting) {
.str());
}
TEST(FormatAndFormatvTest, NonNegativePlusInteger) {
EXPECT_EQ("-255", printToString(format("%+d", -255)));
EXPECT_EQ("+255", printToString(format("%+d", 255)));
EXPECT_EQ("+0", printToString(format("%+d", 0)));
EXPECT_EQ("-7", printToString(format("%+d", -7)));
// Check that +d works for signed and unsigned integral values.
// Ensure the default is not changed (a + is not added without requesting it).
EXPECT_EQ("+1", formatv("{0:+d}", static_cast<unsigned int>(1)).str());
EXPECT_EQ("+2", formatv("{0:+d}", static_cast<int>(2)).str());
EXPECT_EQ("+3", formatv("{0:+d}", static_cast<unsigned long>(3)).str());
EXPECT_EQ("+4", formatv("{0:+d}", static_cast<long>(4)).str());
EXPECT_EQ("+5", formatv("{0:+d}", static_cast<unsigned long long>(5)).str());
EXPECT_EQ("+6", formatv("{0:+d}", static_cast<long long>(6)).str());
EXPECT_EQ("-7", formatv("{0:+d}", static_cast<int>(-7)).str());
EXPECT_EQ("-8", formatv("{0:+d}", static_cast<long>(-8)).str());
EXPECT_EQ("-9", formatv("{0:+d}", static_cast<long long>(-9)).str());
EXPECT_EQ("11", formatv("{0:d}", static_cast<unsigned int>(11)).str());
EXPECT_EQ("22", formatv("{0:d}", static_cast<int>(22)).str());
EXPECT_EQ("33", formatv("{0:d}", static_cast<unsigned long>(33)).str());
EXPECT_EQ("44", formatv("{0:d}", static_cast<long>(44)).str());
EXPECT_EQ("55", formatv("{0:d}", static_cast<unsigned long long>(55)).str());
EXPECT_EQ("66", formatv("{0:d}", static_cast<long long>(66)).str());
EXPECT_EQ("-77", formatv("{0:d}", static_cast<int>(-77)).str());
EXPECT_EQ("-88", formatv("{0:d}", static_cast<long>(-88)).str());
EXPECT_EQ("-99", formatv("{0:d}", static_cast<long long>(-99)).str());
// Ensure that 0 is also prefixed with + (old behaviour from format(), see
// above).
EXPECT_EQ("+0", formatv("{0:+d}", 0).str());
EXPECT_EQ("0", formatv("{0:d}", 0).str());
// Ensure that an empty or otherwise empty format string is also working as
// expected
EXPECT_EQ("+333", formatv("{0:+}", 333).str());
EXPECT_EQ("444", formatv("{0:}", 444).str());
// Try with width modifier as well, to ensure that the + is still present and
// that the width is correct.
EXPECT_EQ(" -1", formatv("{0,4:+d}", -1).str());
EXPECT_EQ(" +1", formatv("{0,4:+d}", 1).str());
}
TEST(FormatVariadicTest, PointerFormatting) {
// 1. Trivial cases. Hex is default. Default Precision is pointer width.
if (sizeof(void *) == 4) {