[clang] [clang] add array out-of-bounds access constraints using llvm.assume (PR #159046)
Sebastian Pop via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 1 21:26:39 PDT 2026
https://github.com/sebpop updated https://github.com/llvm/llvm-project/pull/159046
>From 860e601973a4ee4dae0cd743714c862b57aec5b2 Mon Sep 17 00:00:00 2001
From: Sebastian Pop <spop at nvidia.com>
Date: Tue, 16 Sep 2025 06:23:44 -0500
Subject: [PATCH] [clang] add array out-of-bounds access constraints using
llvm.assume
Following C and C++ standards, generate llvm.assume statements for array
subscript bounds to provide optimization hints.
For this code:
```
int arr[10];
int example(int i) {
return arr[i];
}
```
clang now generates an `assume(i < 10)`:
```
define i32 @example(i32 noundef %i) local_unnamed_addr #0 {
entry:
%idxprom = zext nneg i32 %i to i64
%bounds.constraint = icmp ult i32 %i, 10
tail call void @llvm.assume(i1 %bounds.constraint)
%arrayidx = getelementptr inbounds nuw i32, ptr @arr, i64 %idxprom
%0 = load i32, ptr %arrayidx, align 4, !tbaa !2
ret i32 %0
}
```
---
clang/include/clang/Basic/CodeGenOptions.def | 1 +
clang/include/clang/Options/Options.td | 5 +
clang/lib/CodeGen/CGExpr.cpp | 177 +++++++++++++++++-
clang/lib/CodeGen/CGExprScalar.cpp | 5 +-
clang/lib/CodeGen/CodeGenFunction.h | 9 +
.../CodeGen/array-bounds-constraints-safety.c | 106 +++++++++++
.../array-bounds-constraints-strict-flex.c | 35 ++++
clang/test/CodeGen/array-bounds-constraints.c | 44 +++++
8 files changed, 374 insertions(+), 8 deletions(-)
create mode 100644 clang/test/CodeGen/array-bounds-constraints-safety.c
create mode 100644 clang/test/CodeGen/array-bounds-constraints-strict-flex.c
create mode 100644 clang/test/CodeGen/array-bounds-constraints.c
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 49374efbeb28b..2f3d4870cac18 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -39,6 +39,7 @@ CODEGENOPT(ImplicitMapSyms, 1, 0, Benign) ///< -Wa,-mmapsyms=implicit
CODEGENOPT(AsmVerbose , 1, 0, Benign) ///< -dA, -fverbose-asm.
CODEGENOPT(PreserveAsmComments, 1, 1, Benign) ///< -dA, -fno-preserve-as-comments.
CODEGENOPT(AssumeSaneOperatorNew , 1, 1, Benign) ///< implicit __attribute__((malloc)) operator new
+CODEGENOPT(AssumeArrayBounds , 1, 0, Benign) ///< Generate llvm.assume for array bounds.
CODEGENOPT(AssumeUniqueVTables , 1, 1, Benign) ///< Assume a class has only one vtable.
CODEGENOPT(Autolink , 1, 1, Benign) ///< -fno-autolink
CODEGENOPT(AutoImport , 1, 1, Benign) ///< -fno-auto-import
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 309bc94068a38..f52804bac94fb 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -1670,6 +1670,11 @@ defm assume_unique_vtables : BoolFOption<"assume-unique-vtables",
BothFlags<[], [ClangOption, CLOption]>>;
def fassume_sane_operator_new : Flag<["-"], "fassume-sane-operator-new">, Group<f_Group>;
+defm assume_array_bounds : BoolFOption<"assume-array-bounds",
+ CodeGenOpts<"AssumeArrayBounds">, DefaultFalse,
+ PosFlag<SetTrue, [], [ClangOption, CC1Option],
+ "Generate llvm.assume for array bounds to enable optimizations (may break code with intentional out-of-bounds access)">,
+ NegFlag<SetFalse, [], [ClangOption, CC1Option]>>;
def fastcp : Flag<["-"], "fastcp">, Group<f_Group>;
def fastf : Flag<["-"], "fastf">, Group<f_Group>;
def fast : Flag<["-"], "fast">, Group<f_Group>;
diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp
index 6abe4331db552..35b3ea0f926ea 100644
--- a/clang/lib/CodeGen/CGExpr.cpp
+++ b/clang/lib/CodeGen/CGExpr.cpp
@@ -1679,8 +1679,14 @@ bool CodeGenFunction::IsWrappedCXXThis(const Expr *Obj) {
LValue CodeGenFunction::EmitCheckedLValue(const Expr *E, TypeCheckKind TCK) {
LValue LV;
- if (SanOpts.has(SanitizerKind::ArrayBounds) && isa<ArraySubscriptExpr>(E))
- LV = EmitArraySubscriptExpr(cast<ArraySubscriptExpr>(E), /*Accessed*/true);
+ // This lvalue is about to be accessed (loaded from or stored to), so the
+ // array subscript must be within the strict bounds. Emitting the subscript as
+ // "accessed" lets both the array-bounds sanitizer and the array-bounds
+ // assumes (-fassume-array-bounds) use "index < size" rather than the weaker
+ // one-past-the-end "index <= size" that is required for plain address-of.
+ if (isa<ArraySubscriptExpr>(E) && (SanOpts.has(SanitizerKind::ArrayBounds) ||
+ CGM.getCodeGenOpts().AssumeArrayBounds))
+ LV = EmitArraySubscriptExpr(cast<ArraySubscriptExpr>(E), /*Accessed*/ true);
else
LV = EmitLValue(E);
if (!isa<DeclRefExpr>(E) && !LV.isBitField() && LV.isSimple()) {
@@ -4976,6 +4982,143 @@ void CodeGenFunction::EmitCountedByBoundsChecking(
}
}
+/// Emit array bounds constraints using llvm.assume for optimization hints.
+///
+/// C Standard (ISO/IEC 9899:2011 - C11)
+/// Section J.2 (Undefined behavior): An array subscript is out of range, even
+/// if an object is apparently accessible with the given subscript (as in the
+/// lvalue expression a[1][7] given the declaration int a[4][5]) (6.5.6).
+///
+/// Section 6.5.6 (Additive operators): If both the pointer operand and the
+/// result point to elements of the same array object, or one past the last
+/// element of the array object, the evaluation shall not produce an overflow;
+/// otherwise, the behavior is undefined.
+///
+/// C++ Standard (ISO/IEC 14882 - 2017)
+/// Section 8.7 (Additive operators):
+/// 4 When an expression that has integral type is added to or subtracted from a
+/// pointer, the result has the type of the pointer operand. If the expression
+/// P points to element x[i] of an array object x with n elements,^86 the
+/// expressions P + J and J + P (where J has the value j) point to the
+/// (possibly-hypothetical) element x[i + j] if 0 ≤ i + j ≤ n; otherwise, the
+/// behavior is undefined. Likewise, the expression P - J points to the
+/// (possibly-hypothetical) element x[i − j] if 0 ≤ i − j ≤ n; otherwise, the
+/// behavior is undefined.
+/// ^86 A pointer past the last element of an array x of n elements is
+/// considered to be equivalent to a pointer to a hypothetical element x[n]
+/// for this purpose; see 6.9.2.
+///
+
+/// The standards allow &arr[size] (one-past-the-end) for iterators,
+/// but dereferencing one-past-the-end is UB. This function uses the Accessed
+/// parameter to distinguish: Accessed=true uses strict bounds (index < size),
+/// Accessed=false allows one-past-the-end (index <= size).
+///
+/// Code that intentionally dereferences out-of-bounds (UB) may break with
+/// optimizations. Disabled when -fsanitize=array-bounds is active.
+///
+void CodeGenFunction::EmitArrayBoundsConstraints(const ArraySubscriptExpr *E,
+ llvm::Value *IndexVal,
+ bool Accessed) {
+ // Disable with -fno-assume-array-bounds.
+ if (!CGM.getCodeGenOpts().AssumeArrayBounds)
+ return;
+
+ // Disable at -O0.
+ if (CGM.getCodeGenOpts().OptimizationLevel == 0)
+ return;
+
+ // Disable with array-bounds sanitizer.
+ if (SanOpts.has(SanitizerKind::ArrayBounds))
+ return;
+
+ // Use the provided IndexVal to avoid duplicating side effects.
+ // The caller has already emitted the index expression once.
+ if (!IndexVal)
+ return;
+
+ const Expr *Base = E->getBase();
+ const Expr *Idx = E->getIdx();
+ QualType BaseType = Base->getType();
+
+ if (const auto *ICE = dyn_cast<ImplicitCastExpr>(Base)) {
+ if (ICE->getCastKind() == CK_ArrayToPointerDecay) {
+ BaseType = ICE->getSubExpr()->getType();
+ }
+ }
+
+ // Handle both constant arrays and VLAs (variable-length arrays.)
+ const ConstantArrayType *CAT = getContext().getAsConstantArrayType(BaseType);
+ llvm::Value *VLASize = nullptr;
+
+ if (!CAT) {
+ if (const VariableArrayType *VAT =
+ getContext().getAsVariableArrayType(BaseType))
+ VLASize = getVLASize(VAT).NumElts;
+ else
+ return; // Not a constant or VLA.
+ }
+
+ llvm::APInt ArraySize;
+ if (CAT)
+ ArraySize = CAT->getSize();
+
+ // Don't generate assumes for flexible array members, whose declared size does
+ // not reflect the real number of elements. This covers true flexible array
+ // members ("data[]") as well as the trailing "data[1]" and "data[0]" idioms,
+ // and honors -fstrict-flex-arrays so that a declared size is trusted when the
+ // user asks for it.
+ if (const auto *ME = dyn_cast<MemberExpr>(Base->IgnoreParenImpCasts())) {
+ const LangOptions::StrictFlexArraysLevelKind StrictFlexArraysLevel =
+ getLangOpts().getStrictFlexArraysLevel();
+ if (ME->isFlexibleArrayMemberLike(getContext(), StrictFlexArraysLevel))
+ return;
+ }
+
+ QualType IdxType = Idx->getType();
+ llvm::Type *IndexType = ConvertType(IdxType);
+ llvm::Value *ArraySizeVal;
+
+ if (CAT)
+ // Constant array: use compile-time size.
+ ArraySizeVal =
+ llvm::ConstantInt::get(IndexType, ArraySize.getLimitedValue());
+ else
+ // VLA: use runtime size.
+ ArraySizeVal =
+ VLASize->getType() == IndexType
+ ? VLASize
+ : Builder.CreateIntCast(VLASize, IndexType, false, "vla.size.cast");
+
+ // Ensure index value has the same type as our constants.
+ if (IndexVal->getType() != IndexType) {
+ bool IsSigned = IdxType->isSignedIntegerOrEnumerationType();
+ IndexVal = Builder.CreateIntCast(IndexVal, IndexType, IsSigned, "idx.cast");
+ }
+
+ // Create the bounds constraint. The Accessed parameter indicates whether the
+ // array element will be dereferenced. Per C/C++ standards, &arr[size]
+ // (one-past-the-end) is legal, e.g. for iterators, but dereferencing it is
+ // undefined behavior:
+ // Accessed == true: element is dereferenced, strict bound: index < size.
+ // Accessed == false: address only, allow one-past-the-end: index <= size.
+ //
+ // The array size is non-negative, so for a signed index the check
+ // "0 <= index (< or <=) size" is equivalent to the same unsigned comparison
+ // of the (sign-extended) index against the size: a negative signed index
+ // wraps to a large unsigned value that fails the check. A single unsigned
+ // comparison therefore handles both signed and unsigned indices.
+ llvm::Value *BoundsConstraint =
+ Accessed ? Builder.CreateICmpULT(IndexVal, ArraySizeVal, "idx.ult.size")
+ : Builder.CreateICmpULE(IndexVal, ArraySizeVal, "idx.ule.size");
+
+ // Mark array bounds assumes with metadata so they can be dropped before
+ // vectorization by DropUnnecessaryAssumesPass to prevent IR bloat.
+ llvm::CallInst *Assume = Builder.CreateAssumption(BoundsConstraint);
+ llvm::MDNode *MD = llvm::MDNode::get(Assume->getContext(), {});
+ Assume->setMetadata("llvm.array.bounds", MD);
+}
+
LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
bool Accessed) {
// The index must always be an integer, which is not an aggregate. Emit it
@@ -5005,13 +5148,20 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
};
IdxPre = nullptr;
+ // Array bounds constraints will be emitted after index evaluation to avoid
+ // duplicating side effects from the index expression.
+
// If the base is a vector type, then we are forming a vector element lvalue
// with this subscript.
if (E->getBase()->getType()->isSubscriptableVectorType() &&
!isa<ExtVectorElementExpr>(E->getBase())) {
// Emit the vector as an lvalue to get its address.
LValue LHS = EmitLValue(E->getBase());
- auto *Idx = EmitIdxAfterBase(/*Promote*/false);
+ auto *Idx = EmitIdxAfterBase(/*Promote*/ false);
+
+ // Emit array bounds constraints for vector subscripts.
+ EmitArrayBoundsConstraints(E, Idx, Accessed);
+
assert(LHS.isSimple() && "Can only subscript lvalue vectors here!");
return LValue::MakeVectorElt(LHS.getAddress(), Idx, E->getBase()->getType(),
LHS.getBaseInfo(), TBAAAccessInfo());
@@ -5056,7 +5206,10 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
// it. It needs to be emitted first in case it's what captures
// the VLA bounds.
Addr = EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
- auto *Idx = EmitIdxAfterBase(/*Promote*/true);
+ auto *Idx = EmitIdxAfterBase(/*Promote*/ true);
+
+ // Emit array bounds constraints for VLA access.
+ EmitArrayBoundsConstraints(E, Idx, Accessed);
// The element count here is the total number of non-VLA elements.
llvm::Value *numElements = getVLASize(vla).NumElts;
@@ -5080,7 +5233,10 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
// Emit the base pointer.
Addr = EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
- auto *Idx = EmitIdxAfterBase(/*Promote*/true);
+ auto *Idx = EmitIdxAfterBase(/*Promote*/ true);
+
+ // Emit array bounds constraints for ObjC interface access.
+ EmitArrayBoundsConstraints(E, Idx, Accessed);
CharUnits InterfaceSize = getContext().getTypeSizeInChars(OIT);
llvm::Value *InterfaceSizeVal =
@@ -5115,7 +5271,10 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
ArrayLV = EmitArraySubscriptExpr(ASE, /*Accessed*/ true);
else
ArrayLV = EmitLValue(Array);
- auto *Idx = EmitIdxAfterBase(/*Promote*/true);
+ auto *Idx = EmitIdxAfterBase(/*Promote*/ true);
+
+ // Emit array bounds constraints for optimization.
+ EmitArrayBoundsConstraints(E, Idx, Accessed);
if (SanOpts.has(SanitizerKind::ArrayBounds))
EmitCountedByBoundsChecking(Array, Array->getType(), ArrayLV.getAddress(),
@@ -5159,7 +5318,11 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E,
// The base must be a pointer; emit it with an estimate of its alignment.
Address BaseAddr =
EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo);
- auto *Idx = EmitIdxAfterBase(/*Promote*/true);
+ auto *Idx = EmitIdxAfterBase(/*Promote*/ true);
+
+ // Emit array bounds constraints for pointer-based array access.
+ EmitArrayBoundsConstraints(E, Idx, Accessed);
+
QualType ptrType = E->getBase()->getType();
Addr = emitArraySubscriptGEP(*this, BaseAddr, Idx, E->getType(),
!getLangOpts().PointerOverflowDefined,
diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp
index 18ed6570730f4..75f1d71adbdb0 100644
--- a/clang/lib/CodeGen/CGExprScalar.cpp
+++ b/clang/lib/CodeGen/CGExprScalar.cpp
@@ -2213,7 +2213,10 @@ Value *ScalarExprEmitter::VisitArraySubscriptExpr(ArraySubscriptExpr *E) {
QualType IdxTy = E->getIdx()->getType();
if (CGF.SanOpts.has(SanitizerKind::ArrayBounds))
- CGF.EmitBoundsCheck(E, E->getBase(), Idx, IdxTy, /*Accessed*/true);
+ CGF.EmitBoundsCheck(E, E->getBase(), Idx, IdxTy, /*Accessed*/ true);
+
+ // Emit array bounds constraints for vector element access.
+ CGF.EmitArrayBoundsConstraints(E, Idx, /*Accessed=*/true);
Value *Ret = Builder.CreateExtractElement(Base, Idx, "vecext");
diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h
index 6d0718c243812..c3be1dce8d0f4 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -3396,6 +3396,15 @@ class CodeGenFunction : public CodeGenTypeCache {
llvm::Value *BoundsVal, QualType BoundsType,
bool Accessed);
+ /// Emit array bounds constraints using llvm.assume for optimization hints.
+ /// Emits assume statements for array bounds without duplicating side effects.
+ /// Takes the already-emitted index value to avoid re-evaluating expressions
+ /// with side effects. The Accessed parameter distinguishes:
+ /// - dereferenced use strict bounds: index < size vs.
+ /// - address-only allows one-past: index <= size.
+ void EmitArrayBoundsConstraints(const ArraySubscriptExpr *E,
+ llvm::Value *IndexVal, bool Accessed);
+
/// Returns debug info, with additional annotation if
/// CGM.getCodeGenOpts().SanitizeAnnotateDebugInfo[Ordinal] is enabled for
/// any of the ordinals.
diff --git a/clang/test/CodeGen/array-bounds-constraints-safety.c b/clang/test/CodeGen/array-bounds-constraints-safety.c
new file mode 100644
index 0000000000000..011d0e798f9b5
--- /dev/null
+++ b/clang/test/CodeGen/array-bounds-constraints-safety.c
@@ -0,0 +1,106 @@
+// Use -O1 -disable-llvm-optzns to check assumes before DropUnnecessaryAssumesPass
+// drops them (assumes with llvm.array.bounds metadata are dropped before vectorization).
+// RUN: %clang_cc1 -emit-llvm -O1 -disable-llvm-optzns -fassume-array-bounds %s -o - | FileCheck %s
+
+// Test that array bounds constraints are NOT applied to cases that might
+// break real-world code with intentional out-of-bounds access patterns.
+
+// CHECK-LABEL: define {{.*}} @test_zero_length_array
+struct ZeroLengthData {
+ int count;
+ int items[0]; // GNU C extension: zero-length array
+};
+
+int test_zero_length_array(struct ZeroLengthData *d, int i) {
+ // CHECK-NOT: call void @llvm.assume
+ // Zero-length array as last field should not generate bounds constraints.
+ return d->items[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_flexible_array_member
+struct Data {
+ int count;
+ int items[1]; // Flexible array member pattern (pre-C99 style)
+};
+
+int test_flexible_array_member(struct Data *d, int i) {
+ // CHECK-NOT: call void @llvm.assume
+ // Flexible array member pattern should NOT generate bounds constraints.
+ return d->items[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_not_flexible_array
+struct NotFlexible {
+ int items[1]; // Size 1 array but NOT the last field.
+ int count; // Something comes after it.
+};
+
+int test_not_flexible_array(struct NotFlexible *s, int i) {
+ // CHECK: call void @llvm.assume{{.*}}!llvm.array.bounds
+ // This is NOT a flexible array pattern, so we generate assume.
+ return s->items[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_pointer_parameter
+int test_pointer_parameter(int *arr, int i) {
+ // CHECK-NOT: call void @llvm.assume
+ // Pointer parameters should NOT generate bounds constraints.
+ return arr[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_vla
+void init_vla(int *arr, int n);
+
+int test_vla(int n, int i) {
+ int arr[n];
+ init_vla(arr, n);
+ // CHECK: call void @llvm.assume{{.*}}!llvm.array.bounds
+ // For VLAs, generate bounds constraints using the runtime size.
+ return arr[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_one_past_end
+extern int extern_array[100];
+int *test_one_past_end(void) {
+ // One-past-the-end address is legal per C standard.
+ // We generate assume(i1 true) because 100 <= 100 is trivially true.
+ // CHECK: call void @llvm.assume(i1 true){{.*}}!llvm.array.bounds
+ return &extern_array[100];
+}
+
+// CHECK-LABEL: define {{.*}} @test_one_past_end_variable
+int *test_one_past_end_variable(int i) {
+ // Taking the address without dereferencing is allowed one-past-the-end, so
+ // the bound is inclusive: index <= size.
+ // CHECK: icmp ule i{{.*}} %{{.*}}, 100
+ // CHECK: call void @llvm.assume{{.*}}!llvm.array.bounds
+ return &extern_array[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_extern_array
+int test_extern_array(int i) {
+ // A real element access uses the strict bound: index < size.
+ // CHECK: icmp ult i{{.*}} %{{.*}}, 100
+ // CHECK: call void @llvm.assume{{.*}}!llvm.array.bounds
+ // Constant-size global array generates bounds constraints.
+ return extern_array[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_local_constant_array
+void init_array(int *arr);
+int test_local_constant_array(int i) {
+ int arr[10];
+ init_array(arr);
+ // CHECK: call void @llvm.assume{{.*}}!llvm.array.bounds
+ // Local constant-size array generates bounds constraints.
+ return arr[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_malloc_array
+int *my_malloc(int);
+int test_malloc_array(int i) {
+ // CHECK-NOT: call void @llvm.assume
+ // Dynamically allocated arrays via pointers do not get bounds constraints.
+ int *x = my_malloc(100 * sizeof(int));
+ return x[i];
+}
diff --git a/clang/test/CodeGen/array-bounds-constraints-strict-flex.c b/clang/test/CodeGen/array-bounds-constraints-strict-flex.c
new file mode 100644
index 0000000000000..747b828267463
--- /dev/null
+++ b/clang/test/CodeGen/array-bounds-constraints-strict-flex.c
@@ -0,0 +1,35 @@
+// Verify that -fstrict-flex-arrays controls whether a trailing array is treated
+// as a flexible array member for the purpose of the array bounds assumes
+// emitted by -fassume-array-bounds. Use -O1 -disable-llvm-optzns to check the
+// assumes before DropUnnecessaryAssumesPass drops them.
+// RUN: %clang_cc1 -emit-llvm -O1 -disable-llvm-optzns -fassume-array-bounds -fstrict-flex-arrays=0 %s -o - | FileCheck %s --check-prefixes=CHECK,LEVEL0
+// RUN: %clang_cc1 -emit-llvm -O1 -disable-llvm-optzns -fassume-array-bounds -fstrict-flex-arrays=3 %s -o - | FileCheck %s --check-prefixes=CHECK,LEVEL3
+
+struct TrailingOne {
+ int count;
+ int data[1];
+};
+
+// CHECK-LABEL: define {{.*}} @access_trailing_size_one
+int access_trailing_size_one(struct TrailingOne *s, int i) {
+ // At -fstrict-flex-arrays=0 the trailing "data[1]" is treated as a flexible
+ // array member, so no bounds assume is emitted.
+ // LEVEL0-NOT: call void @llvm.assume
+ // At -fstrict-flex-arrays=3 only "data[]" is flexible, so "data[1]" is a real
+ // one-element array and a bounds assume is emitted.
+ // LEVEL3: call void @llvm.assume{{.*}}!llvm.array.bounds
+ return s->data[i];
+}
+
+struct TrailingIncomplete {
+ int count;
+ int data[];
+};
+
+// CHECK-LABEL: define {{.*}} @access_trailing_incomplete
+int access_trailing_incomplete(struct TrailingIncomplete *s, int i) {
+ // A true flexible array member ("data[]") is never given a bounds assume,
+ // regardless of the -fstrict-flex-arrays level.
+ // CHECK-NOT: call void @llvm.assume
+ return s->data[i];
+}
diff --git a/clang/test/CodeGen/array-bounds-constraints.c b/clang/test/CodeGen/array-bounds-constraints.c
new file mode 100644
index 0000000000000..41a02ab4dc880
--- /dev/null
+++ b/clang/test/CodeGen/array-bounds-constraints.c
@@ -0,0 +1,44 @@
+// This test verifies that clang generates llvm.assume statements to inform the
+// optimizer that array subscripts are within bounds to enable better optimization.
+// Use -O1 -disable-llvm-optzns to check assumes before DropUnnecessaryAssumesPass
+// drops them (assumes with llvm.array.bounds metadata are dropped before vectorization).
+// RUN: %clang_cc1 -emit-llvm -O1 -disable-llvm-optzns -fassume-array-bounds %s -o - | FileCheck %s
+
+// Verify no assumes are generated.
+// RUN: %clang_cc1 -emit-llvm -O1 -disable-llvm-optzns -fno-assume-array-bounds %s -o - | FileCheck %s -check-prefix=NO-FLAG
+
+// CHECK-LABEL: define {{.*}} @test_simple_array
+// NO-FLAG-LABEL: define {{.*}} @test_simple_array
+void init_array(int *arr);
+int test_simple_array(int i) {
+ int arr[10];
+ init_array(arr);
+ // CHECK: call void @llvm.assume(i1 %{{.*}}){{.*}}!llvm.array.bounds
+ // NO-FLAG-NOT: call void @llvm.assume
+ return arr[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_multidimensional_array
+int test_multidimensional_array(int i, int j) {
+ int arr[5][8];
+ init_array(arr[0]);
+ // CHECK: call void @llvm.assume(i1 %{{.*}}){{.*}}!llvm.array.bounds
+ // CHECK: call void @llvm.assume(i1 %{{.*}}){{.*}}!llvm.array.bounds
+ return arr[i][j];
+}
+
+// CHECK-LABEL: define {{.*}} @test_unsigned_index
+int test_unsigned_index(unsigned int i) {
+ int arr[10];
+ init_array(arr);
+ // CHECK: call void @llvm.assume(i1 %{{.*}}){{.*}}!llvm.array.bounds
+ return arr[i];
+}
+
+// CHECK-LABEL: define {{.*}} @test_store
+void test_store(int i, int value) {
+ int arr[10];
+ // CHECK: call void @llvm.assume(i1 %{{.*}}){{.*}}!llvm.array.bounds
+ arr[i] = value;
+ init_array(arr);
+}
More information about the cfe-commits
mailing list