[Clang][CodeGen] Preserve alignment information for pointer arithmetics (#152575)

Previously, the alignment of pointer arithmetics was inferred from the
pointee type, losing the alignment information from its operands:

503c0908c3/clang/lib/CodeGen/CGExpr.cpp (L1446-L1449)

This patch preserves alignment information for pointer arithmetics `P
+/- C`, to match the behavior of identical array subscript `&P[C]`:
https://godbolt.org/z/xx1hfTrx4.

Closes https://github.com/llvm/llvm-project/issues/152330. Although the
motivating case can be fixed by
https://github.com/llvm/llvm-project/pull/145733, the alignment cannot
be recovered without a dominating memory access with larger alignment.
This commit is contained in:
Yingwei Zheng 2025-08-25 20:08:32 +08:00 committed by GitHub
parent 41aba9ef3b
commit 5569bf26f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 148 additions and 20 deletions

View File

@ -1325,6 +1325,57 @@ void CodeGenModule::EmitExplicitCastExprType(const ExplicitCastExpr *E,
// LValue Expression Emission
//===----------------------------------------------------------------------===//
static CharUnits getArrayElementAlign(CharUnits arrayAlign, llvm::Value *idx,
CharUnits eltSize) {
// If we have a constant index, we can use the exact offset of the
// element we're accessing.
if (auto *constantIdx = dyn_cast<llvm::ConstantInt>(idx)) {
CharUnits offset = constantIdx->getZExtValue() * eltSize;
return arrayAlign.alignmentAtOffset(offset);
}
// Otherwise, use the worst-case alignment for any element.
return arrayAlign.alignmentOfArrayElement(eltSize);
}
/// Emit pointer + index arithmetic.
static Address emitPointerArithmetic(CodeGenFunction &CGF,
const BinaryOperator *BO,
LValueBaseInfo *BaseInfo,
TBAAAccessInfo *TBAAInfo,
KnownNonNull_t IsKnownNonNull) {
assert(BO->isAdditiveOp() && "Expect an addition or subtraction.");
Expr *pointerOperand = BO->getLHS();
Expr *indexOperand = BO->getRHS();
bool isSubtraction = BO->getOpcode() == BO_Sub;
Address BaseAddr = Address::invalid();
llvm::Value *index = nullptr;
// In a subtraction, the LHS is always the pointer.
// Note: do not change the evaluation order.
if (!isSubtraction && !pointerOperand->getType()->isAnyPointerType()) {
std::swap(pointerOperand, indexOperand);
index = CGF.EmitScalarExpr(indexOperand);
BaseAddr = CGF.EmitPointerWithAlignment(pointerOperand, BaseInfo, TBAAInfo,
NotKnownNonNull);
} else {
BaseAddr = CGF.EmitPointerWithAlignment(pointerOperand, BaseInfo, TBAAInfo,
NotKnownNonNull);
index = CGF.EmitScalarExpr(indexOperand);
}
llvm::Value *pointer = BaseAddr.getBasePointer();
llvm::Value *Res = CGF.EmitPointerArithmetic(
BO, pointerOperand, pointer, indexOperand, index, isSubtraction);
QualType PointeeTy = BO->getType()->getPointeeType();
CharUnits Align =
getArrayElementAlign(BaseAddr.getAlignment(), index,
CGF.getContext().getTypeSizeInChars(PointeeTy));
return Address(Res, CGF.ConvertTypeForMem(PointeeTy), Align,
CGF.CGM.getPointerAuthInfoForPointeeType(PointeeTy),
/*Offset=*/nullptr, IsKnownNonNull);
}
static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
TBAAAccessInfo *TBAAInfo,
KnownNonNull_t IsKnownNonNull,
@ -1387,6 +1438,7 @@ static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
if (CE->getCastKind() == CK_AddressSpaceConversion)
Addr = CGF.Builder.CreateAddrSpaceCast(
Addr, CGF.ConvertType(E->getType()), ElemTy);
return CGF.authPointerToPointerCast(Addr, CE->getSubExpr()->getType(),
CE->getType());
}
@ -1447,6 +1499,12 @@ static Address EmitPointerWithAlignment(const Expr *E, LValueBaseInfo *BaseInfo,
}
}
// Pointer arithmetic: pointer +/- index.
if (auto *BO = dyn_cast<BinaryOperator>(E)) {
if (BO->isAdditiveOp())
return emitPointerArithmetic(CGF, BO, BaseInfo, TBAAInfo, IsKnownNonNull);
}
// TODO: conditional operators, comma.
// Otherwise, use the alignment of the type.
@ -4236,21 +4294,6 @@ static Address emitArraySubscriptGEP(CodeGenFunction &CGF, Address addr,
}
}
static CharUnits getArrayElementAlign(CharUnits arrayAlign,
llvm::Value *idx,
CharUnits eltSize) {
// If we have a constant index, we can use the exact offset of the
// element we're accessing.
if (auto constantIdx = dyn_cast<llvm::ConstantInt>(idx)) {
CharUnits offset = constantIdx->getZExtValue() * eltSize;
return arrayAlign.alignmentAtOffset(offset);
// Otherwise, use the worst-case alignment for any element.
} else {
return arrayAlign.alignmentOfArrayElement(eltSize);
}
}
static QualType getFixedSizeElementType(const ASTContext &ctx,
const VariableArrayType *vla) {
QualType eltType;

View File

@ -4186,7 +4186,9 @@ Value *ScalarExprEmitter::EmitOverflowCheckedBinOp(const BinOpInfo &Ops) {
return phi;
}
/// This function is used for BO_Add/BO_Sub/BO_AddAssign/BO_SubAssign.
/// BO_Add/BO_Sub are handled by EmitPointerWithAlignment to preserve alignment
/// information.
/// This function is used for BO_AddAssign/BO_SubAssign.
static Value *emitPointerArithmetic(CodeGenFunction &CGF, const BinOpInfo &op,
bool isSubtraction) {
// Must have binary (not unary) expr here. Unary pointer

View File

@ -55,7 +55,7 @@ int align3_x0 = __alignof(((struct s3*) 0)->x[0]);
// CHECK: load i32, ptr %{{.*}}, align 1
// CHECK: }
// CHECK-LABEL: define{{.*}} i32 @f0_b
// CHECK: load i32, ptr %{{.*}}, align 4
// CHECK: load i32, ptr %{{.*}}, align 1
// CHECK: }
int f0_a(struct s0 *a) {
return a->x[1];
@ -100,7 +100,7 @@ int f1_d(struct s1 *a) {
// CHECK: load i32, ptr %{{.*}}, align 1
// CHECK: }
// CHECK-LABEL: define{{.*}} i32 @f2_b
// CHECK: load i32, ptr %{{.*}}, align 4
// CHECK: load i32, ptr %{{.*}}, align 1
// CHECK: }
// CHECK-LABEL: define{{.*}} i32 @f2_c
// CHECK: load i32, ptr %{{.*}}, align 1
@ -125,7 +125,7 @@ int f2_d(struct s2 *a) {
// CHECK: load i32, ptr %{{.*}}, align 1
// CHECK: }
// CHECK-LABEL: define{{.*}} i32 @f3_b
// CHECK: load i32, ptr %{{.*}}, align 4
// CHECK: load i32, ptr %{{.*}}, align 1
// CHECK: }
// CHECK-LABEL: define{{.*}} i32 @f3_c
// CHECK: load i32, ptr %{{.*}}, align 1

View File

@ -0,0 +1,83 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5
// RUN: %clang_cc1 -O1 -triple=x86_64-unknown-linux %s -emit-llvm -o - | FileCheck %s
typedef unsigned char uint8_t;
typedef unsigned long long uint64_t;
struct a {
uint64_t b;
uint8_t block[16];
};
// CHECK-LABEL: define dso_local void @ptradd_0(
// CHECK-SAME: ptr noundef writeonly captures(none) initializes((8, 9)) [[CTX:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[BLOCK:%.*]] = getelementptr inbounds nuw i8, ptr [[CTX]], i64 8
// CHECK-NEXT: store i8 0, ptr [[BLOCK]], align 8, !tbaa [[TBAA2:![0-9]+]]
// CHECK-NEXT: ret void
//
void ptradd_0(struct a *ctx) {
*(ctx->block + 0) = 0;
}
// CHECK-LABEL: define dso_local void @ptradd_4(
// CHECK-SAME: ptr noundef writeonly captures(none) initializes((12, 13)) [[CTX:%.*]]) local_unnamed_addr #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[ADD_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[CTX]], i64 12
// CHECK-NEXT: store i8 0, ptr [[ADD_PTR]], align 4, !tbaa [[TBAA2]]
// CHECK-NEXT: ret void
//
void ptradd_4(struct a *ctx) {
*(ctx->block + 4) = 0;
}
// CHECK-LABEL: define dso_local void @ptradd_8(
// CHECK-SAME: ptr noundef writeonly captures(none) initializes((16, 17)) [[CTX:%.*]]) local_unnamed_addr #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[ADD_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[CTX]], i64 16
// CHECK-NEXT: store i8 0, ptr [[ADD_PTR]], align 8, !tbaa [[TBAA2]]
// CHECK-NEXT: ret void
//
void ptradd_8(struct a *ctx) {
*(ctx->block + 8) = 0;
}
// CHECK-LABEL: define dso_local void @ptradd_8_commuted(
// CHECK-SAME: ptr noundef writeonly captures(none) initializes((16, 17)) [[CTX:%.*]]) local_unnamed_addr #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[ADD_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[CTX]], i64 16
// CHECK-NEXT: store i8 0, ptr [[ADD_PTR]], align 8, !tbaa [[TBAA2]]
// CHECK-NEXT: ret void
//
void ptradd_8_commuted(struct a *ctx) {
*(8 + ctx->block) = 0;
}
// CHECK-LABEL: define dso_local void @ptrsub_4(
// CHECK-SAME: ptr noundef writeonly captures(none) initializes((8, 9)) [[CTX:%.*]]) local_unnamed_addr #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[ADD_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[CTX]], i64 8
// CHECK-NEXT: store i8 0, ptr [[ADD_PTR]], align 4, !tbaa [[TBAA2]]
// CHECK-NEXT: ret void
//
void ptrsub_4(struct a *ctx) {
*(&ctx->block[4] - 4) = 0;
}
// CHECK-LABEL: define dso_local void @neg_ptradd_var_index(
// CHECK-SAME: ptr noundef writeonly captures(none) [[CTX:%.*]], i8 noundef zeroext [[IDX:%.*]]) local_unnamed_addr #[[ATTR0]] {
// CHECK-NEXT: [[ENTRY:.*:]]
// CHECK-NEXT: [[BLOCK:%.*]] = getelementptr inbounds nuw i8, ptr [[CTX]], i64 8
// CHECK-NEXT: [[IDX_EXT:%.*]] = zext i8 [[IDX]] to i64
// CHECK-NEXT: [[ADD_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[BLOCK]], i64 [[IDX_EXT]]
// CHECK-NEXT: store i8 0, ptr [[ADD_PTR]], align 1, !tbaa [[TBAA2]]
// CHECK-NEXT: ret void
//
void neg_ptradd_var_index(struct a *ctx, uint8_t idx) {
*(ctx->block + idx) = 0;
}
//.
// CHECK: [[TBAA2]] = !{[[META3:![0-9]+]], [[META3]], i64 0}
// CHECK: [[META3]] = !{!"omnipotent char", [[META4:![0-9]+]], i64 0}
// CHECK: [[META4]] = !{!"Simple C/C++ TBAA"}
//.

View File

@ -18,7 +18,7 @@ struct X { int z[17]; };
// CHECK-NEXT: store i8 [[TMP0]], ptr [[ADD_PTR]], align 1
// CHECK-NEXT: [[TMP1:%.*]] = load i8, ptr [[Y_ADDR_ASCAST]], align 1
// CHECK-NEXT: [[ADD_PTR1:%.*]] = getelementptr inbounds i8, ptr [[AGG_RESULT_ASCAST]], i64 2
// CHECK-NEXT: store i8 [[TMP1]], ptr [[ADD_PTR1]], align 1
// CHECK-NEXT: store i8 [[TMP1]], ptr [[ADD_PTR1]], align 2
// CHECK-NEXT: ret void
//
X foo(char x, char y) {