https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/204606
>From 9770a9ee4abf4275a27635fa45bdf1d9da21b326 Mon Sep 17 00:00:00 2001 From: Xavier Roche <[email protected]> Date: Thu, 18 Jun 2026 15:50:59 +0200 Subject: [PATCH 1/3] [Clang] POC: __builtin_to_chars / __builtin_from_chars Proof of concept for constexpr and runtime integer-to-text conversion as a compiler builtin, including _BitInt wider than 128 bits. Motivated by the P3666 _BitInt library-support discussion and P3652's constexpr <charconv>: a builtin folds the conversion in the constant evaluator without spending the program's -fconstexpr-steps budget, which the library division loop cannot do for very wide values. Both builtins are usable in a constant expression (classic evaluator) and at run time. Sema sets the result to the buffer pointer type; constant evaluation lives in PointerExprEvaluator using host APInt arithmetic; runtime codegen is an inline loop with no new runtime-library symbol. The bytecode interpreter (-fexperimental-new-constant-interpreter) is not implemented. Local proof of concept, not intended for upstream review. Assisted-by: Claude (Anthropic) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --- clang/include/clang/Basic/Builtins.td | 14 + .../clang/Basic/DiagnosticSemaKinds.td | 10 + clang/lib/AST/ExprConstant.cpp | 205 ++++++++++++++ clang/lib/CodeGen/CGBuiltin.cpp | 268 ++++++++++++++++++ clang/lib/Sema/SemaChecking.cpp | 138 +++++++++ clang/test/CodeGen/builtin-charconv.c | 21 ++ clang/test/Sema/builtin-charconv.c | 40 +++ .../SemaCXX/builtin-charconv-constexpr.cpp | 115 ++++++++ clang/test/SemaCXX/builtin-charconv-steps.cpp | 25 ++ 9 files changed, 836 insertions(+) create mode 100644 clang/test/CodeGen/builtin-charconv.c create mode 100644 clang/test/Sema/builtin-charconv.c create mode 100644 clang/test/SemaCXX/builtin-charconv-constexpr.cpp create mode 100644 clang/test/SemaCXX/builtin-charconv-steps.cpp diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 053a257ba6d4a..5e527a2400993 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -4750,6 +4750,20 @@ def MulOverflow : Builtin { let Prototype = "bool(...)"; } +// POC: constexpr-capable charconv primitives for integers of any width, +// including _BitInt(N > 128). Return type (char* / const char*) is set in Sema. +def ToChars : Builtin { + let Spellings = ["__builtin_to_chars"]; + let Attributes = [NoThrow, CustomTypeChecking, Constexpr]; + let Prototype = "int(...)"; +} + +def FromChars : Builtin { + let Spellings = ["__builtin_from_chars"]; + let Attributes = [NoThrow, CustomTypeChecking, Constexpr]; + let Prototype = "int(...)"; +} + class UOverflowTemplate : Template<["unsigned int", "unsigned long int", "unsigned long long int"], ["_overflow", "l_overflow", "ll_overflow"]>; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index f84cd8dca6d4c..a4307b6db6ab9 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -9704,6 +9704,16 @@ def err_overflow_builtin_must_be_ptr_int : Error< def err_overflow_builtin_bit_int_max_size : Error< "__builtin_mul_overflow does not support 'signed _BitInt' operands of more " "than %0 bits">; +def err_charconv_builtin_must_be_char_ptr : Error< + "argument %0 to %1 must be a pointer to " + "%select{|non-const }2'char' (%3 invalid)">; +def err_charconv_builtin_must_be_int_ptr : Error< + "value argument to %0 must be a pointer to a non-const integer type " + "(%1 invalid)">; +def err_charconv_builtin_must_be_int : Error< + "value argument to %0 must be an integer type (%1 invalid)">; +def err_charconv_builtin_invalid_base : Error< + "base argument to %0 must be between 2 and 36 (%1 invalid)">; def err_expected_struct_pointer_argument : Error< "expected pointer to struct as %ordinal0 argument to %1, found %2">; def err_expected_callable_argument : Error< diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 6ac16c2b831d2..0d918b5679156 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -10708,6 +10708,211 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, } } + case Builtin::BI__builtin_to_chars: { + // (char *first, char *last, T value, int base) -> char *. + LValue First, Last; + if (!EvaluatePointer(E->getArg(0), First, Info) || + !EvaluatePointer(E->getArg(1), Last, Info)) + return false; + APSInt Value, Base; + if (!EvaluateInteger(E->getArg(2), Value, Info) || + !EvaluateInteger(E->getArg(3), Base, Info)) + return false; + + uint64_t B = Base.getZExtValue(); + if (B < 2 || B > 36) { + Info.FFDiag(E->getArg(3)); + return false; + } + + // Sign and magnitude. Widen by one bit so negating the most-negative + // value cannot overflow. + bool Neg = Value.isSigned() && Value.isNegative(); + APInt Mag = Value.isSigned() ? Value.sext(Value.getBitWidth() + 1) + : Value.zext(Value.getBitWidth() + 1); + if (Neg) + Mag.negate(); + + static const char Table[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + SmallVector<char, 64> Rev; // least-significant digit first + if (Mag.isZero()) { + Rev.push_back('0'); + } else { + APInt BaseAP(Mag.getBitWidth(), B); + while (!Mag.isZero()) { + APInt Q, R; + APInt::udivrem(Mag, BaseAP, Q, R); + Rev.push_back(Table[R.getZExtValue()]); + Mag = std::move(Q); + } + } + uint64_t Len = Rev.size() + (Neg ? 1 : 0); + + // Capacity check against [first, last). char is one byte. + if (!HasSameBase(First, Last)) { + Info.FFDiag(E); + return false; + } + int64_t Cap = + (Last.getLValueOffset() - First.getLValueOffset()).getQuantity(); + if (Cap < 0 || (uint64_t)Cap < Len) + return ZeroInitialization(E); // buffer too small -> nullptr + + QualType CharTy = E->getArg(0)->getType()->getPointeeType(); + unsigned CW = Info.Ctx.getCharWidth(); + bool CharUnsigned = !CharTy->isSignedIntegerType(); + auto WriteChar = [&](LValue &Dest, char C) -> bool { + APValue V{APSInt(APInt(CW, (uint64_t)(unsigned char)C), CharUnsigned)}; + return handleAssignment(Info, E, Dest, CharTy, V) && + HandleLValueArrayAdjustment(Info, E, Dest, CharTy, 1); + }; + + LValue Dest = First; + if (Neg && !WriteChar(Dest, '-')) + return false; + for (char C : llvm::reverse(Rev)) + if (!WriteChar(Dest, C)) + return false; + + // Return the past-the-end write pointer. + Result = First; + return HandleLValueArrayAdjustment(Info, E, Result, CharTy, Len); + } + + case Builtin::BI__builtin_from_chars: { + // (const char *first, const char *last, T *value, int base, int *ec) + // -> const char *. + LValue First, Last, ValPtr, EcPtr; + if (!EvaluatePointer(E->getArg(0), First, Info) || + !EvaluatePointer(E->getArg(1), Last, Info) || + !EvaluatePointer(E->getArg(2), ValPtr, Info) || + !EvaluatePointer(E->getArg(4), EcPtr, Info)) + return false; + APSInt Base; + if (!EvaluateInteger(E->getArg(3), Base, Info)) + return false; + uint64_t B = Base.getZExtValue(); + if (B < 2 || B > 36) { + Info.FFDiag(E->getArg(3)); + return false; + } + + if (!HasSameBase(First, Last)) { + Info.FFDiag(E); + return false; + } + int64_t Len = + (Last.getLValueOffset() - First.getLValueOffset()).getQuantity(); + if (Len < 0) + Len = 0; + + QualType CharTy = E->getArg(0)->getType()->getPointeeType(); + QualType ValTy = E->getArg(2)->getType()->getPointeeType(); + QualType EcTy = E->getArg(4)->getType()->getPointeeType(); + unsigned VW = Info.Ctx.getIntWidth(ValTy); + bool VSigned = ValTy->isSignedIntegerType(); + + auto DigitVal = [&](char C) -> int { + int D; + if (C >= '0' && C <= '9') + D = C - '0'; + else if (C >= 'a' && C <= 'z') + D = C - 'a' + 10; + else if (C >= 'A' && C <= 'Z') + D = C - 'A' + 10; + else + return -1; + return (uint64_t)D < B ? D : -1; + }; + + LValue Cur = First; + int64_t Pos = 0; + auto ReadChar = [&](char &C) -> bool { + APValue V; + if (!handleLValueToRValueConversion(Info, E, CharTy, Cur, V) || + !V.isInt()) + return false; + C = (char)V.getInt().getZExtValue(); + return true; + }; + auto Advance = [&]() -> bool { + ++Pos; + return HandleLValueArrayAdjustment(Info, E, Cur, CharTy, 1); + }; + + bool Neg = false; + if (Pos < Len) { + char C; + if (!ReadChar(C)) + return false; + if (C == '-' && VSigned) { + Neg = true; + if (!Advance()) + return false; + } + } + + // Accumulate magnitude in VW+1 bits with overflow detection. + unsigned AW = VW + 1; + APInt Acc(AW, 0), BaseAP(AW, B); + bool AnyDigit = false, Ovf = false; + while (Pos < Len) { + char C; + if (!ReadChar(C)) + return false; + int D = DigitVal(C); + if (D < 0) + break; + AnyDigit = true; + bool O1 = false, O2 = false; + APInt T = Acc.umul_ov(BaseAP, O1); + T = T.uadd_ov(APInt(AW, (uint64_t)D), O2); + if (O1 || O2) + Ovf = true; + else + Acc = std::move(T); + if (!Advance()) + return false; + } + + auto WriteInt = [&](LValue &Dest, QualType Ty, const APInt &Val, + bool Unsigned) -> bool { + APValue V{APSInt(Val, Unsigned)}; + return handleAssignment(Info, E, Dest, Ty, V); + }; + unsigned EW = Info.Ctx.getIntWidth(EcTy); + bool EUnsigned = !EcTy->isSignedIntegerType(); + auto WriteEc = [&](unsigned Code) -> bool { + return WriteInt(EcPtr, EcTy, APInt(EW, Code), EUnsigned); + }; + + if (!AnyDigit) { + // invalid_argument: ptr == first, value untouched. + if (!WriteEc(1)) + return false; + Result = First; + return true; + } + + // Range check against the target's representable bounds. + APInt MaxMag = VSigned ? (Neg ? APInt::getOneBitSet(AW, VW - 1) + : APInt::getOneBitSet(AW, VW - 1) - 1) + : APInt::getLowBitsSet(AW, VW); + if (Ovf || Acc.ugt(MaxMag)) { + // result_out_of_range: ptr past consumed digits, value untouched. + if (!WriteEc(2)) + return false; + Result = Cur; + return true; + } + + APInt Stored = (Neg ? (-Acc) : Acc).trunc(VW); + if (!WriteInt(ValPtr, ValTy, Stored, !VSigned) || !WriteEc(0)) + return false; + Result = Cur; + return true; + } + default: return false; } diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 509ab4245d99a..9b2f83f6cbed0 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -5184,6 +5184,274 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, addInstToNewSourceAtom(I, nullptr); return RValue::get(Dest, *this); } + case Builtin::BI__builtin_to_chars: { + // (char *first, char *last, T value, int base) -> char *. Inline loop; no + // runtime symbol. Precondition base in [2,36] (UB otherwise, like the std). + Value *First = EmitScalarExpr(E->getArg(0)); + Value *Last = EmitScalarExpr(E->getArg(1)); + Value *Val = EmitScalarExpr(E->getArg(2)); + Value *Base = EmitScalarExpr(E->getArg(3)); + bool VSigned = E->getArg(2)->getType()->isSignedIntegerType(); + unsigned N = cast<llvm::IntegerType>(Val->getType())->getBitWidth(); + // Calc width must hold the magnitude and the base. + auto *CalcTy = llvm::IntegerType::get(getLLVMContext(), std::max(N, 8u)); + + Value *Neg = VSigned ? Builder.CreateICmpSLT( + Val, llvm::ConstantInt::get(Val->getType(), 0)) + : Builder.getFalse(); + // Unsigned negate yields the correct magnitude for the most-negative value. + Value *Mag = + VSigned ? Builder.CreateSelect(Neg, Builder.CreateNeg(Val), Val) : Val; + Mag = Builder.CreateZExtOrTrunc(Mag, CalcTy); + Value *BaseW = Builder.CreateIntCast(Base, CalcTy, /*isSigned=*/false); + + Address QAddr = CreateDefaultAlignTempAlloca(CalcTy, "tc.q"); + Address CntAddr = CreateDefaultAlignTempAlloca(Int64Ty, "tc.cnt"); + Builder.CreateStore(Mag, QAddr); + Builder.CreateStore(llvm::ConstantInt::get(Int64Ty, 0), CntAddr); + + BasicBlock *CountBB = createBasicBlock("tc.count"); + BasicBlock *CountDone = createBasicBlock("tc.count.done"); + Builder.CreateBr(CountBB); + EmitBlock(CountBB); + { + Value *Q = Builder.CreateLoad(QAddr); + Value *QN = Builder.CreateUDiv(Q, BaseW); + Builder.CreateStore(QN, QAddr); + Value *Cnt = Builder.CreateLoad(CntAddr); + Builder.CreateStore( + Builder.CreateAdd(Cnt, llvm::ConstantInt::get(Int64Ty, 1)), CntAddr); + Builder.CreateCondBr( + Builder.CreateICmpEQ(QN, llvm::ConstantInt::get(CalcTy, 0)), + CountDone, CountBB); + } + EmitBlock(CountDone); + Value *Nd = Builder.CreateLoad(CntAddr); + Value *NegZ = Builder.CreateZExt(Neg, Int64Ty); + Value *Len = Builder.CreateAdd(Nd, NegZ); + Value *Cap = Builder.CreatePtrDiff(Int8Ty, Last, First); + Cap = Builder.CreateSExtOrTrunc(Cap, Int64Ty); + Value *Fits = Builder.CreateICmpULE(Len, Cap); + + BasicBlock *WriteBB = createBasicBlock("tc.write"); + BasicBlock *WriteLoop = createBasicBlock("tc.write.loop"); + BasicBlock *WriteDone = createBasicBlock("tc.write.done"); + BasicBlock *TooSmall = createBasicBlock("tc.toosmall"); + BasicBlock *Exit = createBasicBlock("tc.exit"); + Builder.CreateCondBr(Fits, WriteBB, TooSmall); + + EmitBlock(WriteBB); + if (VSigned) { + BasicBlock *SignBB = createBasicBlock("tc.sign"); + BasicBlock *NoSign = createBasicBlock("tc.nosign"); + Builder.CreateCondBr(Neg, SignBB, NoSign); + EmitBlock(SignBB); + Builder.CreateAlignedStore(Builder.getInt8('-'), First, llvm::Align(1)); + Builder.CreateBr(NoSign); + EmitBlock(NoSign); + } + Value *WriteBase = Builder.CreateGEP(Int8Ty, First, NegZ); + Builder.CreateStore(Mag, QAddr); // reset for the write pass + Address PosAddr = CreateDefaultAlignTempAlloca(Int64Ty, "tc.pos"); + Builder.CreateStore( + Builder.CreateSub(Nd, llvm::ConstantInt::get(Int64Ty, 1)), PosAddr); + Builder.CreateBr(WriteLoop); + EmitBlock(WriteLoop); + { + Value *Q = Builder.CreateLoad(QAddr); + Value *D = Builder.CreateURem(Q, BaseW); + Value *QN = Builder.CreateUDiv(Q, BaseW); + Builder.CreateStore(QN, QAddr); + Value *DT = Builder.CreateZExtOrTrunc(D, Int8Ty); + Value *IsDig = Builder.CreateICmpULT(DT, Builder.getInt8(10)); + Value *Ch = Builder.CreateSelect( + IsDig, Builder.CreateAdd(DT, Builder.getInt8('0')), + Builder.CreateAdd(DT, Builder.getInt8('a' - 10))); + Value *Pos = Builder.CreateLoad(PosAddr); + Builder.CreateAlignedStore(Ch, Builder.CreateGEP(Int8Ty, WriteBase, Pos), + llvm::Align(1)); + Builder.CreateStore( + Builder.CreateSub(Pos, llvm::ConstantInt::get(Int64Ty, 1)), PosAddr); + Builder.CreateCondBr( + Builder.CreateICmpEQ(QN, llvm::ConstantInt::get(CalcTy, 0)), + WriteDone, WriteLoop); + } + EmitBlock(WriteDone); + Value *RetOk = Builder.CreateGEP(Int8Ty, First, Len); + Builder.CreateBr(Exit); + EmitBlock(TooSmall); + Builder.CreateBr(Exit); + EmitBlock(Exit); + PHINode *Ret = Builder.CreatePHI(First->getType(), 2); + Ret->addIncoming(RetOk, WriteDone); + Ret->addIncoming(llvm::Constant::getNullValue(First->getType()), TooSmall); + return RValue::get(Ret); + } + case Builtin::BI__builtin_from_chars: { + // (const char *first, const char *last, T *value, int base, int *ec) + // -> const char *. + Value *First = EmitScalarExpr(E->getArg(0)); + Value *Last = EmitScalarExpr(E->getArg(1)); + Value *ValPtr = EmitScalarExpr(E->getArg(2)); + Value *Base = EmitScalarExpr(E->getArg(3)); + Value *EcPtr = EmitScalarExpr(E->getArg(4)); + + QualType ValPointee = E->getArg(2)->getType()->getPointeeType(); + QualType EcPointee = E->getArg(4)->getType()->getPointeeType(); + bool VSigned = ValPointee->isSignedIntegerType(); + auto *ValTy = cast<llvm::IntegerType>(ConvertType(ValPointee)); + llvm::Type *EcTy = ConvertType(EcPointee); + unsigned N = ValTy->getBitWidth(); + auto *AccTy = llvm::IntegerType::get(getLLVMContext(), std::max(N + 1, 8u)); + llvm::Align ValAlign = + getContext().getTypeAlignInChars(ValPointee).getAsAlign(); + llvm::Align EcAlign = + getContext().getTypeAlignInChars(EcPointee).getAsAlign(); + Value *BaseA = Builder.CreateIntCast(Base, AccTy, /*isSigned=*/false); + + Address CurAddr = CreateDefaultAlignTempAlloca(First->getType(), "fc.cur"); + Address AccAddr = CreateDefaultAlignTempAlloca(AccTy, "fc.acc"); + Address OvfAddr = CreateDefaultAlignTempAlloca(Int8Ty, "fc.ovf"); + Address AnyAddr = CreateDefaultAlignTempAlloca(Int8Ty, "fc.any"); + Address NegAddr = CreateDefaultAlignTempAlloca(Int8Ty, "fc.neg"); + Builder.CreateStore(First, CurAddr); + Builder.CreateStore(llvm::ConstantInt::get(AccTy, 0), AccAddr); + Builder.CreateStore(Builder.getInt8(0), OvfAddr); + Builder.CreateStore(Builder.getInt8(0), AnyAddr); + Builder.CreateStore(Builder.getInt8(0), NegAddr); + + if (VSigned) { + // Optional leading '-' (no '+', per [charconv.from.chars]). + BasicBlock *SignChk = createBasicBlock("fc.signchk"); + BasicBlock *SignRead = createBasicBlock("fc.signread"); + BasicBlock *DoNeg = createBasicBlock("fc.doneg"); + BasicBlock *AfterSign = createBasicBlock("fc.aftersign"); + Builder.CreateBr(SignChk); + EmitBlock(SignChk); + Value *Cur0 = Builder.CreateLoad(CurAddr); + Builder.CreateCondBr(Builder.CreateICmpNE(Cur0, Last), SignRead, + AfterSign); + EmitBlock(SignRead); + Value *C0 = Builder.CreateAlignedLoad(Int8Ty, Cur0, llvm::Align(1)); + Builder.CreateCondBr(Builder.CreateICmpEQ(C0, Builder.getInt8('-')), + DoNeg, AfterSign); + EmitBlock(DoNeg); + Builder.CreateStore(Builder.getInt8(1), NegAddr); + Builder.CreateStore(Builder.CreateGEP(Int8Ty, Cur0, Builder.getInt64(1)), + CurAddr); + Builder.CreateBr(AfterSign); + EmitBlock(AfterSign); + } + + BasicBlock *Loop = createBasicBlock("fc.loop"); + BasicBlock *Body = createBasicBlock("fc.body"); + BasicBlock *Acc = createBasicBlock("fc.acc"); + BasicBlock *AfterLoop = createBasicBlock("fc.after"); + Builder.CreateBr(Loop); + EmitBlock(Loop); + Value *Cur = Builder.CreateLoad(CurAddr); + Builder.CreateCondBr(Builder.CreateICmpNE(Cur, Last), Body, AfterLoop); + + EmitBlock(Body); + Value *Ch = Builder.CreateAlignedLoad(Int8Ty, Cur, llvm::Align(1)); + auto InRange = [&](char Lo, char Hi) { + return Builder.CreateAnd(Builder.CreateICmpUGE(Ch, Builder.getInt8(Lo)), + Builder.CreateICmpULE(Ch, Builder.getInt8(Hi))); + }; + Value *IsDig = InRange('0', '9'); + Value *IsLow = InRange('a', 'z'); + Value *IsUp = InRange('A', 'Z'); + Value *DVal = Builder.CreateSelect( + IsDig, Builder.CreateSub(Ch, Builder.getInt8('0')), + Builder.CreateSelect( + IsLow, Builder.CreateSub(Ch, Builder.getInt8('a' - 10)), + Builder.CreateSelect( + IsUp, Builder.CreateSub(Ch, Builder.getInt8('A' - 10)), + Builder.getInt8(0)))); + Value *DW = Builder.CreateZExtOrTrunc(DVal, AccTy); + Value *IsChar = Builder.CreateOr(Builder.CreateOr(IsDig, IsLow), IsUp); + Value *Valid = Builder.CreateAnd(IsChar, Builder.CreateICmpULT(DW, BaseA)); + Builder.CreateCondBr(Valid, Acc, AfterLoop); + + EmitBlock(Acc); + Builder.CreateStore(Builder.getInt8(1), AnyAddr); + Value *AccV = Builder.CreateLoad(AccAddr); + Value *Mul = Builder.CreateBinaryIntrinsic( + llvm::Intrinsic::umul_with_overflow, AccV, BaseA); + Value *O1 = Builder.CreateExtractValue(Mul, 1); + Value *Sum = + Builder.CreateBinaryIntrinsic(llvm::Intrinsic::uadd_with_overflow, + Builder.CreateExtractValue(Mul, 0), DW); + Value *O2 = Builder.CreateExtractValue(Sum, 1); + Builder.CreateStore(Builder.CreateExtractValue(Sum, 0), AccAddr); + Value *OvfNow = Builder.CreateOr(O1, O2); + Value *OldOvf = + Builder.CreateICmpNE(Builder.CreateLoad(OvfAddr), Builder.getInt8(0)); + Builder.CreateStore( + Builder.CreateZExt(Builder.CreateOr(OvfNow, OldOvf), Int8Ty), OvfAddr); + Builder.CreateStore(Builder.CreateGEP(Int8Ty, Cur, Builder.getInt64(1)), + CurAddr); + Builder.CreateBr(Loop); + + EmitBlock(AfterLoop); + Value *CurEnd = Builder.CreateLoad(CurAddr); + Value *AnyB = + Builder.CreateICmpNE(Builder.CreateLoad(AnyAddr), Builder.getInt8(0)); + Value *NegB = + Builder.CreateICmpNE(Builder.CreateLoad(NegAddr), Builder.getInt8(0)); + Value *OvfB = + Builder.CreateICmpNE(Builder.CreateLoad(OvfAddr), Builder.getInt8(0)); + Value *AccFin = Builder.CreateLoad(AccAddr); + // Range bound: signed uses 2^(N-1)-1 (pos) / 2^(N-1) (neg); unsigned 2^N-1. + Value *MaxMag; + if (VSigned) { + Value *MaxPos = llvm::ConstantInt::get( + AccTy, llvm::APInt::getOneBitSet(AccTy->getBitWidth(), N - 1) - 1); + Value *MaxNeg = llvm::ConstantInt::get( + AccTy, llvm::APInt::getOneBitSet(AccTy->getBitWidth(), N - 1)); + MaxMag = Builder.CreateSelect(NegB, MaxNeg, MaxPos); + } else { + MaxMag = llvm::ConstantInt::get( + AccTy, llvm::APInt::getLowBitsSet(AccTy->getBitWidth(), N)); + } + Value *TooBig = + Builder.CreateOr(OvfB, Builder.CreateICmpUGT(AccFin, MaxMag)); + Value *AccTrunc = Builder.CreateTrunc(AccFin, ValTy); + Value *Final = + VSigned + ? Builder.CreateSelect(NegB, Builder.CreateNeg(AccTrunc), AccTrunc) + : AccTrunc; + + BasicBlock *Invalid = createBasicBlock("fc.invalid"); + BasicBlock *Check = createBasicBlock("fc.check"); + BasicBlock *Oor = createBasicBlock("fc.oor"); + BasicBlock *Ok = createBasicBlock("fc.ok"); + BasicBlock *Done = createBasicBlock("fc.done"); + Builder.CreateCondBr(AnyB, Check, Invalid); + + EmitBlock(Invalid); + Builder.CreateAlignedStore(llvm::ConstantInt::get(EcTy, 1), EcPtr, EcAlign); + Builder.CreateBr(Done); + + EmitBlock(Check); + Builder.CreateCondBr(TooBig, Oor, Ok); + + EmitBlock(Oor); + Builder.CreateAlignedStore(llvm::ConstantInt::get(EcTy, 2), EcPtr, EcAlign); + Builder.CreateBr(Done); + + EmitBlock(Ok); + Builder.CreateAlignedStore(Final, ValPtr, ValAlign); + Builder.CreateAlignedStore(llvm::ConstantInt::get(EcTy, 0), EcPtr, EcAlign); + Builder.CreateBr(Done); + + EmitBlock(Done); + PHINode *Ret = Builder.CreatePHI(First->getType(), 3); + Ret->addIncoming(First, Invalid); + Ret->addIncoming(CurEnd, Oor); + Ret->addIncoming(CurEnd, Ok); + return RValue::get(Ret); + } case Builtin::BI__builtin_wmemchr: { // The MSVC runtime library does not provide a definition of wmemchr, so we // need an inline implementation. diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index b8a3f48a32f24..499bd03dea30c 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -487,6 +487,136 @@ static bool BuiltinOverflow(Sema &S, CallExpr *TheCall, unsigned BuiltinID) { return false; } +// Shared checking for __builtin_to_chars / __builtin_from_chars buffer +// arguments: a pointer to 'char', optionally required to be non-const. +static bool checkCharconvCharPtr(Sema &S, CallExpr *TheCall, unsigned ArgIdx, + unsigned BuiltinID, bool RequireNonConst) { + ExprResult Arg = + S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(ArgIdx)); + if (Arg.isInvalid()) + return true; + TheCall->setArg(ArgIdx, Arg.get()); + + QualType Ty = Arg.get()->getType(); + const auto *PtrTy = Ty->getAs<PointerType>(); + if (!PtrTy || !PtrTy->getPointeeType()->isCharType() || + (RequireNonConst && PtrTy->getPointeeType().isConstQualified())) { + S.Diag(Arg.get()->getBeginLoc(), + diag::err_charconv_builtin_must_be_char_ptr) + << (ArgIdx + 1) << S.Context.BuiltinInfo.getQuotedName(BuiltinID) + << RequireNonConst << Ty << Arg.get()->getSourceRange(); + return true; + } + return false; +} + +// Shared base-argument checking: an integer, in [2, 36] when constant. +static bool checkCharconvBase(Sema &S, CallExpr *TheCall, unsigned ArgIdx, + unsigned BuiltinID) { + ExprResult Arg = + S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(ArgIdx)); + if (Arg.isInvalid()) + return true; + TheCall->setArg(ArgIdx, Arg.get()); + + if (!Arg.get()->getType()->isIntegerType()) { + S.Diag(Arg.get()->getBeginLoc(), diag::err_charconv_builtin_invalid_base) + << S.Context.BuiltinInfo.getQuotedName(BuiltinID) + << Arg.get()->getType() << Arg.get()->getSourceRange(); + return true; + } + if (std::optional<llvm::APSInt> Base = + Arg.get()->getIntegerConstantExpr(S.Context)) { + bool Bad = Base->isNegative() || Base->getActiveBits() > 6; + if (!Bad) { + uint64_t B = Base->getZExtValue(); + Bad = B < 2 || B > 36; + } + if (Bad) { + S.Diag(Arg.get()->getBeginLoc(), diag::err_charconv_builtin_invalid_base) + << S.Context.BuiltinInfo.getQuotedName(BuiltinID) + << toString(*Base, 10, Base->isSigned()) + << Arg.get()->getSourceRange(); + return true; + } + } + return false; +} + +// __builtin_to_chars(char *first, char *last, T value, int base) -> char *. +static bool BuiltinToChars(Sema &S, CallExpr *TheCall, unsigned BuiltinID) { + if (S.checkArgCount(TheCall, 4)) + return true; + if (checkCharconvCharPtr(S, TheCall, 0, BuiltinID, + /*RequireNonConst=*/true) || + checkCharconvCharPtr(S, TheCall, 1, BuiltinID, /*RequireNonConst=*/true)) + return true; + + ExprResult Value = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(2)); + if (Value.isInvalid()) + return true; + TheCall->setArg(2, Value.get()); + if (!Value.get()->getType()->isIntegerType()) { + S.Diag(Value.get()->getBeginLoc(), diag::err_charconv_builtin_must_be_int) + << S.Context.BuiltinInfo.getQuotedName(BuiltinID) + << Value.get()->getType() << Value.get()->getSourceRange(); + return true; + } + + if (checkCharconvBase(S, TheCall, 3, BuiltinID)) + return true; + + // Result is the past-the-end write pointer, of the buffer's type. + TheCall->setType(TheCall->getArg(0)->getType()); + return false; +} + +// __builtin_from_chars(const char *first, const char *last, T *value, +// int base, int *ec) -> const char *. +static bool BuiltinFromChars(Sema &S, CallExpr *TheCall, unsigned BuiltinID) { + if (S.checkArgCount(TheCall, 5)) + return true; + if (checkCharconvCharPtr(S, TheCall, 0, BuiltinID, + /*RequireNonConst=*/false) || + checkCharconvCharPtr(S, TheCall, 1, BuiltinID, /*RequireNonConst=*/false)) + return true; + + ExprResult Value = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(2)); + if (Value.isInvalid()) + return true; + TheCall->setArg(2, Value.get()); + QualType ValTy = Value.get()->getType(); + const auto *ValPtr = ValTy->getAs<PointerType>(); + if (!ValPtr || !ValPtr->getPointeeType()->isIntegerType() || + ValPtr->getPointeeType().isConstQualified()) { + S.Diag(Value.get()->getBeginLoc(), + diag::err_charconv_builtin_must_be_int_ptr) + << S.Context.BuiltinInfo.getQuotedName(BuiltinID) << ValTy + << Value.get()->getSourceRange(); + return true; + } + + if (checkCharconvBase(S, TheCall, 3, BuiltinID)) + return true; + + ExprResult Ec = S.DefaultFunctionArrayLvalueConversion(TheCall->getArg(4)); + if (Ec.isInvalid()) + return true; + TheCall->setArg(4, Ec.get()); + const auto *EcPtr = Ec.get()->getType()->getAs<PointerType>(); + if (!EcPtr || !EcPtr->getPointeeType()->isIntegerType() || + EcPtr->getPointeeType().isConstQualified()) { + S.Diag(Ec.get()->getBeginLoc(), diag::err_charconv_builtin_must_be_int_ptr) + << S.Context.BuiltinInfo.getQuotedName(BuiltinID) << Ec.get()->getType() + << Ec.get()->getSourceRange(); + return true; + } + + // Result is the past-the-consumed-input pointer, of the input's type. + TheCall->setType(TheCall->getArg(0)->getType()); + return false; +} + namespace { struct BuiltinDumpStructGenerator { Sema &S; @@ -3384,6 +3514,14 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, if (BuiltinOverflow(*this, TheCall, BuiltinID)) return ExprError(); break; + case Builtin::BI__builtin_to_chars: + if (BuiltinToChars(*this, TheCall, BuiltinID)) + return ExprError(); + break; + case Builtin::BI__builtin_from_chars: + if (BuiltinFromChars(*this, TheCall, BuiltinID)) + return ExprError(); + break; case Builtin::BI__builtin_operator_new: case Builtin::BI__builtin_operator_delete: { bool IsDelete = BuiltinID == Builtin::BI__builtin_operator_delete; diff --git a/clang/test/CodeGen/builtin-charconv.c b/clang/test/CodeGen/builtin-charconv.c new file mode 100644 index 0000000000000..09fb0b6bec3ce --- /dev/null +++ b/clang/test/CodeGen/builtin-charconv.c @@ -0,0 +1,21 @@ +// RUN: %clang_cc1 -triple x86_64-linux-gnu -O0 -emit-llvm %s -o - | FileCheck %s + +// POC runtime codegen: an inline loop, no external charconv runtime symbol. + +int gec; + +// CHECK-LABEL: define{{.*}} ptr @do_to_chars +char *do_to_chars(char *first, char *last, int v, int base) { + return __builtin_to_chars(first, last, v, base); +} +// CHECK: udiv +// CHECK: urem +// CHECK-NOT: call{{.*}}@{{.*}}chars + +// CHECK-LABEL: define{{.*}} ptr @do_from_chars +const char *do_from_chars(const char *first, const char *last, int *out, int base) { + return __builtin_from_chars(first, last, out, base, &gec); +} +// CHECK: @llvm.umul.with.overflow +// CHECK: @llvm.uadd.with.overflow +// CHECK-NOT: call{{.*}}@{{.*}}chars diff --git a/clang/test/Sema/builtin-charconv.c b/clang/test/Sema/builtin-charconv.c new file mode 100644 index 0000000000000..3370031d70009 --- /dev/null +++ b/clang/test/Sema/builtin-charconv.c @@ -0,0 +1,40 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s + +// POC diagnostics for __builtin_to_chars / __builtin_from_chars. + +void to_chars_checks(char *p, const char *cp, int v, int base) { + (void)__builtin_to_chars(p, p, v, 10); // ok + (void)__builtin_to_chars(p, p, v, base); // ok, runtime base + + __builtin_to_chars(p, p, v); // expected-error {{too few arguments}} + __builtin_to_chars(p, p, v, 10, p); // expected-error {{too many arguments}} + + // Buffer must be a pointer to non-const char. + (void)__builtin_to_chars(v, p, v, 10); // expected-error {{must be a pointer to non-const 'char'}} + (void)__builtin_to_chars(cp, p, v, 10); // expected-error {{must be a pointer to non-const 'char'}} + + // Value must be an integer. + (void)__builtin_to_chars(p, p, p, 10); // expected-error {{value argument to '__builtin_to_chars' must be an integer type}} + + // Constant base must be in [2, 36]. + (void)__builtin_to_chars(p, p, v, 1); // expected-error {{base argument to '__builtin_to_chars' must be between 2 and 36}} + (void)__builtin_to_chars(p, p, v, 37); // expected-error {{base argument to '__builtin_to_chars' must be between 2 and 36}} +} + +void from_chars_checks(const char *cp, char *p, int *out, const int *cout, int base) { + int ec; + (void)__builtin_from_chars(cp, cp, out, 10, &ec); // ok + (void)__builtin_from_chars(p, p, out, base, &ec); // ok, non-const buffer is fine + + __builtin_from_chars(cp, cp, out, 10); // expected-error {{too few arguments}} + + // Value must be a pointer to a non-const integer. + (void)__builtin_from_chars(cp, cp, cout, 10, &ec); // expected-error {{value argument to '__builtin_from_chars' must be a pointer to a non-const integer type}} + (void)__builtin_from_chars(cp, cp, base, 10, &ec); // expected-error {{value argument to '__builtin_from_chars' must be a pointer to a non-const integer type}} + + // ec must be a pointer to a non-const integer. + (void)__builtin_from_chars(cp, cp, out, 10, base); // expected-error {{must be a pointer to a non-const integer type}} + + // Constant base range. + (void)__builtin_from_chars(cp, cp, out, 0, &ec); // expected-error {{base argument to '__builtin_from_chars' must be between 2 and 36}} +} diff --git a/clang/test/SemaCXX/builtin-charconv-constexpr.cpp b/clang/test/SemaCXX/builtin-charconv-constexpr.cpp new file mode 100644 index 0000000000000..9437ee86d147e --- /dev/null +++ b/clang/test/SemaCXX/builtin-charconv-constexpr.cpp @@ -0,0 +1,115 @@ +// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s +// expected-no-diagnostics + +// POC scope: the conversion is implemented in the classic constant evaluator +// (ExprConstant.cpp). The experimental bytecode interpreter +// (-fexperimental-new-constant-interpreter) would need a parallel +// implementation in clang/lib/AST/ByteCode and is left as follow-up. + +// POC: __builtin_to_chars / __builtin_from_chars are usable in constant +// expressions, for integers of any width including _BitInt(N > 128). + +template <class T> constexpr bool rt(T v, int base) { + char buf[2100] = {}; + char *end = __builtin_to_chars(buf, buf + sizeof(buf), v, base); + if (!end) + return false; + T out = 0; + int ec = 0; + const char *p = __builtin_from_chars(buf, end, &out, base, &ec); + return ec == 0 && p == end && out == v; +} + +template <class T> constexpr bool rt_all_bases(T v) { + return rt(v, 2) && rt(v, 8) && rt(v, 10) && rt(v, 16) && rt(v, 36) && + rt(v, 3) && rt(v, 7); +} + +// Standard integer types, including edge values. +static_assert(rt_all_bases<int>(0)); +static_assert(rt_all_bases<int>(1)); +static_assert(rt_all_bases<int>(-1)); +static_assert(rt_all_bases<int>(2147483647)); +static_assert(rt_all_bases<int>(-2147483647 - 1)); // most-negative +static_assert(rt_all_bases<unsigned>(0u)); +static_assert(rt_all_bases<unsigned>(4294967295u)); +static_assert(rt_all_bases<long long>(-9223372036854775807LL - 1)); +static_assert(rt_all_bases<unsigned long long>(18446744073709551615ULL)); + +#ifdef __SIZEOF_INT128__ +static_assert(rt_all_bases<__int128>(0)); +static_assert(rt_all_bases<__int128>((__int128)-1)); +static_assert(rt_all_bases<unsigned __int128>(~(unsigned __int128)0)); +#endif + +// _BitInt wider than 128 bits: the motivating case. +using s256 = signed _BitInt(256); +using u256 = unsigned _BitInt(256); +using s1024 = signed _BitInt(1024); + +constexpr s256 mk256() { + s256 v = 1; + for (int i = 0; i < 150; ++i) // 3^150 < 2^255, stays in range + v = v * 3 + 1; + return v; +} + +static_assert(rt_all_bases<u256>(0)); +static_assert(rt_all_bases<u256>(~(u256)0)); // all ones +static_assert(rt_all_bases<s256>(mk256())); +static_assert(rt_all_bases<s256>(-mk256())); +// Most-negative via unsigned cast; a signed shift into the sign bit is UB. +static_assert(rt_all_bases<s256>((s256)(((u256)1) << 255))); +static_assert(rt(((s1024)1) << 1000, 10)); +static_assert(rt(((u256)1) << 200, 16)); + +// Buffer too small -> null return. +constexpr bool too_small() { + char buf[2] = {}; + return __builtin_to_chars(buf, buf + 2, 12345, 10) == nullptr; +} +static_assert(too_small()); + +// Exact-fit buffer succeeds. +constexpr bool exact_fit() { + char buf[3] = {}; + char *end = __builtin_to_chars(buf, buf + 3, -42, 10); + return end == buf + 3 && buf[0] == '-' && buf[1] == '4' && buf[2] == '2'; +} +static_assert(exact_fit()); + +// from_chars error classes and parsing edges. +constexpr int parse_ec(const char *s, unsigned long n, int base) { + long long out = 0; + int ec = 0; + __builtin_from_chars(s, s + n, &out, base, &ec); + return ec; +} +static_assert(parse_ec("", 0, 10) == 1); // invalid_argument +static_assert(parse_ec("xyz", 3, 10) == 1); // invalid_argument +static_assert(parse_ec("+5", 2, 10) == 1); // '+' not accepted +static_assert(parse_ec("123", 3, 10) == 0); // ok +static_assert(parse_ec("99999999999999999999999999999", 29, 10) == 2); // range + +// from_chars stops at the first non-digit; no base prefix is consumed. +constexpr bool partial() { + long long out = 0; + int ec = 0; + const char *s = "0x1f"; + const char *p = __builtin_from_chars(s, s + 4, &out, 16, &ec); + return ec == 0 && out == 0 && p == s + 1; // parsed only "0" +} +static_assert(partial()); + +// '8' is not a base-8 digit; case-insensitive letters in base 16. +static_assert(parse_ec("8", 1, 8) == 1); +constexpr bool hex_case() { + const char upper[] = "FF"; + const char lower[] = "ff"; + long long a = 0, b = 0; + int ea = 0, eb = 0; + __builtin_from_chars(upper, upper + 2, &a, 16, &ea); + __builtin_from_chars(lower, lower + 2, &b, 16, &eb); + return ea == 0 && eb == 0 && a == 255 && b == 255; +} +static_assert(hex_case()); diff --git a/clang/test/SemaCXX/builtin-charconv-steps.cpp b/clang/test/SemaCXX/builtin-charconv-steps.cpp new file mode 100644 index 0000000000000..b0e07731e52eb --- /dev/null +++ b/clang/test/SemaCXX/builtin-charconv-steps.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_cc1 -std=c++20 -fconstexpr-steps=100 -fsyntax-only -verify %s +// expected-no-diagnostics + +// The builtin does not spend constexpr steps proportional to the operand width. +// Parsing the 302-digit decimal of 2^1000 into a 1024-bit _BitInt under a +// 100-step budget succeeds; a hand-written Horner loop over 302 digits would +// exceed it. to_chars cannot be shown the same way, because its output buffer +// is a constexpr array whose bound is itself limited by -fconstexpr-steps. + +using u1024 = unsigned _BitInt(1024); + +constexpr u1024 parse(const char *s, int n) { + u1024 out = 0; + int ec = 0; + __builtin_from_chars(s, s + n, &out, 10, &ec); + return ec == 0 ? out : 0; +} + +static_assert( + parse("107150860718626732094842504906000181056140481170553360744375038837" + "035105112493612249319837881569585812759467291755314682518714528569" + "231404359845775746985748039345677748242309854210746050623711418779" + "541821530464749835819412673987675591655439460770629145711964776865" + "42167660429831652624386837205668069376", + 302) == ((u1024)1) << 1000); >From 31648ee017647ab6f7acf6241b4294936c0c6dfb Mon Sep 17 00:00:00 2001 From: Xavier Roche <[email protected]> Date: Thu, 18 Jun 2026 15:59:14 +0200 Subject: [PATCH 2/3] [Clang] POC: add independent-oracle anchors to charconv constexpr test The round-trip checks verify from_chars(to_chars(v)) == v, which a symmetric bug in both directions would survive, and bases 2/3/7/8/36 had no independent oracle. Add fixed expected strings for to_chars per base and fixed expected values for from_chars, so a digit-table or base-offset error is caught. The runtime engine matches the constexpr engine byte-for-byte (review INV-1), so these constexpr anchors also cover the runtime codegen path; clang/test does not execute compiled binaries, and a runnable runtime probe is kept in the project sandbox. Assisted-by: Claude (Anthropic) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --- .../SemaCXX/builtin-charconv-constexpr.cpp | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/clang/test/SemaCXX/builtin-charconv-constexpr.cpp b/clang/test/SemaCXX/builtin-charconv-constexpr.cpp index 9437ee86d147e..ad2703194d887 100644 --- a/clang/test/SemaCXX/builtin-charconv-constexpr.cpp +++ b/clang/test/SemaCXX/builtin-charconv-constexpr.cpp @@ -113,3 +113,41 @@ constexpr bool hex_case() { return ea == 0 && eb == 0 && a == 255 && b == 255; } static_assert(hex_case()); + +// Independent-oracle anchors. The round-trip checks above pass even if a +// symmetric bug hits both directions; these compare to_chars output and +// from_chars values against fixed expected results, per base. INV-1 of the +// review establishes the runtime engine matches the constexpr engine +// byte-for-byte, so these constexpr anchors cover the runtime path too. +constexpr bool to_eq(auto v, int base, const char *expect) { + char buf[300] = {}; + char *e = __builtin_to_chars(buf, buf + sizeof(buf), v, base); + if (!e) + return false; + const char *p = buf; + for (; *expect; ++p, ++expect) + if (p == e || *p != *expect) + return false; + return p == e; +} +static_assert(to_eq(5u, 2, "101")); +static_assert(to_eq(8u, 8, "10")); +static_assert(to_eq(255u, 16, "ff")); +static_assert(to_eq(35u, 36, "z")); +static_assert(to_eq(1000000u, 10, "1000000")); +static_assert(to_eq(-255, 16, "-ff")); +static_assert(to_eq((s256)(((u256)1) << 200), 10, + "16069380442589902755419620923411626025222029937827928353" + "01376")); + +constexpr long long parse_val(const char *s, unsigned n, int base) { + long long out = -999; + int ec = 0; + __builtin_from_chars(s, s + n, &out, base, &ec); + return out; +} +static_assert(parse_val("101", 3, 2) == 5); +static_assert(parse_val("777", 3, 8) == 511); +static_assert(parse_val("7f", 2, 16) == 127); +static_assert(parse_val("z", 1, 36) == 35); +static_assert(parse_val("-123", 4, 10) == -123); >From dca4f629c6d7e04be6c90a57951bc19754f688e0 Mon Sep 17 00:00:00 2001 From: Xavier Roche <[email protected]> Date: Thu, 18 Jun 2026 17:02:14 +0200 Subject: [PATCH 3/3] [Clang][POC] Pin x86_64 triple in the wide-_BitInt charconv tests The two tests use _BitInt(256) and _BitInt(1024) without a target triple, so they pick up the host target and fail on AArch64, whose default __BITINT_MAXWIDTH__ is 128. Pin -triple x86_64-linux-gnu so the tests do not depend on the host. The builtin implementation is target-independent; only the tests assumed a large maximum width. Assisted-by: Claude (Anthropic) Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]> --- clang/test/SemaCXX/builtin-charconv-constexpr.cpp | 5 ++++- clang/test/SemaCXX/builtin-charconv-steps.cpp | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/clang/test/SemaCXX/builtin-charconv-constexpr.cpp b/clang/test/SemaCXX/builtin-charconv-constexpr.cpp index ad2703194d887..ff3ae325fae53 100644 --- a/clang/test/SemaCXX/builtin-charconv-constexpr.cpp +++ b/clang/test/SemaCXX/builtin-charconv-constexpr.cpp @@ -1,6 +1,9 @@ -// RUN: %clang_cc1 -std=c++20 -fsyntax-only -verify %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fsyntax-only -verify %s // expected-no-diagnostics +// A wide _BitInt needs a target with a large __BITINT_MAXWIDTH__; pin x86_64 so +// the test does not depend on the host target (e.g. AArch64 caps it at 128). + // POC scope: the conversion is implemented in the classic constant evaluator // (ExprConstant.cpp). The experimental bytecode interpreter // (-fexperimental-new-constant-interpreter) would need a parallel diff --git a/clang/test/SemaCXX/builtin-charconv-steps.cpp b/clang/test/SemaCXX/builtin-charconv-steps.cpp index b0e07731e52eb..0d56442f0bbaa 100644 --- a/clang/test/SemaCXX/builtin-charconv-steps.cpp +++ b/clang/test/SemaCXX/builtin-charconv-steps.cpp @@ -1,6 +1,9 @@ -// RUN: %clang_cc1 -std=c++20 -fconstexpr-steps=100 -fsyntax-only -verify %s +// RUN: %clang_cc1 -triple x86_64-linux-gnu -std=c++20 -fconstexpr-steps=100 -fsyntax-only -verify %s // expected-no-diagnostics +// Pin x86_64 so the wide _BitInt does not depend on the host target's +// __BITINT_MAXWIDTH__ (e.g. AArch64 caps it at 128). + // The builtin does not spend constexpr steps proportional to the operand width. // Parsing the 302-digit decimal of 2^1000 into a 1024-bit _BitInt under a // 100-step budget succeeds; a hand-written Horner loop over 302 digits would _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
