https://github.com/SharmaRithik updated 
https://github.com/llvm/llvm-project/pull/210400

>From 2223da484f59b6774c00e654a6c0fba22824a4dd Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Thu, 2 Jul 2026 00:02:36 +0000
Subject: [PATCH 1/2] [CIR] Recognize strlen in the IdiomRecognizer pass

This patch adds the operation cir.std.strlen and teaches the recognizer
to raise strlen calls, following the ClangIR incubator. A C library
function carries no identity tag, so strlen is matched by its callee
symbol, which works since C names have no mangling.

The symbol alone is not enough when builtins are disabled, so the
recognizer honors the no builtin state CIRGen records, the mark on the
call and the list on the calling function. A call is raised only when it
is a direct call named strlen with one pointer to an 8 bit character of
either signedness, since the signedness of plain char follows the
target, and a fundamental unsigned integer result. A _BitInt is excluded
even at width 8.

The raised operation lowers back through the same generic function as
the other raised operations, and every call attribute is carried across,
so a no builtin mark or list survives the round trip. Tests cover the
raise, the symbol match, the no builtin cases, and the type guard.

Aided by Claude Opus 4.8
---
 .../include/clang/CIR/Dialect/IR/CIRStdOps.td |  14 +-
 .../CIR/Dialect/IR/CIRTypeConstraints.td      |   8 +
 .../Dialect/Transforms/IdiomRecognizer.cpp    |  81 +++++++--
 .../Dialect/Transforms/LoweringPrepare.cpp    |   5 +-
 clang/test/CIR/IR/std-ops-invalid.cir         |  51 ++++++
 clang/test/CIR/IR/std-ops.cir                 |  25 +++
 .../idiom-recognizer-strlen-guards.cir        | 172 ++++++++++++++++++
 .../test/CIR/Transforms/idiom-recognizer.cpp  |  27 +++
 8 files changed, 365 insertions(+), 18 deletions(-)
 create mode 100644 clang/test/CIR/IR/std-ops-invalid.cir
 create mode 100644 clang/test/CIR/IR/std-ops.cir
 create mode 100644 clang/test/CIR/Transforms/idiom-recognizer-strlen-guards.cir

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td 
b/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td
index 608a958e5117c..f0a47045c5d61 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td
@@ -14,8 +14,8 @@
 #ifndef CLANG_CIR_DIALECT_IR_CIRSTDOPS_TD
 #define CLANG_CIR_DIALECT_IR_CIRSTDOPS_TD
 
-// knownKind names the CIR_KnownFuncKind entry whose tagged calls raise
-// to this operation.
+// knownKind names the CIR_KnownFuncKind entry this operation raises, or is
+// empty for a C library function that has no such entry.
 class CIR_StdOp<string functionName, dag args, dag res,
                 list<Trait> traits = [], string knownKind = "">
     : CIR_Op<"std." # functionName, traits> {
@@ -50,4 +50,14 @@ def CIR_StdFindOp : CIR_StdOp<"find",
   }];
 }
 
+def CIR_StrLenOp : CIR_StdOp<"strlen",
+  (ins CIR_PtrToType<CIR_AnyChar8Type>:$string),
+  (outs CIR_AnyFundamentalUIntType:$result)> {
+  let summary = "C standard library strlen()";
+  let assemblyFormat = [{
+    `(` $string `:` qualified(type($string)) `,` $original_fn `)`
+    `->` qualified(type($result)) attr-dict
+  }];
+}
+
 #endif // CLANG_CIR_DIALECT_IR_CIRSTDOPS_TD
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td 
b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
index a8643ec21af80..668ac5b41a905 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
@@ -125,6 +125,14 @@ def CIR_AnyFundamentalIntType
     let cppFunctionName = "isFundamentalIntType";
 }
 
+// An 8 bit character type of either signedness, since the signedness of plain
+// char follows the target. _BitInt types are excluded even at width 8.
+def CIR_AnyChar8Type
+    : CIR_ConfinedType<CIR_AnyIntType,
+        [CIR_HasWidthPred<8>, CIR_IsNotBitIntPred], "8-bit character type"> {
+    let cppFunctionName = "isChar8Type";
+}
+
 def CIR_AnyFundamentalUIntType
     : CIR_ConfinedType<CIR_AnyUIntType,
         [CIR_HasFundamentalIntWidthPred, CIR_IsNotBitIntPred],
diff --git a/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp 
b/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp
index 030a6bed68b99..843d0758c9224 100644
--- a/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp
@@ -45,9 +45,41 @@ template <> bool signatureMatches<StdFindOp>(CallOp call) {
          iterTy == call->getResult(0).getType();
 }
 
