From 547c67a3885309ba02975a3ad3bef09f6c499e0d Mon Sep 17 00:00:00 2001 From: Andy Kaylor Date: Wed, 2 Apr 2025 13:42:44 -0700 Subject: [PATCH] [CIR] Fix for missing side effects during null pointer initialization When a pointer variable is initialized with a null pointer, the AST contains a NullToPointer cast. If we just emit a null pointer of the correct type, we will miss any side effects that occur in the initializer. This change adds code to emit the initializer expression if it is not a simple constant. This results in an extra null pointer constant being emitted when the expression has side effects, but the constant emitted while visiting the expression does not have the correct type, so the alternative would be to capture the emitted constant and bitcast it to the correct type. An extra constant seems less intrusive than an unnecessary bitcast. --- clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp | 10 ++- clang/test/CIR/CodeGen/nullptr-init.cpp | 76 ++++++++++++++++++++++ 2 files changed, 83 insertions(+), 3 deletions(-) create mode 100644 clang/test/CIR/CodeGen/nullptr-init.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index d9dc455cf374..2878de37c9ff 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -1180,9 +1180,11 @@ mlir::Value CIRGenFunction::emitPromotedScalarExpr(const Expr *E, } [[maybe_unused]] static bool MustVisitNullValue(const Expr *E) { - // If a null pointer expression's type is the C++0x nullptr_t, then - // it's not necessarily a simple constant and it must be evaluated + // If a null pointer expression's type is the C++0x nullptr_t and + // the expression is not a simple literal, it must be evaluated // for its potential side effects. + if (isa(E) || isa(E)) + return false; return E->getType()->isNullPtrType(); } @@ -1723,7 +1725,9 @@ mlir::Value ScalarExprEmitter::VisitCastExpr(CastExpr *CE) { return emitLValue(E).getPointer(); case CK_NullToPointer: { - // FIXME: use MustVisitNullValue(E) and evaluate expr. + if (MustVisitNullValue(E)) + CGF.emitIgnoredExpr(E); + // Note that DestTy is used as the MLIR type instead of a custom // nullptr type. mlir::Type Ty = CGF.convertType(DestTy); diff --git a/clang/test/CIR/CodeGen/nullptr-init.cpp b/clang/test/CIR/CodeGen/nullptr-init.cpp new file mode 100644 index 000000000000..90db8f76da4e --- /dev/null +++ b/clang/test/CIR/CodeGen/nullptr-init.cpp @@ -0,0 +1,76 @@ +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu %s -fclangir -emit-cir -o %t.cir +// RUN: FileCheck --input-file=%t.cir -check-prefix=CIR %s +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu %s -fclangir -emit-llvm -o %t.ll +// RUN: FileCheck --input-file=%t.ll -check-prefix=LLVM %s + +void t1() { + int *p1 = nullptr; + int *p2 = 0; + int *p3 = (int*)0; +} + +// CIR: cir.func @_Z2t1v() +// CIR-NEXT: %[[P1:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["p1", init] {alignment = 8 : i64} +// CIR-NEXT: %[[P2:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["p2", init] {alignment = 8 : i64} +// CIR-NEXT: %[[P3:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["p3", init] {alignment = 8 : i64} +// CIR-NEXT: %[[NULLPTR1:.*]] = cir.const #cir.ptr : !cir.ptr +// CIR-NEXT: cir.store %[[NULLPTR1]], %[[P1]] : !cir.ptr, !cir.ptr> +// CIR-NEXT: %[[NULLPTR2:.*]] = cir.const #cir.ptr : !cir.ptr +// CIR-NEXT: cir.store %[[NULLPTR2]], %[[P2]] : !cir.ptr, !cir.ptr> +// CIR-NEXT: %[[NULLPTR3:.*]] = cir.const #cir.ptr : !cir.ptr +// CIR-NEXT: cir.store %[[NULLPTR3]], %[[P3]] : !cir.ptr, !cir.ptr> +// CIR-NEXT: cir.return +// CIR-NEXT: } + +// LLVM: define{{.*}} @_Z2t1v() +// LLVM-NEXT: %[[P1:.*]] = alloca ptr, i64 1, align 8 +// LLVM-NEXT: %[[P2:.*]] = alloca ptr, i64 1, align 8 +// LLVM-NEXT: %[[P3:.*]] = alloca ptr, i64 1, align 8 +// LLVM-NEXT: store ptr null, ptr %[[P1]], align 8 +// LLVM-NEXT: store ptr null, ptr %[[P2]], align 8 +// LLVM-NEXT: store ptr null, ptr %[[P3]], align 8 +// LLVM-NEXT: ret void +// LLVM-NEXT: } + +// Verify that we're capturing side effects during null pointer initialization. +int t2() { + int x = 0; + int *p = (x = 1, nullptr); + return x; +} + +// Note: An extra null pointer constant gets emitted as a result of visiting the +// compound initialization expression. We could avoid this by capturing +// the result of the compound initialization expression and explicitly +// casting it to the required type, but a redundant constant seems less +// intrusive than a redundant bitcast. + +// CIR: cir.func @_Z2t2v() +// CIR-NEXT: %[[RETVAL_ADDR:.*]] = cir.alloca !s32i, !cir.ptr, ["__retval"] {alignment = 4 : i64} +// CIR-NEXT: %[[X:.*]] = cir.alloca !s32i, !cir.ptr, ["x", init] {alignment = 4 : i64} +// CIR-NEXT: %[[P:.*]] = cir.alloca !cir.ptr, !cir.ptr>, ["p", init] {alignment = 8 : i64} +// CIR-NEXT: %[[ZERO:.*]] = cir.const #cir.int<0> : !s32i +// CIR-NEXT: cir.store %[[ZERO]], %[[X]] : !s32i, !cir.ptr +// CIR-NEXT: %[[ONE:.*]] = cir.const #cir.int<1> : !s32i +// CIR-NEXT: cir.store %[[ONE]], %[[X]] : !s32i, !cir.ptr +// CIR-NEXT: %[[NULLPTR_EXTRA:.*]] = cir.const #cir.ptr : !cir.ptr +// CIR-NEXT: %[[NULLPTR:.*]] = cir.const #cir.ptr : !cir.ptr +// CIR-NEXT: cir.store %[[NULLPTR]], %[[P]] : !cir.ptr, !cir.ptr> +// CIR-NEXT: %[[X_VAL:.*]] = cir.load %[[X]] : !cir.ptr, !s32i +// CIR-NEXT: cir.store %[[X_VAL]], %[[RETVAL_ADDR]] : !s32i, !cir.ptr +// CIR-NEXT: %[[RETVAL:.*]] = cir.load %[[RETVAL_ADDR]] : !cir.ptr, !s32i +// CIR-NEXT: cir.return %[[RETVAL]] : !s32i +// CIR-NEXT: } + +// LLVM: define{{.*}} @_Z2t2v() +// LLVM-NEXT: %[[RETVAL_ADDR:.*]] = alloca i32, i64 1, align 4 +// LLVM-NEXT: %[[X:.*]] = alloca i32, i64 1, align 4 +// LLVM-NEXT: %[[P:.*]] = alloca ptr, i64 1, align 8 +// LLVM-NEXT: store i32 0, ptr %[[X]], align 4 +// LLVM-NEXT: store i32 1, ptr %[[X]], align 4 +// LLVM-NEXT: store ptr null, ptr %[[P]], align 8 +// LLVM-NEXT: %[[X_VAL:.*]] = load i32, ptr %[[X]], align 4 +// LLVM-NEXT: store i32 %[[X_VAL]], ptr %[[RETVAL_ADDR]], align 4 +// LLVM-NEXT: %[[RETVAL:.*]] = load i32, ptr %[[RETVAL_ADDR]], align 4 +// LLVM-NEXT: ret i32 %[[RETVAL]] +// LLVM-NEXT: }