diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def index 872f73ebf3810..274ae075c2de7 100644 --- a/clang/include/clang/Basic/CodeGenOptions.def +++ b/clang/include/clang/Basic/CodeGenOptions.def @@ -33,6 +33,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/Driver/Options.td b/clang/include/clang/Driver/Options.td index def7c09d58cfb..d4b324e9650a7 100644 --- a/clang/include/clang/Driver/Options.td +++ b/clang/include/clang/Driver/Options.td @@ -1603,6 +1603,11 @@ defm assume_unique_vtables : BoolFOption<"assume-unique-vtables", BothFlags<[], [ClangOption, CLOption]>>; def fassume_sane_operator_new : Flag<["-"], "fassume-sane-operator-new">, Group; +defm assume_array_bounds : BoolFOption<"assume-array-bounds", + CodeGenOpts<"AssumeArrayBounds">, DefaultFalse, + PosFlag, + NegFlag>; def fastcp : Flag<["-"], "fastcp">, Group; def fastf : Flag<["-"], "fastf">, Group; def fast : Flag<["-"], "fast">, Group; diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp index e6e4947882544..b0d35f5ba508e 100644 --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -4559,6 +4559,157 @@ 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(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 member pattern. + // Size-1 arrays: "struct { int len; char data[1]; }" (pre-C99 idiom.) + // Zero-length arrays: "struct { int len; char data[0]; }" (GCC extension + // https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html) + // Both patterns use arrays as placeholders for variable-length data. + if (CAT && (ArraySize == 0 || ArraySize == 1)) { + if (const auto *ME = dyn_cast(Base->IgnoreParenImpCasts())) { + if (const auto *FD = dyn_cast(ME->getMemberDecl())) { + const RecordDecl *RD = FD->getParent(); + // Check if this field is the last field in the record. + // Only the last field can be a flexible array member. + const FieldDecl *LastField = nullptr; + for (const auto *Field : RD->fields()) + LastField = Field; + if (LastField == FD) + // This is a zero-length or size-1 array as the last field. + // Likely a flexible array member pattern - skip assumes. + 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 bounds constraint: 0 <= index && index (< or <=) size. + // The Accessed parameter indicates whether the array element will be + // dereferenced. Per C/C++ standards, &arr[size] (one-past-the-end) is legal + // for iterators. + // Accessed = true: element is dereferenced, strict bounds: 0 <= index < size + // Accessed = false: address only, allow one-past-the-end: 0 <= index <= size + + if (IdxType->isSignedIntegerOrEnumerationType()) { + // For signed indices: index >= 0 && index [<|<=] size. + llvm::Value *Zero = llvm::ConstantInt::get(IndexType, 0); + llvm::Value *LowerBound = + Builder.CreateICmpSGE(IndexVal, Zero, "idx.ge.zero"); + llvm::Value *UpperBound = Accessed + ? Builder.CreateICmpSLT(IndexVal, ArraySizeVal, "idx.slt.size") + : Builder.CreateICmpSLE(IndexVal, ArraySizeVal, "idx.sle.size"); + llvm::Value *BoundsConstraint = + Builder.CreateAnd(LowerBound, UpperBound, "bounds.constraint"); + Builder.CreateAssumption(BoundsConstraint); + } else { + // For unsigned indices: index [<|<=] size. (>= 0 is implicit.) + llvm::Value *UpperBound = Accessed + ? Builder.CreateICmpULT(IndexVal, ArraySizeVal, "idx.ult.size") + : Builder.CreateICmpULE(IndexVal, ArraySizeVal, "idx.ule.size"); + Builder.CreateAssumption(UpperBound); + } +} + LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E, bool Accessed) { // The index must always be an integer, which is not an aggregate. Emit it @@ -4588,6 +4739,9 @@ 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() && @@ -4595,6 +4749,10 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E, // Emit the vector as an lvalue to get its address. LValue LHS = EmitLValue(E->getBase()); 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()); @@ -4635,6 +4793,9 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E, Addr = EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo); 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; @@ -4659,6 +4820,9 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E, Addr = EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo); 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 = llvm::ConstantInt::get(Idx->getType(), InterfaceSize.getQuantity()); @@ -4694,6 +4858,9 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E, ArrayLV = EmitLValue(Array); auto *Idx = EmitIdxAfterBase(/*Promote*/true); + // Emit array bounds constraints for optimization. + EmitArrayBoundsConstraints(E, Idx, Accessed); + if (SanOpts.has(SanitizerKind::ArrayBounds)) EmitCountedByBoundsChecking(Array, Idx, ArrayLV.getAddress(), E->getIdx()->getType(), Array->getType(), @@ -4737,6 +4904,10 @@ LValue CodeGenFunction::EmitArraySubscriptExpr(const ArraySubscriptExpr *E, Address BaseAddr = EmitPointerWithAlignment(E->getBase(), &EltBaseInfo, &EltTBAAInfo); 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 4fa25c5d66669..eb4e0dd9828f6 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -2100,6 +2100,9 @@ Value *ScalarExprEmitter::VisitArraySubscriptExpr(ArraySubscriptExpr *E) { if (CGF.SanOpts.has(SanitizerKind::ArrayBounds)) CGF.EmitBoundsCheck(E, E->getBase(), Idx, IdxTy, /*Accessed*/true); + // Emit array bounds constraints for vector element access. + CGF.EmitArrayBoundsConstraints(E, Idx, /*Accessed=*/true); + return Builder.CreateExtractElement(Base, Idx, "vecext"); } diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 727487b46054f..8354d5c130d72 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -3341,6 +3341,15 @@ class CodeGenFunction : public CodeGenTypeCache { llvm::Value *Index, QualType IndexType, QualType IndexedType, 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..504b5ab31536b --- /dev/null +++ b/clang/test/CodeGen/array-bounds-constraints-safety.c @@ -0,0 +1,107 @@ +// RUN: %clang_cc1 -emit-llvm -O2 -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. + +// C18 standard allows one-past-the-end pointers, and some legacy code +// intentionally accesses out-of-bounds for performance or compatibility. +// This test verifies that bounds constraints are only applied to safe cases. + +// 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. + // See https://gcc.gnu.org/onlinedocs/gcc/Zero-Length.html + 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 (size 1 array as last field) should NOT + // generate bounds constraints because items[1] is just a placeholder + // for a larger array allocated with `malloc (sizeof (struct Data) + 42)`. + 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 + // This is NOT a flexible array pattern (not the last field), + // so we're fine generating `assume(i < 1)`. + 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 + // because we don't know the actual array size. + 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]; // Variable-length array. + init_vla(arr, n); // Initialize to avoid UB. + // CHECK: call void @llvm.assume + // For VLAs, generate bounds constraints using the runtime size: 0 <= i < n. + return arr[i]; +} + +// CHECK-LABEL: define {{.*}} @test_one_past_end +extern int extern_array[100]; +int *test_one_past_end(void) { + // CHECK-NOT: call void @llvm.assume + // Taking address of one-past-the-end is legal per C standard. + // Used in iterators (e.g., arr + size, std::end(arr)). + // No assumes are generated for address-only operations. + return &extern_array[100]; // Legal: one past the end. +} + +// CHECK-LABEL: define {{.*}} @test_extern_array +int test_extern_array(int i) { + // CHECK: call void @llvm.assume + // This will generate bounds constraints. + // The array is a constant-size global array. + // This is the safe case where we want optimization hints. + 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); // Initialize to avoid UB from uninitialized read. + // CHECK: call void @llvm.assume + // This will generate bounds constraints. + // We know the exact size of this alloca array. + // This is the safe case where we want optimization hints. + 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 accessed 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.c b/clang/test/CodeGen/array-bounds-constraints.c new file mode 100644 index 0000000000000..0b16deaf64aac --- /dev/null +++ b/clang/test/CodeGen/array-bounds-constraints.c @@ -0,0 +1,54 @@ +// This test verifies that clang generates llvm.assume statements to inform the +// optimizer that array subscripts are within bounds to enable better optimization. +// RUN: %clang_cc1 -emit-llvm -O2 -fassume-array-bounds %s -o - | FileCheck %s + +// Verify no assumes are generated. +// RUN: %clang_cc1 -emit-llvm -O2 -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); + // Single-dimension array subscript: Accessed defaults to false to support + // C++ iterators that use &arr[size]. This generates index < 11 (not < 10) + // to allow one-past-the-end address formation. + // CHECK: %{{.*}} = icmp ult i32 %i, 11 + // CHECK: call void @llvm.assume(i1 %{{.*}}) + // 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]; // Valid indices: i in [0, 4], j in [0, 7] + init_array(arr[0]); // Initialize to avoid UB from uninitialized read. + // Multidimensional: inner subscript (i) uses Accessed=true (strict < 5) + // outer subscript (j) may allow one-past-the-end + // CHECK: %{{.*}} = icmp ult i32 %i, 5 + // CHECK: call void @llvm.assume(i1 %{{.*}}) + // CHECK: %{{.*}} = icmp ult i32 %j, 9 + // CHECK: call void @llvm.assume(i1 %{{.*}}) + return arr[i][j]; +} + +// CHECK-LABEL: define {{.*}} @test_unsigned_index +int test_unsigned_index(unsigned int i) { + int arr[10]; + init_array(arr); // Initialize to avoid UB from uninitialized read. + // Accessed=false, allows one-past-the-end + // CHECK: %{{.*}} = icmp ult i32 %i, 11 + // CHECK: call void @llvm.assume(i1 %{{.*}}) + return arr[i]; +} + +// CHECK-LABEL: define {{.*}} @test_store_undef +void test_store_undef(int i, int value) { + int arr[10]; + // Accessed=false, allows one-past-the-end + // CHECK: %{{.*}} = icmp ult i32 %i, 11 + // CHECK: call void @llvm.assume(i1 %{{.*}}) + arr[i] = value; + init_array(arr); // Avoid optimization of the above statement. +}