-// Raises a direct cir.call to `TargetOp` when the callee carries the
-// matching identity tag.
-template <typename TargetOp> class StdRecognizer {
+// strlen takes a pointer to an 8 bit character and returns size_t, an unsigned
+// fundamental integer. A _BitInt is not a character type even at width 8.
+template <> bool signatureMatches<StrLenOp>(CallOp call) {
+  if (call.getNumOperands() != StrLenOp::getNumArgs() ||
+      call->getNumResults() != 1)
+    return false;
+  auto ptrTy = mlir::dyn_cast<cir::PointerType>(call.getOperand(0).getType());
+  return ptrTy && cir::isChar8Type(ptrTy.getPointee()) &&
+         cir::isFundamentalUIntType(call->getResult(0).getType());
+}
+
+// Returns true when the recorded no builtin state forbids treating the call
+// as the C library function `name`. The call carries a nobuiltin mark and
+// the caller a nobuiltins list, where an empty list disables them all.
+bool isNoBuiltin(CallOp call, llvm::StringRef name) {
+  if (call->hasAttr(cir::CIRDialect::getNoBuiltinAttrName()))
+    return true;
+
+  auto enclosing = call->getParentOfType<cir::FuncOp>();
+  auto noBuiltins = enclosing ? enclosing->getAttrOfType<mlir::ArrayAttr>(
+                                    cir::CIRDialect::getNoBuiltinsAttrName())
+                              : nullptr;
+  if (!noBuiltins)
+    return false;
+  return noBuiltins.empty() ||
+         llvm::any_of(noBuiltins, [name](mlir::Attribute entry) {
+           auto builtinName = mlir::dyn_cast<mlir::StringAttr>(entry);
+           return builtinName && builtinName.getValue() == name;
+         });
+}
+
+// Raises a direct cir.call to `TargetOp`. C++ entities are matched through
+// the identity tag on the callee, and C library functions by callee symbol,
+// since C names have no mangling.
+template <typename TargetOp, bool MatchByTag = true> class StdRecognizer {
   template <size_t... Indices>
   static TargetOp buildCall(cir::CIRBaseBuilderTy &builder, CallOp call,
                             std::index_sequence<Indices...>) {
@@ -64,16 +96,32 @@ template <typename TargetOp> class StdRecognizer {
         !signatureMatches<TargetOp>(call))
       return false;
 
-    // Only a free std function with the right name carries the tag, so
-    // members, static members, and operators never match. The shape of the
-    // call is checked here, so a variadic callee never matches.
-    cir::FuncOp callee = call.resolveCalleeInTable(symbolTables);
-    if (!callee || callee.getFunctionType().isVarArg())
-      return false;
-    auto funcIdentity = mlir::dyn_cast_if_present<cir::FuncIdentityAttr>(
-        callee.getFuncInfoAttr());
-    if (!funcIdentity || funcIdentity.getKind() != TargetOp::getFuncKind())
-      return false;
+    if constexpr (MatchByTag) {
+      // Only a free std function with the right name carries the tag, so
+      // members, static members, and operators never match. The shape of the
+      // call is checked here, so a variadic callee never matches.
+      cir::FuncOp callee = call.resolveCalleeInTable(symbolTables);
+      if (!callee || callee.getFunctionType().isVarArg())
+        return false;
+      auto funcIdentity = mlir::dyn_cast_if_present<cir::FuncIdentityAttr>(
+          callee.getFuncInfoAttr());
+      if (!funcIdentity || funcIdentity.getKind() != TargetOp::getFuncKind())
+        return false;
+    } else {
+      // A C library function has no identity tag, so it is matched by callee
+      // symbol, which works because C names are unmangled. The symbol alone is
+      // not enough when builtins are disabled, so the recorded no builtin 
state
+      // gates the match.
+      if (*call.getCallee() != TargetOp::getFunctionName() ||
+          isNoBuiltin(call, TargetOp::getFunctionName()))
+        return false;
+      // The library function is not variadic, so a variadic callee that only
+      // shares the name is not that function. This lookup runs only after the
+      // name matches.
+      cir::FuncOp callee = call.resolveCalleeInTable(symbolTables);
+      if (callee && callee.getFunctionType().isVarArg())
+        return false;
+    }
 
     cir::CIRBaseBuilderTy builder(context);
     builder.setInsertionPointAfter(call.getOperation());
@@ -82,7 +130,7 @@ template <typename TargetOp> class StdRecognizer {
     // The raised operation keeps every call attribute except the callee,
     // which it carries as original_fn, so lowering back loses nothing.
     for (mlir::NamedAttribute attr : call->getAttrs())
-      if (attr.getName() != "callee")
+      if (attr.getName() != call.getCalleeAttrName())
         op->setAttr(attr.getName(), attr.getValue());
     call.replaceAllUsesWith(op);
     call.erase();
@@ -103,7 +151,10 @@ struct IdiomRecognizerPass
 
 void IdiomRecognizerPass::recognizeStandardLibraryCall(
     CallOp call, mlir::SymbolTableCollection &symbolTables) {
-  StdRecognizer<StdFindOp>::raise(call, getContext(), symbolTables);
+  if (StdRecognizer<StdFindOp>::raise(call, getContext(), symbolTables))
+    return;
+  StdRecognizer<StrLenOp, /*MatchByTag=*/false>::raise(call, getContext(),
+                                                       symbolTables);
 }
 
 void IdiomRecognizerPass::runOnOperation() {
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp 
b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index 4c2369f27ef7f..c163cdaef887e 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -2280,6 +2280,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) {
     lowerArrayDtor(arrayDtor);
   } else if (auto findOp = mlir::dyn_cast<cir::StdFindOp>(op)) {
     lowerStdOp(findOp);
+  } else if (auto strlenOp = mlir::dyn_cast<cir::StrLenOp>(op)) {
+    lowerStdOp(strlenOp);
   } else if (auto cast = mlir::dyn_cast<cir::CastOp>(op)) {
     lowerCastOp(cast);
   } else if (auto complexConj = mlir::dyn_cast<cir::ComplexConjOp>(op)) {
@@ -2918,7 +2920,8 @@ void LoweringPreparePass::runOnOperation() {
                   cir::ComplexConjOp, cir::ComplexMulOp, cir::ComplexDivOp,
                   cir::DynamicCastOp, cir::FuncOp, cir::CallOp,
                   cir::GetGlobalOp, cir::GlobalOp, cir::StoreOp,
-                  cir::CmpThreeWayOp, cir::LocalInitOp, cir::StdFindOp>(op))
+                  cir::CmpThreeWayOp, cir::LocalInitOp, cir::StdFindOp,
+                  cir::StrLenOp>(op))
       opsToTransform.push_back(op);
   });
 
diff --git a/clang/test/CIR/IR/std-ops-invalid.cir 
b/clang/test/CIR/IR/std-ops-invalid.cir
new file mode 100644
index 0000000000000..37e371ee72472
--- /dev/null
+++ b/clang/test/CIR/IR/std-ops-invalid.cir
@@ -0,0 +1,51 @@
+// RUN: cir-opt %s -verify-diagnostics -split-input-file
+
+!s8i = !cir.int<s, 8>
+!s32i = !cir.int<s, 32>
+
+module {
+  cir.func @bad_find(%f: !cir.ptr<!s8i>, %l: !cir.ptr<!s8i>, %v: 
!cir.ptr<!s8i>) -> !s32i {
+    // expected-error@below {{'cir.std.find' op failed to verify that all of 
{first, last, result} have same type}}
+    %0 = cir.std.find(%f : !cir.ptr<!s8i>, %l : !cir.ptr<!s8i>, %v : 
!cir.ptr<!s8i>, @x) -> !s32i
+    cir.return %0 : !s32i
+  }
+}
+
+// -----
+
+!s32i = !cir.int<s, 32>
+!u64i = !cir.int<u, 64>
+
+module {
+  cir.func @bad_strlen(%s: !cir.ptr<!s32i>) -> !u64i {
+    // expected-error@below {{'cir.std.strlen' op operand #0 must be pointer 
to 8-bit character type}}
+    %0 = cir.std.strlen(%s : !cir.ptr<!s32i>, @strlen) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+
+// -----
+
+!bi8 = !cir.int<s, 8, bitint>
+!u64i = !cir.int<u, 64>
+
+module {
+  cir.func @bad_strlen_bitint_arg(%s: !cir.ptr<!bi8>) -> !u64i {
+    // expected-error@below {{'cir.std.strlen' op operand #0 must be pointer 
to 8-bit character type}}
+    %0 = cir.std.strlen(%s : !cir.ptr<!bi8>, @strlen) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+
+// -----
+
+!s8i = !cir.int<s, 8>
+!s64i = !cir.int<s, 64>
+
+module {
+  cir.func @bad_strlen_signed_result(%s: !cir.ptr<!s8i>) -> !s64i {
+    // expected-error@below {{'cir.std.strlen' op result #0 must be 
fundamental unsigned integer type}}
+    %0 = cir.std.strlen(%s : !cir.ptr<!s8i>, @strlen) -> !s64i
+    cir.return %0 : !s64i
+  }
+}
diff --git a/clang/test/CIR/IR/std-ops.cir b/clang/test/CIR/IR/std-ops.cir
new file mode 100644
index 0000000000000..3fde07e3b979a
--- /dev/null
+++ b/clang/test/CIR/IR/std-ops.cir
@@ -0,0 +1,25 @@
+// RUN: cir-opt %s | FileCheck %s
+
+// Round-trip parse and print of the raised standard library ops.
+
+!s8i = !cir.int<s, 8>
+!u64i = !cir.int<u, 64>
+
+module {
+  cir.func private @_ZSt4find(!cir.ptr<!s8i>, !cir.ptr<!s8i>, !cir.ptr<!s8i>) 
-> !cir.ptr<!s8i>
+  cir.func private @strlen(!cir.ptr<!s8i>) -> !u64i
+
+  cir.func @test_find(%first: !cir.ptr<!s8i>, %last: !cir.ptr<!s8i>, %value: 
!cir.ptr<!s8i>) -> !cir.ptr<!s8i> {
+    %0 = cir.std.find(%first : !cir.ptr<!s8i>, %last : !cir.ptr<!s8i>, %value 
: !cir.ptr<!s8i>, @_ZSt4find) -> !cir.ptr<!s8i>
+    cir.return %0 : !cir.ptr<!s8i>
+  }
+  // CHECK: cir.func @test_find(%[[FIRST:.*]]: !cir.ptr<!s8i>, %[[LAST:.*]]: 
!cir.ptr<!s8i>, %[[VALUE:.*]]: !cir.ptr<!s8i>)
+  // CHECK: cir.std.find(%[[FIRST]] : !cir.ptr<!s8i>, %[[LAST]] : 
!cir.ptr<!s8i>, %[[VALUE]] : !cir.ptr<!s8i>, @_ZSt4find) -> !cir.ptr<!s8i>
+
+  cir.func @test_strlen(%s: !cir.ptr<!s8i>) -> !u64i {
+    %0 = cir.std.strlen(%s : !cir.ptr<!s8i>, @strlen) -> !u64i
+    cir.return %0 : !u64i
+  }
+  // CHECK: cir.func @test_strlen(%[[S:.*]]: !cir.ptr<!s8i>)
+  // CHECK: cir.std.strlen(%[[S]] : !cir.ptr<!s8i>, @strlen) -> !u64i
+}
diff --git a/clang/test/CIR/Transforms/idiom-recognizer-strlen-guards.cir 
b/clang/test/CIR/Transforms/idiom-recognizer-strlen-guards.cir
new file mode 100644
index 0000000000000..b1f75a9b3c3f1
--- /dev/null
+++ b/clang/test/CIR/Transforms/idiom-recognizer-strlen-guards.cir
@@ -0,0 +1,172 @@
+// RUN: cir-opt %s --cir-idiom-recognizer -split-input-file | FileCheck %s
+
+// Negative coverage for the strlen recognizer, exercised directly on CIR so
+// each conflicting strlen signature lives in its own module without a macro.
+// The recognizer raises only a direct, non-variadic call named strlen that
+// takes one pointer to an 8 bit character, returns a fundamental unsigned
+// integer, and has no builtin state blocking it. The first module is a
+// positive control proving the pass runs. Every later module keeps its plain
+// cir.call.
+
+!s8i = !cir.int<s, 8>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s8i>) -> !u64i
+  cir.func @positive(%s: !cir.ptr<!s8i>) -> !u64i {
+    %0 = cir.call @strlen(%s) : (!cir.ptr<!s8i>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @positive
+// CHECK: cir.std.strlen(%{{.*}} : !cir.ptr<!s8i>, @strlen) -> !u64i
+
+// -----
+// A function with strlen's signature but a different name is matched by 
symbol,
+// so it is not raised.
+!s8i = !cir.int<s, 8>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @mylen(!cir.ptr<!s8i>) -> !u64i
+  cir.func @wrong_name(%s: !cir.ptr<!s8i>) -> !u64i {
+    %0 = cir.call @mylen(%s) : (!cir.ptr<!s8i>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @wrong_name
+// CHECK: cir.call @mylen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// The library strlen is not variadic, so a variadic callee that shares the 
name
+// is not the library strlen.
+!s8i = !cir.int<s, 8>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s8i>, ...) -> !u64i
+  cir.func @variadic(%s: !cir.ptr<!s8i>) -> !u64i {
+    %0 = cir.call @strlen(%s) : (!cir.ptr<!s8i>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @variadic
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// A call with no result has nothing to raise, so it is not matched.
+!s8i = !cir.int<s, 8>
+module {
+  cir.func private @strlen(!cir.ptr<!s8i>)
+  cir.func @void_result(%s: !cir.ptr<!s8i>) {
+    cir.call @strlen(%s) : (!cir.ptr<!s8i>) -> ()
+    cir.return
+  }
+}
+// CHECK-LABEL: cir.func @void_result
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// A nullary call has no argument to inspect, so it is not matched.
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen() -> !u64i
+  cir.func @nullary() -> !u64i {
+    %0 = cir.call @strlen() : () -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @nullary
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// The pointer must be to an 8 bit character, so a wider integer does not 
match.
+!s32i = !cir.int<s, 32>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s32i>) -> !u64i
+  cir.func @bad_arg_type(%s: !cir.ptr<!s32i>) -> !u64i {
+    %0 = cir.call @strlen(%s) : (!cir.ptr<!s32i>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @bad_arg_type
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// A _BitInt is not a character type even at width 8.
+!s8bitint = !cir.int<s, 8, bitint>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s8bitint>) -> !u64i
+  cir.func @bad_arg_bitint(%s: !cir.ptr<!s8bitint>) -> !u64i {
+    %0 = cir.call @strlen(%s) : (!cir.ptr<!s8bitint>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @bad_arg_bitint
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// A nobuiltin mark on the call blocks the raise.
+!s8i = !cir.int<s, 8>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s8i>) -> !u64i
+  cir.func @nobuiltin_mark(%s: !cir.ptr<!s8i>) -> !u64i {
+    %0 = cir.call @strlen(%s) {nobuiltin} : (!cir.ptr<!s8i>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @nobuiltin_mark
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// A caller nobuiltins list naming strlen blocks the raise.
+!s8i = !cir.int<s, 8>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s8i>) -> !u64i
+  cir.func @nobuiltins_list(%s: !cir.ptr<!s8i>) -> !u64i attributes 
{nobuiltins = ["strlen"]} {
+    %0 = cir.call @strlen(%s) : (!cir.ptr<!s8i>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @nobuiltins_list
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// An empty caller nobuiltins list disables every builtin, so it also blocks 
the
+// raise.
+!s8i = !cir.int<s, 8>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s8i>) -> !u64i
+  cir.func @empty_nobuiltins(%s: !cir.ptr<!s8i>) -> !u64i attributes 
{nobuiltins = []} {
+    %0 = cir.call @strlen(%s) : (!cir.ptr<!s8i>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @empty_nobuiltins
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
+
+// -----
+// The result is size_t, which is unsigned, so a signed result is not raised.
+!s8i = !cir.int<s, 8>
+!s64i = !cir.int<s, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s8i>) -> !s64i
+  cir.func @signed_result(%s: !cir.ptr<!s8i>) -> !s64i {
+    %0 = cir.call @strlen(%s) : (!cir.ptr<!s8i>) -> !s64i
+    cir.return %0 : !s64i
+  }
+}
+// CHECK-LABEL: cir.func @signed_result
+// CHECK: cir.call @strlen
+// CHECK-NOT: cir.std.strlen
diff --git a/clang/test/CIR/Transforms/idiom-recognizer.cpp 
b/clang/test/CIR/Transforms/idiom-recognizer.cpp
index f60d10f2ae4b1..fbf2b206c1ab0 100644
--- a/clang/test/CIR/Transforms/idiom-recognizer.cpp
+++ b/clang/test/CIR/Transforms/idiom-recognizer.cpp
@@ -8,10 +8,24 @@
 // RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir 
-clangir-enable-idiom-recognizer -emit-cir -mmlir 
--mlir-print-ir-after=cir-idiom-recognizer %s -o %t.cir 2>&1 | FileCheck %s 
--check-prefix=RAISED '--implicit-check-not=cir.call @_ZSt4find'
 // RUN: FileCheck %s --check-prefix=FINAL --input-file=%t.cir 
--implicit-check-not=cir.std.
 
+// On targets where plain char is unsigned the pointer lowers to !u8i, and
+// recognition works the same.
+// RUN: %clang_cc1 -std=c++17 -triple aarch64-unknown-linux-gnu 
-fno-signed-char -fclangir -clangir-enable-idiom-recognizer -emit-cir -mmlir 
--mlir-print-ir-after=cir-idiom-recognizer %s -o %t.aarch64.cir 2>&1 | 
FileCheck %s --check-prefix=RAISED
+
+// A no builtin list for another function survives the round trip on the
+// rebuilt call.
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu 
-fno-builtin-memcpy -fclangir -clangir-enable-idiom-recognizer -emit-cir %s -o 
%t.fnb.cir
+// RUN: FileCheck %s --check-prefix=FNB --input-file=%t.fnb.cir
+
+// With builtins disabled the strlen call is left alone, while the tagged
+// std::find is unaffected and still raises.
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fno-builtin 
-fclangir -clangir-enable-idiom-recognizer -emit-cir -mmlir 
--mlir-print-ir-after=cir-idiom-recognizer %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=NB --implicit-check-not=cir.std.strlen
+
 namespace std {
 template <class Iter, class T>
 __attribute__((pure)) Iter find(Iter, Iter, const T &) noexcept;
 }
+extern "C" unsigned long strlen(const char *);
 
 char *test_find(char *first, char *last, const char &value) {
   return std::find(first, last, value);
@@ -32,6 +46,19 @@ char *test_find(char *first, char *last, const char &value) {
 // FINAL-SAME: -> (!cir.ptr<!s8i> {llvm.noundef})
 // FINAL-NOT: cir.call @_ZSt4find
 
+unsigned long test_strlen(const char *s) { return strlen(s); }
+// RAISED: cir.std.strlen(
+// RAISED-SAME: @strlen
+// RAISED-SAME: -> !u64i
+// FINAL: %[[S_ADDR:.*]] = cir.alloca "s"
+// FINAL: %[[S:.*]] = cir.load{{.*}} %[[S_ADDR]] :
+// FINAL: cir.call @strlen(%[[S]]) nothrow
+// FINAL-SAME: (!cir.ptr<!s8i> {llvm.noundef})
+// FINAL-SAME: -> !u64i
+// FNB: cir.call @strlen
+// FNB-SAME: nobuiltins = ["memcpy"]
+// NB: cir.call @strlen
+
 // A function merely named like the std one is not raised, and it survives the
 // whole pipeline as the same plain call.
 char *find(char *first, char *last, const char &value);

>From e1b7f4ef852bd74fce8d4c3056546f704b916857 Mon Sep 17 00:00:00 2001
From: SharmaRithik <[email protected]>
Date: Sat, 18 Jul 2026 01:17:57 +0000
Subject: [PATCH 2/2] [CIR] Generate the strlen signature match and merge no
 builtin onto calls

Generate the strlen signature check from its ODS operand and result
types, merge the caller no builtin list onto each call so the recognizer
reads it there, and lower raised operations through a shared interface.
---
 .../include/clang/CIR/Dialect/IR/CIRStdOps.td | 44 ++++++++++-
 .../CIR/Dialect/IR/CIRTypeConstraints.td      |  4 +
 .../clang/CIR/Interfaces/CIROpInterfaces.td   | 20 +++++
 clang/lib/CIR/CodeGen/CIRGenCall.cpp          | 64 ++++++++++-----
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 12 +++
 .../Dialect/Transforms/IdiomRecognizer.cpp    | 77 +++++++------------
 .../Dialect/Transforms/LoweringPrepare.cpp    | 15 ++--
 .../test/CIR/CodeGen/no-builtin-attr-caller.c | 25 ++++++
 clang/test/CIR/IR/std-ops.cir                 |  2 -
 .../idiom-recognizer-strlen-guards.cir        | 40 +++++-----
 .../test/CIR/Transforms/idiom-recognizer.cpp  | 21 +++--
 11 files changed, 215 insertions(+), 109 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/no-builtin-attr-caller.c

diff --git a/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td 
b/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td
index f0a47045c5d61..d95f109ffb812 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td
@@ -18,7 +18,7 @@
 // empty for a C library function that has no such entry.
 class CIR_StdOp<string functionName, dag args, dag res,
                 list<Trait> traits = [], string knownKind = "">
-    : CIR_Op<"std." # functionName, traits> {
+    : CIR_Op<"std." # functionName, traits # [CIR_StdOpInterface]> {
   let arguments = !con(args, (ins FlatSymbolRefAttr:$original_fn));
   let results = res;
   let hasLLVMLowering = false;
@@ -30,6 +30,12 @@ class CIR_StdOp<string functionName, dag args, dag res,
     static llvm::StringLiteral getFunctionName() {
       return "}] # functionName # [{";
     }
+    // True when matched by its KnownFuncKind tag, not the callee symbol.
+    static constexpr bool hasKnownFuncKind() {
+      return }] # !if(!empty(knownKind), "false", "true") # [{;
+    }
+    static bool signatureMatches(mlir::TypeRange operands,
+                                 mlir::TypeRange results);
   }] # !if(!empty(knownKind), "", [{
     static cir::KnownFuncKind getFuncKind() {
       return cir::KnownFuncKind::}] # knownKind # [{;
@@ -37,6 +43,38 @@ class CIR_StdOp<string functionName, dag args, dag res,
   }]);
 }
 
+// A C library op whose signatureMatches is generated from its type 
constraints.
+class CIR_CStdLibOp<string functionName, dag args, dag res,
+                    list<Trait> traits = []>
+    : CIR_StdOp<functionName, args, res, traits> {
+  assert !foldl(true, !listconcat(
+                          !foreach(i, !range(!size(args)),
+                                   !getdagarg<TypeConstraint>(args, i)),
+                          !foreach(i, !range(!size(res)),
+                                   !getdagarg<TypeConstraint>(res, i))),
+                acc, c, !and(acc, !ne(c.cppFunctionName, ""))),
+      "CIR_CStdLibOp operand and result types must set cppFunctionName";
+
+  let extraClassDefinition = [{
+    bool $cppClass::signatureMatches(mlir::TypeRange operands,
+                                     mlir::TypeRange results) {
+      return }] # !interleave(
+          !listconcat(
+              ["operands.size() == " # !size(args),
+               "results.size() == " # !size(res)],
+              !foreach(i, !range(!size(args)),
+                       "::cir::" #
+                       !getdagarg<TypeConstraint>(args, i).cppFunctionName #
+                       "(operands[" # i # "])"),
+              !foreach(i, !range(!size(res)),
+                       "::cir::" #
+                       !getdagarg<TypeConstraint>(res, i).cppFunctionName #
+                       "(results[" # i # "])")),
+          " && ") # [{;
+    }
+  }];
+}
+
 def CIR_StdFindOp : CIR_StdOp<"find",
   (ins CIR_AnyType:$first, CIR_AnyType:$last, CIR_AnyType:$pattern),
   (outs CIR_AnyType:$result),
@@ -50,8 +88,8 @@ def CIR_StdFindOp : CIR_StdOp<"find",
   }];
 }
 
