https://github.com/dzbarsky created https://github.com/llvm/llvm-project/pull/202633
CheckShift is instantiated for each primitive left-hand and right-hand type and for both shift directions. Its four diagnostic paths are cold, but each instantiation currently emits the diagnostic construction and undefined behavior handling. Move those paths to the non-template, noinline DiagnoseShiftFailure function. Valid shifts retain the existing checks and do not call the outlined function. Invalid shifts preserve the same diagnostic arguments and call noteUndefinedBehavior exactly once. On arm64 macOS with a Release build of standalone Clang, this changes: linked binary: 222,173,192 -> 221,924,960 bytes (-248,232) stripped binary: 128,407,464 -> 128,291,224 bytes (-116,240) __TEXT,__text: 98,369,916 -> 98,255,864 bytes (-114,052) A 50,000-expression valid-shift benchmark covered primitive integers, __int128, and _BitInt. Across 100 alternating process-CPU measurements, user CPU changed from 80.460 to 80.630 seconds total (+0.21%); the paired median was unchanged and the paired 95% interval was -0.87% to +1.29%. Generated objects were byte-identical. Validated all RUN modes from clang/test/AST/ByteCode/shifts.cpp and intap.cpp under both constant interpreters, including C++11, C++17, C++20, and the ARM target variants. Baseline and candidate diagnostics were byte-identical. Work towards #202616 >From b8245dada7a6f37f09c39f2c974628582b2e8b05 Mon Sep 17 00:00:00 2001 From: David Zbarsky <[email protected]> Date: Tue, 9 Jun 2026 02:21:56 -0400 Subject: [PATCH] [clang][AST] Outline constant-interpreter shift diagnostics CheckShift is instantiated for each primitive left-hand and right-hand type and for both shift directions. Its four diagnostic paths are cold, but each instantiation currently emits the diagnostic construction and undefined behavior handling. Move those paths to the non-template, noinline DiagnoseShiftFailure function. Valid shifts retain the existing checks and do not call the outlined function. Invalid shifts preserve the same diagnostic arguments and call noteUndefinedBehavior exactly once. On arm64 macOS with a Release build of standalone Clang, this changes: linked binary: 222,173,192 -> 221,924,960 bytes (-248,232) stripped binary: 128,407,464 -> 128,291,224 bytes (-116,240) __TEXT,__text: 98,369,916 -> 98,255,864 bytes (-114,052) A 50,000-expression valid-shift benchmark covered primitive integers, __int128, and _BitInt. Across 100 alternating process-CPU measurements, user CPU changed from 80.460 to 80.630 seconds total (+0.21%); the paired median was unchanged and the paired 95% interval was -0.87% to +1.29%. Generated objects were byte-identical. Validated all RUN modes from clang/test/AST/ByteCode/shifts.cpp and intap.cpp under both constant interpreters, including C++11, C++17, C++20, and the ARM target variants. Baseline and candidate diagnostics were byte-identical. --- clang/lib/AST/ByteCode/Interp.cpp | 27 ++++++++++++++++++++++++ clang/lib/AST/ByteCode/Interp.h | 35 ++++++++++++++++++------------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index f6cac7aeb9fb5..27e86b986aee0 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -225,6 +225,33 @@ namespace interp { PRESERVE_NONE static bool BCP(InterpState &S, CodePtr &RealPC, int32_t Offset, PrimType PT); +bool DiagnoseShiftFailure(InterpState &S, CodePtr OpPC, ShiftFailure Failure, + const APSInt *Value, unsigned Bits) { + switch (Failure) { + case ShiftFailure::NegativeCount: + assert(Value); + S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_negative_shift) + << *Value; + break; + case ShiftFailure::TooLarge: { + assert(Value); + const Expr *E = S.Current->getExpr(OpPC); + S.CCEDiag(E, diag::note_constexpr_large_shift) + << *Value << E->getType() << Bits; + break; + } + case ShiftFailure::NegativeLeftOperand: + assert(Value); + S.CCEDiag(S.Current->getExpr(OpPC), diag::note_constexpr_lshift_of_negative) + << *Value; + break; + case ShiftFailure::DiscardsBits: + S.CCEDiag(S.Current->getExpr(OpPC), diag::note_constexpr_lshift_discards); + break; + } + return S.noteUndefinedBehavior(); +} + static void popArg(InterpState &S, const Expr *Arg) { PrimType Ty = S.getContext().classify(Arg).value_or(PT_Ptr); TYPE_SWITCH(Ty, S.Stk.discard<T>()); diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 4d72204f51db9..a69aa75738844 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -35,6 +35,7 @@ #include "llvm/ADT/APFloat.h" #include "llvm/ADT/APSInt.h" #include "llvm/ADT/ScopeExit.h" +#include "llvm/Support/Compiler.h" #include <type_traits> // preserve_none causes problems when asan is enabled on both AArch64 and other @@ -147,25 +148,33 @@ bool CheckDynamicCast(InterpState &S, CodePtr OpPC); enum class ShiftDir { Left, Right }; +enum class ShiftFailure { + NegativeCount, + TooLarge, + NegativeLeftOperand, + DiscardsBits, +}; + +LLVM_ATTRIBUTE_NOINLINE bool DiagnoseShiftFailure(InterpState &S, CodePtr OpPC, + ShiftFailure Failure, + const APSInt *Value = nullptr, + unsigned Bits = 0); + /// Checks if the shift operation is legal. template <ShiftDir Dir, typename LT, typename RT> bool CheckShift(InterpState &S, CodePtr OpPC, const LT &LHS, const RT &RHS, unsigned Bits) { if (RHS.isNegative()) { - const SourceInfo &Loc = S.Current->getSource(OpPC); - S.CCEDiag(Loc, diag::note_constexpr_negative_shift) << RHS.toAPSInt(); - if (!S.noteUndefinedBehavior()) + const APSInt Value = RHS.toAPSInt(); + if (!DiagnoseShiftFailure(S, OpPC, ShiftFailure::NegativeCount, &Value)) return false; } // C++11 [expr.shift]p1: Shift width must be less than the bit width of // the shifted type. if (Bits > 1 && RHS >= Bits) { - const Expr *E = S.Current->getExpr(OpPC); - const APSInt Val = RHS.toAPSInt(); - QualType Ty = E->getType(); - S.CCEDiag(E, diag::note_constexpr_large_shift) << Val << Ty << Bits; - if (!S.noteUndefinedBehavior()) + const APSInt Value = RHS.toAPSInt(); + if (!DiagnoseShiftFailure(S, OpPC, ShiftFailure::TooLarge, &Value, Bits)) return false; } @@ -174,15 +183,13 @@ bool CheckShift(InterpState &S, CodePtr OpPC, const LT &LHS, const RT &RHS, // C++11 [expr.shift]p2: A signed left shift must have a non-negative // operand, and must not overflow the corresponding unsigned type. if (LHS.isNegative()) { - const Expr *E = S.Current->getExpr(OpPC); - S.CCEDiag(E, diag::note_constexpr_lshift_of_negative) << LHS.toAPSInt(); - if (!S.noteUndefinedBehavior()) + const APSInt Value = LHS.toAPSInt(); + if (!DiagnoseShiftFailure(S, OpPC, ShiftFailure::NegativeLeftOperand, + &Value)) return false; } else if (LHS.toUnsigned().countLeadingZeros() < static_cast<unsigned>(RHS)) { - const Expr *E = S.Current->getExpr(OpPC); - S.CCEDiag(E, diag::note_constexpr_lshift_discards); - if (!S.noteUndefinedBehavior()) + if (!DiagnoseShiftFailure(S, OpPC, ShiftFailure::DiscardsBits)) return false; } } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
