[clang] [clang] -fstrict-enums optimization for by-value parameters (PR #154807)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Aug 26 07:19:44 PDT 2025
https://github.com/keinflue updated https://github.com/llvm/llvm-project/pull/154807
>From 35308dce96c6901a088422ff85609789064d40e5 Mon Sep 17 00:00:00 2001
From: keinflue <keinflue at posteo.de>
Date: Thu, 21 Aug 2025 18:32:21 +0200
Subject: [PATCH] [clang] -fstrict-enums optimization for by-value parameters
This adds a range attribute to function parameters to enable
optimization with regards to restricted value ranges of
enumerations without fixed underlying type in C++ with the
-fstrict-enums option. Loads for such enumerations were already
annotated previously but do not have the desired effect for
by-value parameters.
As a side effect this also applies these range attributes to
types with boolean representation which are not represented as
i1 in parameters (if these exist).
The optimization is disabled with -fsanitize=bool and
-fsanitize=enum to avoid removal of their checks.
This also slighlty refactors the getRangeForType to be reusable
for this purpose.
Fixes #154022
---
clang/lib/CodeGen/CGCall.cpp | 11 ++
clang/lib/CodeGen/CGExpr.cpp | 68 +++++--
clang/lib/CodeGen/CodeGenFunction.h | 5 +
clang/test/CodeGenCXX/gh154022.cpp | 288 ++++++++++++++++++++++++++++
4 files changed, 354 insertions(+), 18 deletions(-)
create mode 100644 clang/test/CodeGenCXX/gh154022.cpp
diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp
index b959982809911..dfba41569fa28 100644
--- a/clang/lib/CodeGen/CGCall.cpp
+++ b/clang/lib/CodeGen/CGCall.cpp
@@ -37,6 +37,7 @@
#include "llvm/IR/AttributeMask.h"
#include "llvm/IR/Attributes.h"
#include "llvm/IR/CallingConv.h"
+#include "llvm/IR/ConstantRange.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/InlineAsm.h"
#include "llvm/IR/IntrinsicInst.h"
@@ -3209,6 +3210,16 @@ void CodeGenFunction::EmitFunctionProlog(const CGFunctionInfo &FI,
auto AI = Fn->getArg(FirstIRArg);
llvm::Type *LTy = ConvertType(Arg->getType());
+ bool HasBoolCheck = SanOpts.has(SanitizerKind::Bool);
+ bool HasEnumCheck = SanOpts.has(SanitizerKind::Enum);
+ if (!HasBoolCheck && !HasEnumCheck && LTy->isIntegerTy()) {
+ const std::optional<llvm::ConstantRange> Range =
+ getRangeForType(Arg->getType(), LTy->getIntegerBitWidth());
+ if (Range && !Range->isFullSet())
+ AI->addAttr(llvm::Attribute::get(getLLVMContext(),
+ llvm::Attribute::Range, *Range));
+ }
+
// Prepare parameter attributes. So far, only attributes for pointer
// parameters are prepared. See
// http://llvm.org/docs/LangRef.html#paramattrs.
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index d5df6dd3e303c..418120c596ed1 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -37,6 +37,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringExtras.h"
+#include "llvm/IR/ConstantRange.h"
#include "llvm/IR/DataLayout.h"
#include "llvm/IR/Intrinsics.h"
#include "llvm/IR/LLVMContext.h"
@@ -52,6 +53,7 @@
#include <numeric>
#include <optional>
#include <string>
+#include <utility>
using namespace clang;
using namespace CodeGen;
@@ -1917,34 +1919,59 @@ llvm::Value *CodeGenFunction::EmitLoadOfScalar(LValue lvalue,
lvalue.getTBAAInfo(), lvalue.isNontemporal());
}
-static bool getRangeForType(CodeGenFunction &CGF, QualType Ty,
- llvm::APInt &Min, llvm::APInt &End,
- bool StrictEnums, bool IsBool) {
+std::optional<llvm::ConstantRange>
+CodeGenFunction::getRangeForType(QualType Ty, unsigned BitWidth,
+ bool ForceStrictEnums,
+ bool AssumeBooleanRepresentation) {
+ assert(BitWidth >= 1 && "Zero bits value range not meaningful.");
+
+ if (AssumeBooleanRepresentation ||
+ (Ty->hasBooleanRepresentation() && !Ty->isVectorType())) {
+ if (BitWidth > 1)
+ return llvm::ConstantRange(llvm::APInt(BitWidth, 0),
+ llvm::APInt(BitWidth, 2));
+ else
+ return llvm::ConstantRange(BitWidth, /*isFullSet=*/true);
+ }
+
const EnumType *ET = Ty->getAs<EnumType>();
const EnumDecl *ED =
ET ? ET->getOriginalDecl()->getDefinitionOrSelf() : nullptr;
+ bool StrictEnums = ForceStrictEnums || CGM.getCodeGenOpts().StrictEnums;
bool IsRegularCPlusPlusEnum =
- CGF.getLangOpts().CPlusPlus && StrictEnums && ET && !ED->isFixed();
- if (!IsBool && !IsRegularCPlusPlusEnum)
- return false;
-
- if (IsBool) {
- Min = llvm::APInt(CGF.getContext().getTypeSize(Ty), 0);
- End = llvm::APInt(CGF.getContext().getTypeSize(Ty), 2);
- } else {
+ getLangOpts().CPlusPlus && StrictEnums && ET && !ED->isFixed();
+ if (IsRegularCPlusPlusEnum) {
+ llvm::APInt Min, End;
ED->getValueRange(End, Min);
+ // getValueRange may produce [0, 0] or [2**(w-1),2**(w-1)] to indicate a
+ // full range
+ auto Range = Min == End
+ ? llvm::ConstantRange(BitWidth, /*isFullSet=*/true)
+ : llvm::ConstantRange(std::move(Min), std::move(End));
+ assert(Range.getActiveBits() <= BitWidth &&
+ "Representing type with fewer bits than necessary for values?");
+ if (Ty->isSignedIntegerOrEnumerationType())
+ return Range.sextOrTrunc(BitWidth);
+ else {
+ assert(Ty->isUnsignedIntegerOrEnumerationType());
+ return Range.zextOrTrunc(BitWidth);
+ }
}
- return true;
+ return std::nullopt;
}
llvm::MDNode *CodeGenFunction::getRangeForLoadFromType(QualType Ty) {
- llvm::APInt Min, End;
- if (!getRangeForType(*this, Ty, Min, End, CGM.getCodeGenOpts().StrictEnums,
- Ty->hasBooleanRepresentation() && !Ty->isVectorType()))
+ llvm::Type *LTy = convertTypeForLoadStore(Ty);
+ if (!LTy->isIntegerTy())
+ return nullptr;
+
+ const std::optional<llvm::ConstantRange> Range =
+ getRangeForType(Ty, LTy->getIntegerBitWidth());
+ if (!Range || Range->isFullSet() || Range->isEmptySet())
return nullptr;
llvm::MDBuilder MDHelper(getLLVMContext());
- return MDHelper.createRange(Min, End);
+ return MDHelper.createRange(Range->getLower(), Range->getUpper());
}
void CodeGenFunction::maybeAttachRangeForLoad(llvm::LoadInst *Load, QualType Ty,
@@ -1986,10 +2013,15 @@ bool CodeGenFunction::EmitScalarRangeCheck(llvm::Value *Value, QualType Ty,
getContext().isTypeIgnoredBySanitizer(SanitizerKind::Enum, Ty))
return false;
- llvm::APInt Min, End;
- if (!getRangeForType(*this, Ty, Min, End, /*StrictEnums=*/true, IsBool))
+ const std::optional<llvm::ConstantRange> Range = getRangeForType(
+ Ty, getContext().getTypeSize(Ty), /*ForceStrictEnums=*/true,
+ /*AssumeBooleanRepresentation*/ IsBool);
+ if (!Range || Range->isFullSet())
return true;
+ llvm::APInt Min = Range->getLower();
+ llvm::APInt End = Range->getUpper();
+
SanitizerKind::SanitizerOrdinal Kind =
NeedsEnumCheck ? SanitizerKind::SO_Enum : SanitizerKind::SO_Bool;
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index ad318f289ee83..5ae485e9fd905 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -39,6 +39,7 @@
#include "llvm/ADT/MapVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Frontend/OpenMP/OMPIRBuilder.h"
+#include "llvm/IR/ConstantRange.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/ValueHandle.h"
#include "llvm/Support/Debug.h"
@@ -5530,6 +5531,10 @@ class CodeGenFunction : public CodeGenTypeCache {
llvm::Value *FormAArch64ResolverCondition(const FMVResolverOption &RO);
llvm::Value *EmitAArch64CpuSupports(const CallExpr *E);
llvm::Value *EmitAArch64CpuSupports(ArrayRef<StringRef> FeatureStrs);
+
+ std::optional<llvm::ConstantRange>
+ getRangeForType(QualType Ty, unsigned BitWidth, bool ForceStrictEnums = false,
+ bool AssumeBooleanRepresentation = false);
};
inline DominatingLLVMValue::saved_type
diff --git a/clang/test/CodeGenCXX/gh154022.cpp b/clang/test/CodeGenCXX/gh154022.cpp
new file mode 100644
index 0000000000000..6a2815afc351e
--- /dev/null
+++ b/clang/test/CodeGenCXX/gh154022.cpp
@@ -0,0 +1,288 @@
+// RUN: %clang_cc1 %s -triple i386-unknown-unknown -emit-llvm -O1 -fstrict-enums -std=c++11 -o - | FileCheck %s
+// RUN: %clang_cc1 %s -triple i386-unknown-unknown -emit-llvm -O3 -std=c++11 -o - | FileCheck --check-prefixes=NO-STRICT-ENUMS-C,NO-STRICT-ENUMS %s
+// RUN: %clang_cc1 -x c %s -triple i386-unknown-unknown -emit-llvm -O1 -std=c23 -fstrict-enums -o - | FileCheck --check-prefix=NO-STRICT-ENUMS-C %s
+
+#ifdef __cplusplus
+extern "C" {
+#endif // __cplusplus
+
+#ifdef __cplusplus
+enum e1 { };
+bool g1a(enum e1 x) {
+ return (unsigned int)x > 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g1a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-LABEL: define{{.*}} i1 @g1a
+// NO-STRICT-ENUMS: ret i1 %cmp
+bool g1b(enum e1 x) {
+ return (unsigned int)x >= 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g1b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-LABEL: define{{.*}} i1 @g1b
+// NO-STRICT-ENUMS: ret i1 %cmp
+#endif // __cplusplus
+
+enum e2 { e2_a };
+bool g2a(enum e2 x) {
+ return (unsigned int)x > 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g2a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g2a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g2b(enum e2 x) {
+ return (unsigned int)x >= 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g2b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g2b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e3 { e3_a = 1 };
+bool g3a(enum e3 x) {
+ return (unsigned int)x > 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g3a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g3a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g3b(enum e3 x) {
+ return (unsigned int)x >= 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g3b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g3b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e4 { e4_a = -1 };
+bool g4a(enum e4 x) {
+ return (int)x > 0;
+}
+// CHECK-LABEL: define{{.*}} i1 @g4a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g4a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g4b(enum e4 x) {
+ return (int)x >= 0;
+}
+// CHECK-LABEL: define{{.*}} i1 @g4b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g4b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g4c(enum e4 x) {
+ return (int)x < -1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g4c
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g4c
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g4d(enum e4 x) {
+ return (int)x <= -1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g4d
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g4d
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e5 { e5_a = 2 };
+bool g5a(enum e5 x) {
+ return (unsigned int)x > 3;
+}
+// CHECK-LABEL: define{{.*}} i1 @g5a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g5a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g5b(enum e5 x) {
+ return (unsigned int)x >= 3;
+}
+// CHECK-LABEL: define{{.*}} i1 @g5b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g5b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e6 { e6_a = -2 };
+bool g6a(enum e6 x) {
+ return (int)x > 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g6a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g6a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g6b(enum e6 x) {
+ return (int)x >= 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g6b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g6b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g6c(enum e6 x) {
+ return (int)x < -2;
+}
+// CHECK-LABEL: define{{.*}} i1 @g6c
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g6c
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g6d(enum e6 x) {
+ return (int)x <= -2;
+}
+// CHECK-LABEL: define{{.*}} i1 @g6d
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g6d
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e7 { e7_a = -1, e7_b = 2 };
+bool g7a(enum e7 x) {
+ return (int)x > 3;
+}
+// CHECK-LABEL: define{{.*}} i1 @g7a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g7a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g7b(enum e7 x) {
+ return (int)x >= 3;
+}
+// CHECK-LABEL: define{{.*}} i1 @g7b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g7b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g7c(enum e7 x) {
+ return (int)x < -4;
+}
+// CHECK-LABEL: define{{.*}} i1 @g7c
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g7c
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g7d(enum e7 x) {
+ return (int)x <= -4;
+}
+// CHECK-LABEL: define{{.*}} i1 @g7d
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g7d
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e8 { e8_a = (unsigned int)-1 };
+bool g8b(enum e8 x) {
+ return (unsigned int)x >= (unsigned int)-1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g8b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g8b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e9 { e9_a = (unsigned int)-2 };
+bool g9b(enum e9 x) {
+ return (unsigned int)x >= (unsigned int)-1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g9b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g9b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e10 { e10_a = (int)(((unsigned int)-1) >> 1) };
+bool g10a(enum e10 x) {
+ return (unsigned int)x > (int)(((unsigned int)-1) >> 1);
+}
+// CHECK-LABEL: define{{.*}} i1 @g10a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g10a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g10b(enum e10 x) {
+ return (unsigned int)x >= (int)(((unsigned int)-1) >> 1);
+}
+// CHECK-LABEL: define{{.*}} i1 @g10b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g10b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g10d(enum e10 x) {
+ return (unsigned int)x <= 0;
+}
+// CHECK-LABEL: define{{.*}} i1 @g10d
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g10d
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e11 { e11_a = -(int)(((unsigned int)-1) >> 1) };
+bool g11b(enum e11 x) {
+ return (int)x >= (int)(((unsigned int)-1) >> 1);
+}
+// CHECK-LABEL: define{{.*}} i1 @g11b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g11b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g11d(enum e11 x) {
+ return (int)x <= -(int)(((unsigned int)-1) >> 1) - 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g11d
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g11d
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e12 { e12_a = -(long long)(((unsigned long long)-1) >> 2) };
+bool g12a(enum e12 x) {
+ return (long long)x > (long long)(((unsigned long long)-1) >> 2);
+}
+// CHECK-LABEL: define{{.*}} i1 @g12a
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g12a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g12b(enum e12 x) {
+ return (long long)x >= (long long)(((unsigned long long)-1) >> 2);
+}
+// CHECK-LABEL: define{{.*}} i1 @g12b
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g12b
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g12c(enum e12 x) {
+ return (long long)x < -(long long)(((unsigned long long)-1) >> 2) - 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g12c
+// CHECK: ret i1 false
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g12c
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g12d(enum e12 x) {
+ return (long long)x <= -(long long)(((unsigned long long)-1) >> 2) - 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g12d
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g12d
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+enum e13 : long long { e13_a = -(long long)(((unsigned long long)-1) >> 2) };
+bool g13a(enum e13 x) {
+ return (long long)x > (long long)(((unsigned long long)-1) >> 2);
+}
+// CHECK-LABEL: define{{.*}} i1 @g13a
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g13a
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+bool g13c(enum e13 x) {
+ return (long long)x < -(long long)(((unsigned long long)-1) >> 2) - 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g13c
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-C-LABEL: define{{.*}} i1 @g13c
+// NO-STRICT-ENUMS-C: ret i1 %cmp
+
+#ifdef __cplusplus
+enum class e14 { e14_a = -(int)(((unsigned int)-1) >> 2) };
+bool g14a(enum e14 x) {
+ return (int)x > (int)(((unsigned int)-1) >> 2);
+}
+// CHECK-LABEL: define{{.*}} i1 @g14a
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-LABEL: define{{.*}} i1 @g14a
+// NO-STRICT-ENUMS: ret i1 %cmp
+bool g14c(enum e14 x) {
+ return (int)x < -(int)(((unsigned int)-1) >> 2) - 1;
+}
+// CHECK-LABEL: define{{.*}} i1 @g14c
+// CHECK: ret i1 %cmp
+// NO-STRICT-ENUMS-LABEL: define{{.*}} i1 @g14c
+// NO-STRICT-ENUMS: ret i1 %cmp
+#endif // __cplusplus
+
+#ifdef __cplusplus
+}
+#endif // __cplusplus
More information about the cfe-commits
mailing list