-def CIR_StrLenOp : CIR_StdOp<"strlen",
-  (ins CIR_PtrToType<CIR_AnyChar8Type>:$string),
+def CIR_StrLenOp : CIR_CStdLibOp<"strlen",
+  (ins CIR_PtrToChar8Type:$string),
   (outs CIR_AnyFundamentalUIntType:$result)> {
   let summary = "C standard library strlen()";
   let assemblyFormat = [{
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td 
b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
index 668ac5b41a905..6ab739b1c3035 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRTypeConstraints.td
@@ -275,6 +275,10 @@ def CIR_PtrToVoidPtrType : CIR_PtrToPtrTo<CIR_AnyVoidType, 
"void type">;
 
 class CIR_PtrToType<Type type> : CIR_PtrToAnyOf<[type]>;
 
+def CIR_PtrToChar8Type : CIR_PtrToType<CIR_AnyChar8Type> {
+    let cppFunctionName = "isPtrToChar8Type";
+}
+
 // Pointer to type constraints
 def CIR_PtrToIntOrFloatType : CIR_PtrToType<CIR_AnyIntOrFloatType>;
 
diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td 
b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
index fb256c4a26c2e..3b60753b1b291 100644
--- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
@@ -258,6 +258,26 @@ let cppNamespace = "::cir" in {
     ];
   }
 
+  def CIR_StdOpInterface : OpInterface<"StdOpInterface"> {
+    let description = [{
+      Interface for raised standard library ops, exposing the original callee
+      so LoweringPrepare can rebuild the call generically.
+    }];
+
+    let methods = [
+      InterfaceMethod<"Return the original callee symbol reference.",
+        "mlir::FlatSymbolRefAttr", "getOriginalFnAttr", (ins), [{}],
+        /*defaultImplementation=*/[{
+          return $_op.getOriginalFnAttr();
+        }]>,
+      InterfaceMethod<"Return the name of the original_fn attribute.",
+        "mlir::StringAttr", "getOriginalFnAttrName", (ins), [{}],
+        /*defaultImplementation=*/[{
+          return $_op.getOriginalFnAttrName();
+        }]>,
+    ];
+  }
+
 } // namespace cir
 
 #endif // CLANG_CIR_INTERFACES_CIROPINTERFACES_TD
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index c3065c8917924..facee48aee555 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -107,40 +107,53 @@ static void 
addAttributesFromFunctionProtoType(CIRGenBuilderTy &builder,
               mlir::UnitAttr::get(builder.getContext()));
 }
 
