[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