[clang] 8fea791 - [CIR] Fix __builtin_clz/__builtin_ctz poison_zero to respect target (#192865)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Apr 22 03:02:49 PDT 2026
Author: Henrich Lauko
Date: 2026-04-22T12:02:45+02:00
New Revision: 8fea7910b0b43396aa5b12261ad3404609b03c26
URL: https://github.com/llvm/llvm-project/commit/8fea7910b0b43396aa5b12261ad3404609b03c26
DIFF: https://github.com/llvm/llvm-project/commit/8fea7910b0b43396aa5b12261ad3404609b03c26.diff
LOG: [CIR] Fix __builtin_clz/__builtin_ctz poison_zero to respect target (#192865)
CIR was hardcoding poisonZero=true for all clz/ctz builtins, ignoring
the target's isCLZForZeroUndef(). This caused incorrect UB on targets
like AArch64 where clz/ctz of zero is well-defined.
Also add support for __builtin_c[lt]zg fallback (2-arg) variants with
compare+select, and add NYI stubs for elementwise variants.
Added:
clang/test/CIR/CodeGenBuiltins/builtin-bit-clz-ctz-target.cpp
Modified:
clang/include/clang/CIR/MissingFeatures.h
clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
Removed:
################################################################################
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 92d052de4e8db..634d4dd83b3f8 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -220,6 +220,7 @@ struct MissingFeatures {
static bool builtinCall() { return false; }
static bool builtinCallF128() { return false; }
static bool builtinCallMathErrno() { return false; }
+ static bool builtinBitCountExpr() { return false; }
static bool builtinCheckKind() { return false; }
static bool cgCapturedStmtInfo() { return false; }
static bool countedBySize() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
index 7a0ff82483009..1f03b1da0bbef 100644
--- a/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenBuiltin.cpp
@@ -39,27 +39,51 @@ static RValue emitLibraryCall(CIRGenFunction &cgf, const FunctionDecl *fd,
return cgf.emitCall(e->getCallee()->getType(), callee, e, ReturnValueSlot());
}
-template <typename Op>
-static RValue emitBuiltinBitOp(CIRGenFunction &cgf, const CallExpr *e,
- bool poisonZero = false) {
- assert(!cir::MissingFeatures::builtinCheckKind());
+template <typename Op, typename... Args>
+static mlir::Value createBuiltinBitOp(CIRGenFunction &cgf, const CallExpr *e,
+ mlir::Value arg, Args... args) {
+ CIRGenBuilderTy &builder = cgf.getBuilder();
+ mlir::Location loc = cgf.getLoc(e->getSourceRange());
+ auto op = Op::create(builder, loc, arg, args...);
+ mlir::Value result = op.getResult();
+ mlir::Type resultTy = cgf.convertType(e->getType());
+ if (resultTy != result.getType())
+ result = builder.createIntCast(result, resultTy);
+ return result;
+}
+template <typename Op, typename... Args>
+static RValue emitBuiltinBitOp(CIRGenFunction &cgf, const CallExpr *e,
+ Args... args) {
mlir::Value arg = cgf.emitScalarExpr(e->getArg(0));
- CIRGenBuilderTy &builder = cgf.getBuilder();
+ return RValue::get(createBuiltinBitOp<Op>(cgf, e, arg, args...));
+}
- Op op;
- if constexpr (std::is_same_v<Op, cir::BitClzOp> ||
- std::is_same_v<Op, cir::BitCtzOp>)
- op = Op::create(builder, cgf.getLoc(e->getSourceRange()), arg, poisonZero);
- else
- op = Op::create(builder, cgf.getLoc(e->getSourceRange()), arg);
+/// Emit a clz/ctz bit op with optional fallback for __builtin_c[lt]zg.
+/// When a fallback is present, the result is the fallback value if the input is
+/// zero, otherwise the bit count.
+template <typename Op>
+static RValue emitBuiltinBitOpWithFallback(CIRGenFunction &cgf,
+ const CallExpr *e) {
+ bool hasFallback = e->getNumArgs() > 1;
+ bool poisonZero = hasFallback || cgf.getTarget().isCLZForZeroUndef();
- mlir::Value result = op.getResult();
- mlir::Type exprTy = cgf.convertType(e->getType());
- if (exprTy != result.getType())
- result = builder.createIntCast(result, exprTy);
+ if (!hasFallback) {
+ assert(!cir::MissingFeatures::builtinCheckKind());
+ return emitBuiltinBitOp<Op>(cgf, e, poisonZero);
+ }
- return RValue::get(result);
+ assert(!cir::MissingFeatures::builtinBitCountExpr());
+ mlir::Value arg = cgf.emitScalarExpr(e->getArg(0));
+ mlir::Value result = createBuiltinBitOp<Op>(cgf, e, arg, poisonZero);
+
+ CIRGenBuilderTy &builder = cgf.getBuilder();
+ mlir::Location loc = cgf.getLoc(e->getSourceRange());
+ mlir::Value zero = builder.getNullValue(arg.getType(), loc);
+ mlir::Value isZero =
+ builder.createCompare(loc, cir::CmpOpKind::eq, arg, zero);
+ mlir::Value fallbackValue = cgf.emitScalarExpr(e->getArg(1));
+ return RValue::get(builder.createSelect(loc, isZero, fallbackValue, result));
}
/// Emit the conversions required to turn the given value into an
@@ -1116,17 +1140,28 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
case Builtin::BI__builtin_ctz:
case Builtin::BI__builtin_ctzl:
case Builtin::BI__builtin_ctzll:
- case Builtin::BI__builtin_ctzg:
assert(!cir::MissingFeatures::builtinCheckKind());
- return emitBuiltinBitOp<cir::BitCtzOp>(*this, e, /*poisonZero=*/true);
+ return emitBuiltinBitOp<cir::BitCtzOp>(*this, e,
+ getTarget().isCLZForZeroUndef());
+ case Builtin::BI__builtin_ctzg:
+ return emitBuiltinBitOpWithFallback<cir::BitCtzOp>(*this, e);
case Builtin::BI__builtin_clzs:
case Builtin::BI__builtin_clz:
case Builtin::BI__builtin_clzl:
case Builtin::BI__builtin_clzll:
- case Builtin::BI__builtin_clzg:
assert(!cir::MissingFeatures::builtinCheckKind());
- return emitBuiltinBitOp<cir::BitClzOp>(*this, e, /*poisonZero=*/true);
+ return emitBuiltinBitOp<cir::BitClzOp>(*this, e,
+ getTarget().isCLZForZeroUndef());
+ case Builtin::BI__builtin_clzg:
+ return emitBuiltinBitOpWithFallback<cir::BitClzOp>(*this, e);
+
+ case Builtin::BI__builtin_elementwise_ctzg:
+ cgm.errorNYI(e->getSourceRange(), "__builtin_elementwise_ctzg");
+ return RValue::get(nullptr);
+ case Builtin::BI__builtin_elementwise_clzg:
+ cgm.errorNYI(e->getSourceRange(), "__builtin_elementwise_clzg");
+ return RValue::get(nullptr);
case Builtin::BI__builtin_ffs:
case Builtin::BI__builtin_ffsl:
@@ -1141,8 +1176,7 @@ RValue CIRGenFunction::emitBuiltinExpr(const GlobalDecl &gd, unsigned builtinID,
case Builtin::BI__lzcnt16:
case Builtin::BI__lzcnt:
case Builtin::BI__lzcnt64:
- assert(!cir::MissingFeatures::builtinCheckKind());
- return emitBuiltinBitOp<cir::BitClzOp>(*this, e, /*poisonZero=*/false);
+ return emitBuiltinBitOp<cir::BitClzOp>(*this, e);
case Builtin::BI__popcnt16:
case Builtin::BI__popcnt:
diff --git a/clang/test/CIR/CodeGenBuiltins/builtin-bit-clz-ctz-target.cpp b/clang/test/CIR/CodeGenBuiltins/builtin-bit-clz-ctz-target.cpp
new file mode 100644
index 0000000000000..fdd048dd8c46d
--- /dev/null
+++ b/clang/test/CIR/CodeGenBuiltins/builtin-bit-clz-ctz-target.cpp
@@ -0,0 +1,104 @@
+// Tests that __builtin_clz/__builtin_ctz respect isCLZForZeroUndef() per target.
+// x86 returns true (default), AArch64 returns false.
+
+// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=AARCH64-CIR
+// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=AARCH64-LLVM
+// RUN: %clang_cc1 -triple aarch64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=AARCH64-LLVM
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=X86-CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=X86-LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=X86-LLVM
+
+int test_builtin_ctz(unsigned x) {
+ return __builtin_ctz(x);
+}
+
+// AARCH64-CIR-LABEL: _Z16test_builtin_ctzj
+// AARCH64-CIR: cir.ctz %{{.+}} : !u32i
+// AARCH64-CIR-NOT: poison_zero
+
+// AARCH64-LLVM-LABEL: _Z16test_builtin_ctzj
+// AARCH64-LLVM: %{{.+}} = call i32 @llvm.cttz.i32(i32 %{{.+}}, i1 false)
+
+// X86-CIR-LABEL: _Z16test_builtin_ctzj
+// X86-CIR: cir.ctz %{{.+}} poison_zero : !u32i
+
+// X86-LLVM-LABEL: _Z16test_builtin_ctzj
+// X86-LLVM: %{{.+}} = call i32 @llvm.cttz.i32(i32 %{{.+}}, i1 true)
+
+int test_builtin_clz(unsigned x) {
+ return __builtin_clz(x);
+}
+
+// AARCH64-CIR-LABEL: _Z16test_builtin_clzj
+// AARCH64-CIR: cir.clz %{{.+}} : !u32i
+// AARCH64-CIR-NOT: poison_zero
+
+// AARCH64-LLVM-LABEL: _Z16test_builtin_clzj
+// AARCH64-LLVM: %{{.+}} = call i32 @llvm.ctlz.i32(i32 %{{.+}}, i1 false)
+
+// X86-CIR-LABEL: _Z16test_builtin_clzj
+// X86-CIR: cir.clz %{{.+}} poison_zero : !u32i
+
+// X86-LLVM-LABEL: _Z16test_builtin_clzj
+// X86-LLVM: %{{.+}} = call i32 @llvm.ctlz.i32(i32 %{{.+}}, i1 true)
+
+int test_builtin_ctzg_fallback(unsigned x, int fb) {
+ return __builtin_ctzg(x, fb);
+}
+
+// On both targets, the fallback case always uses poison_zero=true.
+
+// AARCH64-CIR-LABEL: _Z26test_builtin_ctzg_fallbackji
+// AARCH64-CIR: %[[CTZ:.+]] = cir.ctz %{{.+}} poison_zero : !u32i
+// AARCH64-CIR: %[[ZERO:.+]] = cir.const #cir.int<0>
+// AARCH64-CIR: %[[ISZERO:.+]] = cir.cmp eq %{{.+}}, %[[ZERO]] : !u32i
+// AARCH64-CIR: cir.select if %[[ISZERO]]
+
+// AARCH64-LLVM-LABEL: _Z26test_builtin_ctzg_fallbackji
+// AARCH64-LLVM: %[[CTZ:.+]] = call i32 @llvm.cttz.i32(i32 %{{.+}}, i1 true)
+// AARCH64-LLVM: %[[ISZERO:.+]] = icmp eq i32 %{{.+}}, 0
+// AARCH64-LLVM: select i1 %[[ISZERO]], i32 %{{.+}}, i32 %[[CTZ]]
+
+// X86-CIR-LABEL: _Z26test_builtin_ctzg_fallbackji
+// X86-CIR: %[[CTZ:.+]] = cir.ctz %{{.+}} poison_zero : !u32i
+// X86-CIR: %[[ZERO:.+]] = cir.const #cir.int<0>
+// X86-CIR: %[[ISZERO:.+]] = cir.cmp eq %{{.+}}, %[[ZERO]] : !u32i
+// X86-CIR: cir.select if %[[ISZERO]]
+
+// X86-LLVM-LABEL: _Z26test_builtin_ctzg_fallbackji
+// X86-LLVM: %[[CTZ:.+]] = call i32 @llvm.cttz.i32(i32 %{{.+}}, i1 true)
+// X86-LLVM: %[[ISZERO:.+]] = icmp eq i32 %{{.+}}, 0
+// X86-LLVM: select i1 %[[ISZERO]], i32 %{{.+}}, i32 %[[CTZ]]
+
+int test_builtin_clzg_fallback(unsigned x, int fb) {
+ return __builtin_clzg(x, fb);
+}
+
+// AARCH64-CIR-LABEL: _Z26test_builtin_clzg_fallbackji
+// AARCH64-CIR: %[[CLZ:.+]] = cir.clz %{{.+}} poison_zero : !u32i
+// AARCH64-CIR: %[[ZERO:.+]] = cir.const #cir.int<0>
+// AARCH64-CIR: %[[ISZERO:.+]] = cir.cmp eq %{{.+}}, %[[ZERO]] : !u32i
+// AARCH64-CIR: cir.select if %[[ISZERO]]
+
+// AARCH64-LLVM-LABEL: _Z26test_builtin_clzg_fallbackji
+// AARCH64-LLVM: %[[CLZ:.+]] = call i32 @llvm.ctlz.i32(i32 %{{.+}}, i1 true)
+// AARCH64-LLVM: %[[ISZERO:.+]] = icmp eq i32 %{{.+}}, 0
+// AARCH64-LLVM: select i1 %[[ISZERO]], i32 %{{.+}}, i32 %[[CLZ]]
+
+// X86-CIR-LABEL: _Z26test_builtin_clzg_fallbackji
+// X86-CIR: %[[CLZ:.+]] = cir.clz %{{.+}} poison_zero : !u32i
+// X86-CIR: %[[ZERO:.+]] = cir.const #cir.int<0>
+// X86-CIR: %[[ISZERO:.+]] = cir.cmp eq %{{.+}}, %[[ZERO]] : !u32i
+// X86-CIR: cir.select if %[[ISZERO]]
+
+// X86-LLVM-LABEL: _Z26test_builtin_clzg_fallbackji
+// X86-LLVM: %[[CLZ:.+]] = call i32 @llvm.ctlz.i32(i32 %{{.+}}, i1 true)
+// X86-LLVM: %[[ISZERO:.+]] = icmp eq i32 %{{.+}}, 0
+// X86-LLVM: select i1 %[[ISZERO]], i32 %{{.+}}, i32 %[[CLZ]]
More information about the cfe-commits
mailing list