-static void addNoBuiltinAttributes(mlir::MLIRContext &ctx,
-                                   mlir::NamedAttrList &attrs,
-                                   const LangOptions &langOpts,
-                                   const NoBuiltinAttr *nba = nullptr) {
-  // First, handle the language options passed through -fno-builtin.
-  // or, if there is a wildcard in the builtin names specified through the
-  // attribute, disable them all.
-  if (langOpts.NoBuiltin ||
-      (nba && llvm::is_contained(nba->builtinNames(), "*"))) {
-    // -fno-builtin disables them all.
-    // Empty attribute means 'all'.
+static void mergeNoBuiltinAttributes(mlir::MLIRContext &ctx,
+                                     mlir::NamedAttrList &attrs,
+                                     llvm::ArrayRef<llvm::StringRef> names,
+                                     bool disableAll) {
+  llvm::SetVector<mlir::Attribute> nbFuncs;
+
+  // Fold into any nobuiltins list already on the call. An existing empty
+  // list disables every builtin, so it wins.
+  if (auto existing =
+          attrs.getNamed(cir::CIRDialect::getNoBuiltinsAttrName())) {
+    auto arr = mlir::cast<mlir::ArrayAttr>(existing->getValue());
+    if (arr.empty())
+      disableAll = true;
+    else
+      nbFuncs.insert(arr.begin(), arr.end());
+  }
+
+  if (disableAll) {
     attrs.set(cir::CIRDialect::getNoBuiltinsAttrName(),
               mlir::ArrayAttr::get(&ctx, {}));
     return;
   }
 
-  llvm::SetVector<mlir::Attribute> nbFuncs;
   auto addNoBuiltinAttr = [&ctx, &nbFuncs](StringRef builtinName) {
     nbFuncs.insert(mlir::StringAttr::get(&ctx, builtinName));
   };
-
-  // Then, add attributes for builtins specified through -fno-builtin-<name>.
-  llvm::for_each(langOpts.NoBuiltinFuncs, addNoBuiltinAttr);
-
-  // Now, let's check the __attribute__((no_builtin("...")) attribute added to
-  // the source.
-  if (nba)
-    llvm::for_each(nba->builtinNames(), addNoBuiltinAttr);
+  llvm::for_each(names, addNoBuiltinAttr);
 
   if (!nbFuncs.empty())
     attrs.set(cir::CIRDialect::getNoBuiltinsAttrName(),
               mlir::ArrayAttr::get(&ctx, nbFuncs.getArrayRef()));
 }
 
+static void addNoBuiltinAttributes(mlir::MLIRContext &ctx,
+                                   mlir::NamedAttrList &attrs,
+                                   const LangOptions &langOpts,
+                                   const NoBuiltinAttr *nba = nullptr) {
+  llvm::SmallVector<llvm::StringRef> names;
+  llvm::append_range(names, langOpts.NoBuiltinFuncs);
+  if (nba)
+    llvm::append_range(names, nba->builtinNames());
+
+  bool disableAll = langOpts.NoBuiltin ||
+                    (nba && llvm::is_contained(nba->builtinNames(), "*"));
+  mergeNoBuiltinAttributes(ctx, attrs, names, disableAll);
+}
+
 /// Add denormal-fp-math and denormal-fp-math-f32 as appropriate for the
 /// requested denormal behavior, accounting for the overriding behavior of the
 /// -f32 case.
@@ -1286,6 +1299,17 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo 
&funcInfo,
                              attrs, argAttrs, retAttrs, callingConv, 
sideEffect,
                              /*attrOnCallSite=*/true, /*isThunk=*/false);
 
+  // A caller's no_builtin list applies to each call it makes, so merge it
+  // onto the call where later passes look for it.
+  if (const auto *caller = dyn_cast_or_null<FunctionDecl>(curFuncDecl)) {
+    if (const auto *nba = caller->getAttr<NoBuiltinAttr>()) {
+      llvm::SmallVector<llvm::StringRef> names;
+      llvm::append_range(names, nba->builtinNames());
+      mergeNoBuiltinAttributes(cgm.getMLIRContext(), attrs, names,
+                               llvm::is_contained(nba->builtinNames(), "*"));
+    }
+  }
+
   auto resolvedFuncOpFromGlobal = [&](mlir::Operation *op) -> cir::FuncOp {
     if (auto fnOp = dyn_cast<cir::FuncOp>(op))
       return fnOp;
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 60ad12b92cdb9..1f0655b52a0c9 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -4657,6 +4657,18 @@ static void printEhDispatchDestinations(OpAsmPrinter &p, 
cir::EhDispatchOp op,
   p << "]";
 }
 
+//===----------------------------------------------------------------------===//
+// Standard library op signature matching
+//===----------------------------------------------------------------------===//
+
+bool cir::StdFindOp::signatureMatches(mlir::TypeRange operands,
+                                      mlir::TypeRange results) {
+  if (operands.size() != getNumArgs() || results.size() != 1)
+    return false;
+  mlir::Type iterTy = operands[0];
+  return iterTy == operands[1] && iterTy == operands[2] && iterTy == 
results[0];
+}
+
 
//===----------------------------------------------------------------------===//
 // TableGen'd op method definitions
 
//===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp 
b/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp
index 843d0758c9224..dda1f9b6e19c1 100644
--- a/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp
@@ -30,43 +30,15 @@ namespace mlir {
 
 namespace {
 
-// A call matches when its shape fits the raised operation, the operand and
-// result counts first and then the operand types. The searched value arrives
-// by reference and must share the iterator type.
-template <typename TargetOp> bool signatureMatches(CallOp call);
-
-template <> bool signatureMatches<StdFindOp>(CallOp call) {
-  if (call.getNumOperands() != StdFindOp::getNumArgs() ||
-      call->getNumResults() != 1)
-    return false;
-  mlir::Type iterTy = call.getOperand(0).getType();
-  return iterTy == call.getOperand(1).getType() &&
-         iterTy == call.getOperand(2).getType() &&
-         iterTy == call->getResult(0).getType();
-}
-
-// strlen takes a pointer to an 8 bit character and returns size_t, an unsigned
-// fundamental integer. A _BitInt is not a character type even at width 8.
-template <> bool signatureMatches<StrLenOp>(CallOp call) {
-  if (call.getNumOperands() != StrLenOp::getNumArgs() ||
-      call->getNumResults() != 1)
-    return false;
-  auto ptrTy = mlir::dyn_cast<cir::PointerType>(call.getOperand(0).getType());
-  return ptrTy && cir::isChar8Type(ptrTy.getPointee()) &&
-         cir::isFundamentalUIntType(call->getResult(0).getType());
-}
-
-// Returns true when the recorded no builtin state forbids treating the call
-// as the C library function `name`. The call carries a nobuiltin mark and
-// the caller a nobuiltins list, where an empty list disables them all.
+// True when the call's no builtin state forbids treating it as `name`. A
+// builtin mark wins over a nobuiltin mark or a nobuiltins list.
 bool isNoBuiltin(CallOp call, llvm::StringRef name) {
+  if (call->hasAttr(cir::CIRDialect::getBuiltinAttrName()))
+    return false;
   if (call->hasAttr(cir::CIRDialect::getNoBuiltinAttrName()))
     return true;
-
-  auto enclosing = call->getParentOfType<cir::FuncOp>();
-  auto noBuiltins = enclosing ? enclosing->getAttrOfType<mlir::ArrayAttr>(
-                                    cir::CIRDialect::getNoBuiltinsAttrName())
-                              : nullptr;
+  auto noBuiltins = call->getAttrOfType<mlir::ArrayAttr>(
+      cir::CIRDialect::getNoBuiltinsAttrName());
   if (!noBuiltins)
     return false;
   return noBuiltins.empty() ||
@@ -76,11 +48,9 @@ bool isNoBuiltin(CallOp call, llvm::StringRef name) {
          });
 }
 
-// Raises a direct cir.call to `TargetOp`. C++ entities are matched through
-// the identity tag on the callee, and C library functions by callee symbol,
-// since C names have no mangling.
-template <typename TargetOp, bool MatchByTag = true> class StdRecognizer {
-  template <size_t... Indices>
+// Raises a direct cir.call to the first candidate in `TargetOps` that matches.
+template <typename... TargetOps> class StdRecognizer {
+  template <typename TargetOp, size_t... Indices>
   static TargetOp buildCall(cir::CIRBaseBuilderTy &builder, CallOp call,
                             std::index_sequence<Indices...>) {
     return TargetOp::create(builder, call.getLoc(),
@@ -88,15 +58,16 @@ template <typename TargetOp, bool MatchByTag = true> class 
StdRecognizer {
                             call.getOperand(Indices)..., call.getCalleeAttr());
   }
 
-public:
-  static bool raise(CallOp call, mlir::MLIRContext &context,
-                    mlir::SymbolTableCollection &symbolTables) {
+  template <typename TargetOp>
+  static bool raiseOne(CallOp call, mlir::MLIRContext &context,
+                       mlir::SymbolTableCollection &symbolTables) {
     // A musttail call must stay a call, so it is never raised.
     if (!call.getCallee() || call.getMusttail() ||
-        !signatureMatches<TargetOp>(call))
+        !TargetOp::signatureMatches(call->getOperandTypes(),
+                                    call->getResultTypes()))
       return false;
 
-    if constexpr (MatchByTag) {
+    if constexpr (TargetOp::hasKnownFuncKind()) {
       // Only a free std function with the right name carries the tag, so
       // members, static members, and operators never match. The shape of the
       // call is checked here, so a variadic callee never matches.
@@ -126,7 +97,8 @@ template <typename TargetOp, bool MatchByTag = true> class 
StdRecognizer {
     cir::CIRBaseBuilderTy builder(context);
     builder.setInsertionPointAfter(call.getOperation());
     constexpr unsigned numArgs = TargetOp::getNumArgs();
-    TargetOp op = buildCall(builder, call, 
std::make_index_sequence<numArgs>());
+    TargetOp op =
+        buildCall<TargetOp>(builder, call, 
std::make_index_sequence<numArgs>());
     // The raised operation keeps every call attribute except the callee,
     // which it carries as original_fn, so lowering back loses nothing.
     for (mlir::NamedAttribute attr : call->getAttrs())
@@ -136,8 +108,18 @@ template <typename TargetOp, bool MatchByTag = true> class 
StdRecognizer {
     call.erase();
     return true;
   }
+
+public:
+  // Tries each candidate in order and stops at the first that raises.
+  static bool raise(CallOp call, mlir::MLIRContext &context,
+                    mlir::SymbolTableCollection &symbolTables) {
+    return (raiseOne<TargetOps>(call, context, symbolTables) || ...);
+  }
 };
 
+// The library calls the recognizer knows how to raise, tried in order.
+using RecognizedStdOps = StdRecognizer<StdFindOp, StrLenOp>;
+
 struct IdiomRecognizerPass
     : public impl::IdiomRecognizerBase<IdiomRecognizerPass> {
   IdiomRecognizerPass() = default;
@@ -151,10 +133,7 @@ struct IdiomRecognizerPass
 
 void IdiomRecognizerPass::recognizeStandardLibraryCall(
     CallOp call, mlir::SymbolTableCollection &symbolTables) {
-  if (StdRecognizer<StdFindOp>::raise(call, getContext(), symbolTables))
-    return;
-  StdRecognizer<StrLenOp, /*MatchByTag=*/false>::raise(call, getContext(),
-                                                       symbolTables);
+  RecognizedStdOps::raise(call, getContext(), symbolTables);
 }
 
 void IdiomRecognizerPass::runOnOperation() {
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp 
b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index c163cdaef887e..0e5187f80918f 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -98,7 +98,7 @@ struct LoweringPreparePass
   void lowerTrivialCopyCall(cir::CallOp op);
   void lowerStoreOfConstAggregate(cir::StoreOp op);
   void lowerLocalInitOp(cir::LocalInitOp op);
-  template <typename StdOpTy> void lowerStdOp(StdOpTy op);
+  void lowerStdOp(cir::StdOpInterface op);
 
   /// Return the FuncOp called by `callOp`.  Uses the cached `symbolTables`
   /// member to avoid the O(M) module-wide scan that the static
@@ -2255,8 +2255,7 @@ void 
LoweringPreparePass::lowerStoreOfConstAggregate(cir::StoreOp op) {
 // Every raised operation carries the original callee, the operands, and the
 // attributes of the call, so this one function lowers any of them back to an
 // equivalent plain call.
-template <typename StdOpTy>
-void LoweringPreparePass::lowerStdOp(StdOpTy typedOp) {
+void LoweringPreparePass::lowerStdOp(cir::StdOpInterface typedOp) {
   mlir::Operation *op = typedOp.getOperation();
   cir::CIRBaseBuilderTy builder(getContext());
   builder.setInsertionPointAfter(op);
@@ -2278,10 +2277,8 @@ void LoweringPreparePass::runOnOp(mlir::Operation *op) {
     lowerArrayCtor(arrayCtor);
   } else if (auto arrayDtor = dyn_cast<cir::ArrayDtor>(op)) {
     lowerArrayDtor(arrayDtor);
-  } else if (auto findOp = mlir::dyn_cast<cir::StdFindOp>(op)) {
-    lowerStdOp(findOp);
-  } else if (auto strlenOp = mlir::dyn_cast<cir::StrLenOp>(op)) {
-    lowerStdOp(strlenOp);
+  } else if (auto stdOp = mlir::dyn_cast<cir::StdOpInterface>(op)) {
+    lowerStdOp(stdOp);
   } else if (auto cast = mlir::dyn_cast<cir::CastOp>(op)) {
     lowerCastOp(cast);
   } else if (auto complexConj = mlir::dyn_cast<cir::ComplexConjOp>(op)) {
@@ -2920,8 +2917,8 @@ void LoweringPreparePass::runOnOperation() {
                   cir::ComplexConjOp, cir::ComplexMulOp, cir::ComplexDivOp,
                   cir::DynamicCastOp, cir::FuncOp, cir::CallOp,
                   cir::GetGlobalOp, cir::GlobalOp, cir::StoreOp,
-                  cir::CmpThreeWayOp, cir::LocalInitOp, cir::StdFindOp,
-                  cir::StrLenOp>(op))
+                  cir::CmpThreeWayOp, cir::LocalInitOp, cir::StdOpInterface>(
+            op))
       opsToTransform.push_back(op);
   });
 
diff --git a/clang/test/CIR/CodeGen/no-builtin-attr-caller.c 
b/clang/test/CIR/CodeGen/no-builtin-attr-caller.c
new file mode 100644
index 0000000000000..37d3440fb9315
--- /dev/null
+++ b/clang/test/CIR/CodeGen/no-builtin-attr-caller.c
@@ -0,0 +1,25 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o 
%t.cir
+// RUN: FileCheck --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir 
-fno-builtin-memcpy %s -o %t.mc.cir
+// RUN: FileCheck --input-file=%t.mc.cir %s --check-prefix=MERGE
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir 
-fno-builtin %s -o %t.all.cir
+// RUN: FileCheck --input-file=%t.all.cir %s --check-prefix=ALL
+
+// A caller's no_builtin list rides on the calls it makes. A 
-fno-builtin-<name>
+// list on the call merges with it, and any empty list disables everything.
+
+unsigned long strlen(const char *);
+
+// CHECK-LABEL: @named
+// CHECK: cir.call @strlen(%{{.*}}){{.*}}nobuiltins = ["strlen"]
+// MERGE-LABEL: @named
+// MERGE: cir.call @strlen(%{{.*}}){{.*}}nobuiltins = ["memcpy", "strlen"]
+// ALL-LABEL: @named
+// ALL: cir.call @strlen(%{{.*}}){{.*}}nobuiltins = []
+__attribute__((no_builtin("strlen")))
+unsigned long named(const char *s) { return strlen(s); }
+
+// CHECK-LABEL: @all
+// CHECK: cir.call @strlen(%{{.*}}){{.*}}nobuiltins = []
+__attribute__((no_builtin))
+unsigned long all(const char *s) { return strlen(s); }
diff --git a/clang/test/CIR/IR/std-ops.cir b/clang/test/CIR/IR/std-ops.cir
index 3fde07e3b979a..8164cc57280c8 100644
--- a/clang/test/CIR/IR/std-ops.cir
+++ b/clang/test/CIR/IR/std-ops.cir
@@ -1,7 +1,5 @@
 // RUN: cir-opt %s | FileCheck %s
 
-// Round-trip parse and print of the raised standard library ops.
-
 !s8i = !cir.int<s, 8>
 !u64i = !cir.int<u, 64>
 
diff --git a/clang/test/CIR/Transforms/idiom-recognizer-strlen-guards.cir 
b/clang/test/CIR/Transforms/idiom-recognizer-strlen-guards.cir
index b1f75a9b3c3f1..c7acc990ab40e 100644
--- a/clang/test/CIR/Transforms/idiom-recognizer-strlen-guards.cir
+++ b/clang/test/CIR/Transforms/idiom-recognizer-strlen-guards.cir
@@ -1,13 +1,5 @@
 // RUN: cir-opt %s --cir-idiom-recognizer -split-input-file | FileCheck %s
 
-// Negative coverage for the strlen recognizer, exercised directly on CIR so
-// each conflicting strlen signature lives in its own module without a macro.
-// The recognizer raises only a direct, non-variadic call named strlen that
-// takes one pointer to an 8 bit character, returns a fundamental unsigned
-// integer, and has no builtin state blocking it. The first module is a
-// positive control proving the pass runs. Every later module keeps its plain
-// cir.call.
-
 !s8i = !cir.int<s, 8>
 !u64i = !cir.int<u, 64>
 module {
@@ -53,7 +45,6 @@ module {
 // CHECK-NOT: cir.std.strlen
 
 // -----
-// A call with no result has nothing to raise, so it is not matched.
 !s8i = !cir.int<s, 8>
 module {
   cir.func private @strlen(!cir.ptr<!s8i>)
@@ -67,7 +58,6 @@ module {
 // CHECK-NOT: cir.std.strlen
 
 // -----
-// A nullary call has no argument to inspect, so it is not matched.
 !u64i = !cir.int<u, 64>
 module {
   cir.func private @strlen() -> !u64i
@@ -111,7 +101,6 @@ module {
 // CHECK-NOT: cir.std.strlen
 
 // -----
-// A nobuiltin mark on the call blocks the raise.
 !s8i = !cir.int<s, 8>
 !u64i = !cir.int<u, 64>
 module {
@@ -126,36 +115,47 @@ module {
 // CHECK-NOT: cir.std.strlen
 
 // -----
-// A caller nobuiltins list naming strlen blocks the raise.
 !s8i = !cir.int<s, 8>
 !u64i = !cir.int<u, 64>
 module {
   cir.func private @strlen(!cir.ptr<!s8i>) -> !u64i
-  cir.func @nobuiltins_list(%s: !cir.ptr<!s8i>) -> !u64i attributes 
{nobuiltins = ["strlen"]} {
-    %0 = cir.call @strlen(%s) : (!cir.ptr<!s8i>) -> !u64i
+  cir.func @call_nobuiltins_list(%s: !cir.ptr<!s8i>) -> !u64i {
+    %0 = cir.call @strlen(%s) {nobuiltins = ["strlen"]} : (!cir.ptr<!s8i>) -> 
!u64i
     cir.return %0 : !u64i
   }
 }
-// CHECK-LABEL: cir.func @nobuiltins_list
+// CHECK-LABEL: cir.func @call_nobuiltins_list
 // CHECK: cir.call @strlen
 // CHECK-NOT: cir.std.strlen
 
 // -----
-// An empty caller nobuiltins list disables every builtin, so it also blocks 
the
-// raise.
+// An empty nobuiltins list disables every builtin, so it blocks the raise.
 !s8i = !cir.int<s, 8>
 !u64i = !cir.int<u, 64>
 module {
   cir.func private @strlen(!cir.ptr<!s8i>) -> !u64i
-  cir.func @empty_nobuiltins(%s: !cir.ptr<!s8i>) -> !u64i attributes 
{nobuiltins = []} {
-    %0 = cir.call @strlen(%s) : (!cir.ptr<!s8i>) -> !u64i
+  cir.func @call_empty_nobuiltins(%s: !cir.ptr<!s8i>) -> !u64i {
+    %0 = cir.call @strlen(%s) {nobuiltins = []} : (!cir.ptr<!s8i>) -> !u64i
     cir.return %0 : !u64i
   }
 }
-// CHECK-LABEL: cir.func @empty_nobuiltins
+// CHECK-LABEL: cir.func @call_empty_nobuiltins
 // CHECK: cir.call @strlen
 // CHECK-NOT: cir.std.strlen
 
+// -----
+!s8i = !cir.int<s, 8>
+!u64i = !cir.int<u, 64>
+module {
+  cir.func private @strlen(!cir.ptr<!s8i>) -> !u64i
+  cir.func @builtin_override(%s: !cir.ptr<!s8i>) -> !u64i {
+    %0 = cir.call @strlen(%s) {builtin, nobuiltin} : (!cir.ptr<!s8i>) -> !u64i
+    cir.return %0 : !u64i
+  }
+}
+// CHECK-LABEL: cir.func @builtin_override
+// CHECK: cir.std.strlen
+
 // -----
 // The result is size_t, which is unsigned, so a signed result is not raised.
 !s8i = !cir.int<s, 8>
diff --git a/clang/test/CIR/Transforms/idiom-recognizer.cpp 
b/clang/test/CIR/Transforms/idiom-recognizer.cpp
index fbf2b206c1ab0..ebce1f306b799 100644
--- a/clang/test/CIR/Transforms/idiom-recognizer.cpp
+++ b/clang/test/CIR/Transforms/idiom-recognizer.cpp
@@ -14,12 +14,12 @@
 
 // A no builtin list for another function survives the round trip on the
 // rebuilt call.
-// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu 
-fno-builtin-memcpy -fclangir -clangir-enable-idiom-recognizer -emit-cir %s -o 
%t.fnb.cir
-// RUN: FileCheck %s --check-prefix=FNB --input-file=%t.fnb.cir
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu 
-fno-builtin-memcpy -fclangir -clangir-enable-idiom-recognizer -emit-cir %s -o 
%t.no-builtin-memcpy.cir
+// RUN: FileCheck %s --check-prefix=NO-BUILTIN-MEMCPY 
--input-file=%t.no-builtin-memcpy.cir
 
 // With builtins disabled the strlen call is left alone, while the tagged
 // std::find is unaffected and still raises.
-// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fno-builtin 
-fclangir -clangir-enable-idiom-recognizer -emit-cir -mmlir 
--mlir-print-ir-after=cir-idiom-recognizer %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=NB --implicit-check-not=cir.std.strlen
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fno-builtin 
-fclangir -clangir-enable-idiom-recognizer -emit-cir -mmlir 
--mlir-print-ir-after=cir-idiom-recognizer %s -o /dev/null 2>&1 | FileCheck %s 
--check-prefix=NO-BUILTINS --implicit-check-not=cir.std.strlen
 
 namespace std {
 template <class Iter, class T>
@@ -34,6 +34,8 @@ char *test_find(char *first, char *last, const char &value) {
 // operands in source order and its attributes.
 // RAISED: cir.std.find(
 // RAISED-SAME: @_ZSt4findIPccET_S1_S1_RKT0_
+// NO-BUILTINS: cir.std.find(
+// NO-BUILTINS-SAME: @_ZSt4findIPccET_S1_S1_RKT0_
 // FINAL: %[[FIRST_ADDR:.*]] = cir.alloca "first"
 // FINAL: %[[LAST_ADDR:.*]] = cir.alloca "last"
 // FINAL: %[[VALUE_ADDR:.*]] = cir.alloca "value"
@@ -55,9 +57,16 @@ unsigned long test_strlen(const char *s) { return strlen(s); 
}
 // FINAL: cir.call @strlen(%[[S]]) nothrow
 // FINAL-SAME: (!cir.ptr<!s8i> {llvm.noundef})
 // FINAL-SAME: -> !u64i
-// FNB: cir.call @strlen
-// FNB-SAME: nobuiltins = ["memcpy"]
-// NB: cir.call @strlen
+// NO-BUILTIN-MEMCPY: cir.call @strlen
+// NO-BUILTIN-MEMCPY-SAME: nobuiltins = ["memcpy"]
+// NO-BUILTINS: cir.call @strlen
+
+__attribute__((no_builtin("strlen")))
+unsigned long test_strlen_no_builtin(const char *s) { return strlen(s); }
+// RAISED-LABEL: @_Z22test_strlen_no_builtinPKc
+// RAISED: cir.call @strlen
+// RAISED-SAME: nobuiltins = ["strlen"]
+// RAISED-NOT: cir.std.strlen
 
 // A function merely named like the std one is not raised, and it survives the
 // whole pipeline as the same plain call.

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to