Author: Rithik Sharma Date: 2026-07-16T09:55:51-07:00 New Revision: be1728c99d806654d4ce4e7ad17aa89885e8053c
URL: https://github.com/llvm/llvm-project/commit/be1728c99d806654d4ce4e7ad17aa89885e8053c DIFF: https://github.com/llvm/llvm-project/commit/be1728c99d806654d4ce4e7ad17aa89885e8053c.diff LOG: [CIR] Add support for the IdiomRecognizer pass (#208854) This patch adds the `IdiomRecognizer` pass, which raises calls to known standard library functions into dedicated operations that later passes can optimize. The implementation follows the ClangIR incubator, and upstream had only the pass skeleton and the frontend wiring. The pass recognizes the standard find algorithm and raises it to the new operation `cir.std.find`. A call is raised when its callee carries the matching identity tag, the argument count and the types line up, the callee is not variadic, and the call is not `musttail`. LoweringPrepare lowers the operation back to the original call with its attributes, so behavior never changes when no transform fires. The tag is the new `func_identity` form under the `func_info` union on `cir.func`. It holds one entry from an enum naming the entities the recognizer knows, so the tag itself carries no names. `CIRGen` attaches it by the plain name, the std membership with inline namespaces looked through, and whether the function is free, so members, static members, operators, and functions outside std never match, while the recognizer checks the shape of each call. The pass reads facts from the operations alone, so the AST requirement and its wiring are gone, the pass is registered with `cir-opt`, and a test drives it over parsed CIR assembly. Added: clang/include/clang/CIR/Dialect/IR/CIRStdOps.td clang/test/CIR/CodeGen/func-identity-attr.c clang/test/CIR/CodeGen/func-identity-attr.cpp clang/test/CIR/IR/func-identity-attr.cir clang/test/CIR/Transforms/idiom-recognizer-guards-void-result.cpp clang/test/CIR/Transforms/idiom-recognizer-guards.cpp clang/test/CIR/Transforms/idiom-recognizer-inline-namespace.cpp clang/test/CIR/Transforms/idiom-recognizer.cir Modified: clang/include/clang/CIR/Dialect/IR/CIRAttrs.td clang/include/clang/CIR/Dialect/IR/CIROps.td clang/include/clang/CIR/Dialect/Passes.h clang/lib/CIR/CodeGen/CIRGenClass.cpp clang/lib/CIR/CodeGen/CIRGenFunction.cpp clang/lib/CIR/CodeGen/CIRGenModule.cpp clang/lib/CIR/CodeGen/CIRGenModule.h clang/lib/CIR/Dialect/IR/CIRDialect.cpp clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp clang/lib/CIR/Lowering/CIRPasses.cpp clang/test/CIR/Transforms/idiom-recognizer.cpp clang/tools/cir-opt/cir-opt.cpp Removed: ################################################################################ diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td index 88e8f91f7e75c..bf91a1831eeff 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td +++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td @@ -1296,11 +1296,56 @@ def CIR_CXXAssignAttr : CIR_Attr<"CXXAssign", "cxx_assign"> { // FuncInfoAttr //===----------------------------------------------------------------------===// +// The standard library entities the identity tag can name. Each entry +// pairs with one raised operation in CIRStdOps.td. +def CIR_KnownFuncKind : CIR_I32EnumAttr<"KnownFuncKind", + "known standard library entity", [ + I32EnumAttrCase<"StdFind", 1, "std::find">, +]> { + let genSpecializedAttr = 0; + // A name like std::find is not a bare identifier, so print it as a quoted + // string. The enum parser already reads a quoted string back. + let parameterPrinter = + "$_printer.printKeywordOrString(" # symbolToStringFnName # "($_self))"; +} + +def CIR_FuncIdentityAttr : CIR_Attr<"FuncIdentity", "func_identity"> { + let summary = "Identifies a function as a known standard library entity"; + let description = [{ + Names the standard library entity a function represents, so that + transformations can recognize calls to well known library functions + without decoding mangled symbol names. + + The tag names the whole entity. For `std::find` that is the free + function named `find` in the `std` namespace, so a member function, a + static member, or an operator can never carry the tag. Inline + namespaces, such as the versioning namespace of libc++, count as part + of `std`. The tag never encodes signatures, and a function that + matches no known entity carries no attribute. + + Example: + ``` + #cir.func_identity<"std::find"> + ``` + }]; + + let parameters = (ins + EnumParameter<CIR_KnownFuncKind>:$kind + ); + + let assemblyFormat = [{ + `<` $kind `>` + }]; + + let canHaveIllegalCXXABIType = 0; +} + // The attributes accepted in the optional func_info slot on cir.func. def CIR_FuncInfoAttr : AnyAttrOf<[ CIR_CXXCtorAttr, CIR_CXXDtorAttr, - CIR_CXXAssignAttr + CIR_CXXAssignAttr, + CIR_FuncIdentityAttr ], "function information attribute">; //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index 762ef56248e4c..0a60514ee21fc 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -8658,4 +8658,6 @@ def CIR_LaunderOp : CIR_Op<"launder", [SameOperandsAndResultType]> { let llvmOp = "LaunderInvariantGroupOp"; } +include "clang/CIR/Dialect/IR/CIRStdOps.td" + #endif // CLANG_CIR_DIALECT_IR_CIROPS_TD diff --git a/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td b/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td new file mode 100644 index 0000000000000..608a958e5117c --- /dev/null +++ b/clang/include/clang/CIR/Dialect/IR/CIRStdOps.td @@ -0,0 +1,53 @@ +//===-- CIRStdOps.td - CIR standard library ops ------------*- tablegen -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +/// +/// Defines ops representing standard library calls, raised from plain calls +/// by the cir-idiom-recognizer pass and lowered back by LoweringPrepare. +/// +//===----------------------------------------------------------------------===// + +#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. +class CIR_StdOp<string functionName, dag args, dag res, + list<Trait> traits = [], string knownKind = ""> + : CIR_Op<"std." # functionName, traits> { + let arguments = !con(args, (ins FlatSymbolRefAttr:$original_fn)); + let results = res; + let hasLLVMLowering = false; + + let extraClassDeclaration = [{ + static constexpr unsigned getNumArgs() { + return }] # !size(args) # [{; + } + static llvm::StringLiteral getFunctionName() { + return "}] # functionName # [{"; + } + }] # !if(!empty(knownKind), "", [{ + static cir::KnownFuncKind getFuncKind() { + return cir::KnownFuncKind::}] # knownKind # [{; + } + }]); +} + +def CIR_StdFindOp : CIR_StdOp<"find", + (ins CIR_AnyType:$first, CIR_AnyType:$last, CIR_AnyType:$pattern), + (outs CIR_AnyType:$result), + [AllTypesMatch<["first", "last", "result"]>], "StdFind"> { + let summary = "std::find()"; + let assemblyFormat = [{ + `(` $first `:` qualified(type($first)) `,` + $last `:` qualified(type($last)) `,` + $pattern `:` qualified(type($pattern)) `,` + $original_fn `)` `->` qualified(type($result)) attr-dict + }]; +} + +#endif // CLANG_CIR_DIALECT_IR_CIRSTDOPS_TD diff --git a/clang/include/clang/CIR/Dialect/Passes.h b/clang/include/clang/CIR/Dialect/Passes.h index 651a1319cfe5d..7d317879ad739 100644 --- a/clang/include/clang/CIR/Dialect/Passes.h +++ b/clang/include/clang/CIR/Dialect/Passes.h @@ -33,7 +33,6 @@ std::unique_ptr<Pass> createLoweringPreparePass(); std::unique_ptr<Pass> createLoweringPreparePass(clang::ASTContext *astCtx); std::unique_ptr<Pass> createGotoSolverPass(); std::unique_ptr<Pass> createIdiomRecognizerPass(); -std::unique_ptr<Pass> createIdiomRecognizerPass(clang::ASTContext *astCtx); std::unique_ptr<Pass> createLibOptPass(); std::unique_ptr<Pass> createLibOptPass(clang::ASTContext *astCtx); diff --git a/clang/lib/CIR/CodeGen/CIRGenClass.cpp b/clang/lib/CIR/CodeGen/CIRGenClass.cpp index eb6490973da75..96ece6703a512 100644 --- a/clang/lib/CIR/CodeGen/CIRGenClass.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenClass.cpp @@ -896,7 +896,7 @@ void CIRGenFunction::emitImplicitAssignmentOperatorBody(FunctionArgList &args) { "Body of an implicit assignment operator should be compound stmt."); const auto *rootCS = cast<CompoundStmt>(rootS); - cgm.setCXXSpecialMemberAttr(cast<cir::FuncOp>(curFn), assignOp); + cgm.setFuncInfoAttr(cast<cir::FuncOp>(curFn), assignOp); assert(!cir::MissingFeatures::incrementProfileCounter()); assert(!cir::MissingFeatures::runCleanupsScope()); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index d0aedc0689404..bbcf5b6c38899 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -813,7 +813,7 @@ void CIRGenFunction::emitConstructorBody(FunctionArgList &args) { ctorType == Ctor_Complete) && "can only generate complete ctor for this ABI"); - cgm.setCXXSpecialMemberAttr(cast<cir::FuncOp>(curFn), ctor); + cgm.setFuncInfoAttr(cast<cir::FuncOp>(curFn), ctor); if (ctorType == Ctor_Complete && isConstructorDelegationValid(ctor) && cgm.getTarget().getCXXABI().hasConstructorVariants()) { @@ -870,7 +870,7 @@ void CIRGenFunction::emitDestructorBody(FunctionArgList &args) { const CXXDestructorDecl *dtor = cast<CXXDestructorDecl>(curGD.getDecl()); CXXDtorType dtorType = curGD.getDtorType(); - cgm.setCXXSpecialMemberAttr(cast<cir::FuncOp>(curFn), dtor); + cgm.setFuncInfoAttr(cast<cir::FuncOp>(curFn), dtor); // For an abstract class, non-base destructors are never used (and can't // be emitted in general, because vbase dtors may not have been validated diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 66a9dc9ea605c..0d5caa6c424df 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -37,6 +37,7 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringExtras.h" #include "llvm/ADT/StringRef.h" +#include "llvm/ADT/StringSwitch.h" #include "llvm/Support/raw_ostream.h" #include "CIRGenFunctionInfo.h" @@ -3458,8 +3459,9 @@ CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name, assert(!cir::MissingFeatures::opFuncExtraAttrs()); - // Mark C++ special member functions (Constructor, Destructor etc.) - setCXXSpecialMemberAttr(func, funcDecl); + // Record the func_info tag, a C++ special member form or a known standard + // library entity. + setFuncInfoAttr(func, funcDecl); if (!cgf) theModule.push_back(func); @@ -3503,8 +3505,8 @@ static cir::AssignKind getAssignKindFromDecl(const CXXMethodDecl *method) { llvm_unreachable("not a copy or move assignment operator"); } -void CIRGenModule::setCXXSpecialMemberAttr( - cir::FuncOp funcOp, const clang::FunctionDecl *funcDecl) { +void CIRGenModule::setFuncInfoAttr(cir::FuncOp funcOp, + const clang::FunctionDecl *funcDecl) { if (!funcDecl) return; @@ -3535,6 +3537,33 @@ void CIRGenModule::setCXXSpecialMemberAttr( funcOp.setFuncInfoAttr(cxxAssign); return; } + + // Otherwise tag a function that matches a known standard library entity. A + // known entity is named by a plain identifier in std. For a member the + // record decides std membership. Inline namespaces, like the versioning + // namespace of libc++, count as part of std. + if (!funcDecl->getIdentifier()) + return; + bool inStdNamespace = method ? method->getParent()->isInStdNamespace() + : funcDecl->isInStdNamespace(); + if (!inStdNamespace) + return; + + // The names and the tags come from CIRStdOps.td, and the recognizer checks + // the shape of each call. Only free functions name a known entity today, so + // a member like char_traits::find never shares the tag of the free std::find. + std::optional<cir::KnownFuncKind> kind; + if (!method) { + kind = llvm::StringSwitch<std::optional<cir::KnownFuncKind>>( + funcDecl->getName()) + .Case(cir::StdFindOp::getFunctionName(), + cir::StdFindOp::getFuncKind()) + .Default(std::nullopt); + } + if (!kind) + return; + + funcOp.setFuncInfoAttr(cir::FuncIdentityAttr::get(&getMLIRContext(), *kind)); } static void setWindowsItaniumDLLImport(CIRGenModule &cgm, bool isLocal, diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 144f8c7b9f3e7..e47498dbc1588 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -798,9 +798,10 @@ class CIRGenModule : public CIRGenTypeCache { cir::FuncType ty, const clang::FunctionDecl *fd); - /// Mark the function as a special member (e.g. constructor, destructor) - void setCXXSpecialMemberAttr(cir::FuncOp funcOp, - const clang::FunctionDecl *funcDecl); + /// Record the func_info tag for a function, either a C++ special member + /// form (constructor, destructor, assignment) or a known standard library + /// entity that passes can recognize without the AST. + void setFuncInfoAttr(cir::FuncOp funcOp, const clang::FunctionDecl *funcDecl); cir::FuncOp createRuntimeFunction(cir::FuncType ty, llvm::StringRef name, mlir::NamedAttrList extraAttrs = {}, diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index c519229a02f26..60ad12b92cdb9 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -2550,8 +2550,8 @@ ParseResult cir::FuncOp::parse(OpAsmParser &parser, OperationState &state) { mlir::Attribute attr; if (parser.parseAttribute(attr).failed()) return failure(); - if (!mlir::isa<cir::CXXCtorAttr, cir::CXXDtorAttr, cir::CXXAssignAttr>( - attr)) + if (!mlir::isa<cir::CXXCtorAttr, cir::CXXDtorAttr, cir::CXXAssignAttr, + cir::FuncIdentityAttr>(attr)) return parser.emitError(attrLoc, "expected a function info attribute, got ") << attr; diff --git a/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp b/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp index 4a8f931dd2d4d..030a6bed68b99 100644 --- a/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp +++ b/clang/lib/CIR/Dialect/Transforms/IdiomRecognizer.cpp @@ -13,21 +13,12 @@ //===----------------------------------------------------------------------===// #include "PassDetail.h" -#include "mlir/Dialect/Func/IR/FuncOps.h" -#include "mlir/IR/BuiltinAttributes.h" -#include "mlir/IR/Region.h" -#include "clang/AST/ASTContext.h" -#include "clang/AST/Mangle.h" -#include "clang/Basic/Module.h" #include "clang/CIR/Dialect/Builder/CIRBaseBuilder.h" #include "clang/CIR/Dialect/IR/CIRDialect.h" #include "clang/CIR/Dialect/Passes.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringRef.h" -#include "llvm/ADT/Twine.h" -#include "llvm/Support/ErrorHandling.h" -#include "llvm/Support/Path.h" + +#include <utility> using namespace mlir; using namespace cir; @@ -39,58 +30,97 @@ 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(); +} + +// Raises a direct cir.call to `TargetOp` when the callee carries the +// matching identity tag. +template <typename TargetOp> class StdRecognizer { + template <size_t... Indices> + static TargetOp buildCall(cir::CIRBaseBuilderTy &builder, CallOp call, + std::index_sequence<Indices...>) { + return TargetOp::create(builder, call.getLoc(), + call->getResult(0).getType(), + call.getOperand(Indices)..., call.getCalleeAttr()); + } + +public: + static bool raise(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)) + 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; + + cir::CIRBaseBuilderTy builder(context); + builder.setInsertionPointAfter(call.getOperation()); + constexpr unsigned numArgs = TargetOp::getNumArgs(); + TargetOp op = buildCall(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()) + if (attr.getName() != "callee") + op->setAttr(attr.getName(), attr.getValue()); + call.replaceAllUsesWith(op); + call.erase(); + return true; + } +}; + struct IdiomRecognizerPass : public impl::IdiomRecognizerBase<IdiomRecognizerPass> { IdiomRecognizerPass() = default; void runOnOperation() override; - void recognizeStandardLibraryCall(CallOp call); - - clang::ASTContext *astCtx; - void setASTContext(clang::ASTContext *c) { astCtx = c; } - - /// Tracks current module. - ModuleOp theModule; + void recognizeStandardLibraryCall(CallOp call, + mlir::SymbolTableCollection &symbolTables); }; } // namespace -void IdiomRecognizerPass::recognizeStandardLibraryCall(CallOp call) { - // To be implemented +void IdiomRecognizerPass::recognizeStandardLibraryCall( + CallOp call, mlir::SymbolTableCollection &symbolTables) { + StdRecognizer<StdFindOp>::raise(call, getContext(), symbolTables); } void IdiomRecognizerPass::runOnOperation() { - // The AST context will be used to provide additional information such as - // namespaces and template parameter lists that are lost after lowering to - // CIR. This information is necessary to recognize many idioms, such as calls - // to standard library functions. - - // For now, the AST will be required to allow for faster prototyping and - // exploring of new optimizations. In the future, it may be preferable to - // make it optional to reduce memory pressure and allow this pass to run - // on standalone CIR assembly (Possibly generated from non-Clang front ends). - - assert(astCtx && "Missing ASTContext, please construct with the right ctor"); - theModule = getOperation(); + // The facts this pass reads live on the operations, so it needs no AST + // and also works on parsed CIR assembly. + mlir::SymbolTableCollection symbolTables; - // Process call operations - theModule->walk([&](CallOp callOp) { + getOperation()->walk([&](CallOp callOp) { // Skip indirect calls. std::optional<llvm::StringRef> callee = callOp.getCallee(); if (!callee) return; - recognizeStandardLibraryCall(callOp); + recognizeStandardLibraryCall(callOp, symbolTables); }); } std::unique_ptr<Pass> mlir::createIdiomRecognizerPass() { return std::make_unique<IdiomRecognizerPass>(); } - -std::unique_ptr<Pass> -mlir::createIdiomRecognizerPass(clang::ASTContext *astCtx) { - auto pass = std::make_unique<IdiomRecognizerPass>(); - pass->setASTContext(astCtx); - return std::move(pass); -} diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp index e50830cffa742..4c2369f27ef7f 100644 --- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp +++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp @@ -98,6 +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); /// Return the FuncOp called by `callOp`. Uses the cached `symbolTables` /// member to avoid the O(M) module-wide scan that the static @@ -2251,11 +2252,34 @@ void LoweringPreparePass::lowerStoreOfConstAggregate(cir::StoreOp op) { constOp.erase(); } +// 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) { + mlir::Operation *op = typedOp.getOperation(); + cir::CIRBaseBuilderTy builder(getContext()); + builder.setInsertionPointAfter(op); + mlir::Type resultType; + if (op->getNumResults()) + resultType = op->getResult(0).getType(); + cir::CallOp call = builder.createCallOp( + op->getLoc(), typedOp.getOriginalFnAttr(), resultType, op->getOperands()); + for (mlir::NamedAttribute attr : op->getAttrs()) + if (attr.getName() != typedOp.getOriginalFnAttrName()) + call->setAttr(attr.getName(), attr.getValue()); + + op->replaceAllUsesWith(call); + op->erase(); +} + void LoweringPreparePass::runOnOp(mlir::Operation *op) { if (auto arrayCtor = dyn_cast<cir::ArrayCtor>(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 cast = mlir::dyn_cast<cir::CastOp>(op)) { lowerCastOp(cast); } else if (auto complexConj = mlir::dyn_cast<cir::ComplexConjOp>(op)) { @@ -2894,7 +2918,7 @@ 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>(op)) + cir::CmpThreeWayOp, cir::LocalInitOp, cir::StdFindOp>(op)) opsToTransform.push_back(op); }); diff --git a/clang/lib/CIR/Lowering/CIRPasses.cpp b/clang/lib/CIR/Lowering/CIRPasses.cpp index f476ee04430cd..2866e5f15379c 100644 --- a/clang/lib/CIR/Lowering/CIRPasses.cpp +++ b/clang/lib/CIR/Lowering/CIRPasses.cpp @@ -33,7 +33,7 @@ runCIRToCIRPasses(mlir::ModuleOp theModule, mlir::MLIRContext &mlirContext, pm.addPass(mlir::createCIRSimplifyPass()); if (enableIdiomRecognizer) - pm.addPass(mlir::createIdiomRecognizerPass(&astContext)); + pm.addPass(mlir::createIdiomRecognizerPass()); if (enableLibOpt) { auto libOptPass = mlir::createLibOptPass(); diff --git a/clang/test/CIR/CodeGen/func-identity-attr.c b/clang/test/CIR/CodeGen/func-identity-attr.c new file mode 100644 index 0000000000000..56e4493626d4d --- /dev/null +++ b/clang/test/CIR/CodeGen/func-identity-attr.c @@ -0,0 +1,10 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - | FileCheck %s + +char *find(char *first, char *last, int value); + +char *caller(char *first, char *last) { return find(first, last, 42); } + +// A C function named like the std one lives outside std, so it carries no +// tag. +// CHECK: cir.func{{.*}} @find +// CHECK-NOT: func_identity diff --git a/clang/test/CIR/CodeGen/func-identity-attr.cpp b/clang/test/CIR/CodeGen/func-identity-attr.cpp new file mode 100644 index 0000000000000..468d1664717b8 --- /dev/null +++ b/clang/test/CIR/CodeGen/func-identity-attr.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck %s --input-file=%t.cir +// RUN: FileCheck %s --input-file=%t.cir --check-prefix=TAG + +namespace std { +inline namespace __1 { +int *find(int *first, int *last, int value); +} +struct container { + int *find(int value); +}; +struct traits { + static int *find(int *first); +}; +} + +namespace other { +int *find(int *first, int *last, int value); +} + +int *std_call(int *first, int *last) { return std::find(first, last, 42); } +// The free std find carries its tag, with inline namespaces looked +// through. +// CHECK: cir.func{{.*}} @_ZNSt3__14find{{.*}} func_info<#cir.func_identity<"std::find">> + +struct S { + void operator()(); +}; + +void other_calls(S &s, std::container &c, int *first, int *last) { + c.find(1); + std::traits::find(first); + other::find(first, last, 42); + s(); +} +// Members, static members, operators, and functions outside std match no +// entity, so the free std find above stays the only tagged function. +// TAG-COUNT-1: #cir.func_identity +// TAG-NOT: #cir.func_identity diff --git a/clang/test/CIR/IR/func-identity-attr.cir b/clang/test/CIR/IR/func-identity-attr.cir new file mode 100644 index 0000000000000..0e338112cb38d --- /dev/null +++ b/clang/test/CIR/IR/func-identity-attr.cir @@ -0,0 +1,8 @@ +// RUN: cir-opt %s --verify-roundtrip | FileCheck %s + +module { + +cir.func private @_ZSt4findv() func_info<#cir.func_identity<"std::find">> +// CHECK: cir.func private @_ZSt4findv() func_info<#cir.func_identity<"std::find">> + +} diff --git a/clang/test/CIR/Transforms/idiom-recognizer-guards-void-result.cpp b/clang/test/CIR/Transforms/idiom-recognizer-guards-void-result.cpp new file mode 100644 index 0000000000000..fd72b75083068 --- /dev/null +++ b/clang/test/CIR/Transforms/idiom-recognizer-guards-void-result.cpp @@ -0,0 +1,14 @@ +// 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 /dev/null 2>&1 | FileCheck %s --implicit-check-not=cir.std. + +// std::find returns the iterator, so a result is required. A void result +// fails the recognizer's result check and the call stays. + +namespace std { +void find(char *first, char *last, const char &value); +} + +void test_void_result(char *first, char *last, const char &value) { + std::find(first, last, value); +} +// CHECK-LABEL: @_Z16test_void_result +// CHECK: cir.call @_ZSt4findPcS_RKc diff --git a/clang/test/CIR/Transforms/idiom-recognizer-guards.cpp b/clang/test/CIR/Transforms/idiom-recognizer-guards.cpp new file mode 100644 index 0000000000000..11612371e023e --- /dev/null +++ b/clang/test/CIR/Transforms/idiom-recognizer-guards.cpp @@ -0,0 +1,67 @@ +// 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 /dev/null 2>&1 | FileCheck %s --implicit-check-not=cir.std. + +namespace std { +// Variadic, only viable for the all-pointer call in test_variadic. +char *find(char *first, ...); +// Result type diff ers from the iterator type. +int find(char *first, char *last, const char &value); +// Searched value type diff ers from the element type. +char *find(char *first, char *last, const int &value); +// Wrong argument count. The C++17 std::find overload taking an ExecutionPolicy +// is also declined here, since it too diff ers from the recognized three +// argument shape. +char *find(char *first, char *last, const char &value, int n); +} + +char *test_variadic(char *first, char *last, char *value) { + return std::find(first, last, value); +} +// CHECK-LABEL: @_Z13test_variadic +// CHECK: cir.call @_ZSt4findPcz + +int test_result_type(char *first, char *last, const char &value) { + return std::find(first, last, value); +} +// CHECK-LABEL: @_Z16test_result_type +// CHECK: cir.call @_ZSt4findPcS_RKc + +char *test_pattern_type(char *first, char *last, const int &value) { + return std::find(first, last, value); +} +// CHECK-LABEL: @_Z17test_pattern_type +// CHECK: cir.call @_ZSt4findPcS_RKi + +char *test_wrong_arg_count(char *first, char *last, const char &value) { + return std::find(first, last, value, 1); +} +// CHECK-LABEL: @_Z20test_wrong_arg_count +// CHECK: cir.call @_ZSt4findPcS_RKci + +// std membership is fixed when the tag is set, so a find outside std is never +// tagged and never raised, whatever the call shape. + +// A nested namespace that is not inline is not std. +namespace std { +namespace another_ns { +template <class Iter, class T> +Iter find(Iter, Iter, const T &); +} +} + +char *test_nested_namespace(char *first, char *last, const char &value) { + return std::another_ns::find(first, last, value); +} +// CHECK-LABEL: @_Z21test_nested_namespace +// CHECK: cir.call + +// An anonymous namespace function is not std::find. +namespace { +template <class Iter, class T> +Iter find(Iter, Iter, const T &); +} + +char *test_anonymous_namespace(char *first, char *last, const char &value) { + return find(first, last, value); +} +// CHECK-LABEL: @_Z24test_anonymous_namespace +// CHECK: cir.call diff --git a/clang/test/CIR/Transforms/idiom-recognizer-inline-namespace.cpp b/clang/test/CIR/Transforms/idiom-recognizer-inline-namespace.cpp new file mode 100644 index 0000000000000..5eb146ba2f3b7 --- /dev/null +++ b/clang/test/CIR/Transforms/idiom-recognizer-inline-namespace.cpp @@ -0,0 +1,17 @@ +// 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 /dev/null 2>&1 | FileCheck %s + +// The versioning namespace of libc++ is inline, so std::find resolves through +// it and the call is still tagged and raised. + +namespace std { +inline namespace __1 { +template <class Iter, class T> +Iter find(Iter, Iter, const T &); +} +} + +char *test_inline_namespace(char *first, char *last, const char &value) { + return std::find(first, last, value); +} +// CHECK-LABEL: @_Z21test_inline_namespace +// CHECK: cir.std.find diff --git a/clang/test/CIR/Transforms/idiom-recognizer.cir b/clang/test/CIR/Transforms/idiom-recognizer.cir new file mode 100644 index 0000000000000..7e9507f13c023 --- /dev/null +++ b/clang/test/CIR/Transforms/idiom-recognizer.cir @@ -0,0 +1,36 @@ +// RUN: cir-opt %s -cir-idiom-recognizer | FileCheck %s + +// The pass reads only the tag on the callee, so recognition also works on +// parsed CIR assembly with no AST behind it. + +!s32i = !cir.int<s, 32> + +module { + +cir.func private @_ZSt4findIPiiET_S1_S1_RKT0_(!cir.ptr<!s32i>, !cir.ptr<!s32i>, !cir.ptr<!s32i>) -> !cir.ptr<!s32i> func_info<#cir.func_identity<"std::find">> + +cir.func @caller(%a: !cir.ptr<!s32i>, %b: !cir.ptr<!s32i>, %v: !cir.ptr<!s32i>) -> !cir.ptr<!s32i> { + %r = cir.call @_ZSt4findIPiiET_S1_S1_RKT0_(%a, %b, %v) : (!cir.ptr<!s32i>, !cir.ptr<!s32i>, !cir.ptr<!s32i>) -> !cir.ptr<!s32i> + cir.return %r : !cir.ptr<!s32i> +} +// The operands are threaded into the raised op in order, first to last. +// CHECK: cir.func @caller(%[[A:.*]]: !cir.ptr<!s32i>, %[[B:.*]]: !cir.ptr<!s32i>, %[[V:.*]]: !cir.ptr<!s32i>) +// CHECK: cir.std.find(%[[A]] :{{.*}}, %[[B]] :{{.*}}, %[[V]] :{{.*}}, @_ZSt4findIPiiET_S1_S1_RKT0_) + +// A musttail call must stay a call. +cir.func @musttail_caller(%a: !cir.ptr<!s32i>, %b: !cir.ptr<!s32i>, %v: !cir.ptr<!s32i>) -> !cir.ptr<!s32i> { + %r = cir.call @_ZSt4findIPiiET_S1_S1_RKT0_(%a, %b, %v) musttail : (!cir.ptr<!s32i>, !cir.ptr<!s32i>, !cir.ptr<!s32i>) -> !cir.ptr<!s32i> + cir.return %r : !cir.ptr<!s32i> +} +// CHECK-LABEL: @musttail_caller +// CHECK: cir.call @_ZSt4findIPiiET_S1_S1_RKT0_({{.*}}) musttail + +// An indirect call has no callee to resolve. +cir.func @indirect_caller(%f: !cir.ptr<!cir.func<(!cir.ptr<!s32i>, !cir.ptr<!s32i>, !cir.ptr<!s32i>) -> !cir.ptr<!s32i>>>, %a: !cir.ptr<!s32i>, %b: !cir.ptr<!s32i>, %v: !cir.ptr<!s32i>) -> !cir.ptr<!s32i> { + %r = cir.call %f(%a, %b, %v) : (!cir.ptr<!cir.func<(!cir.ptr<!s32i>, !cir.ptr<!s32i>, !cir.ptr<!s32i>) -> !cir.ptr<!s32i>>>, !cir.ptr<!s32i>, !cir.ptr<!s32i>, !cir.ptr<!s32i>) -> !cir.ptr<!s32i> + cir.return %r : !cir.ptr<!s32i> +} +// CHECK-LABEL: @indirect_caller +// CHECK-NOT: cir.std.find + +} diff --git a/clang/test/CIR/Transforms/idiom-recognizer.cpp b/clang/test/CIR/Transforms/idiom-recognizer.cpp index 28a2b502eb18a..f60d10f2ae4b1 100644 --- a/clang/test/CIR/Transforms/idiom-recognizer.cpp +++ b/clang/test/CIR/Transforms/idiom-recognizer.cpp @@ -1,2 +1,58 @@ // RUN: %clang_cc1 -fclangir -emit-cir -mmlir --mlir-print-ir-after-all -clangir-enable-idiom-recognizer %s -o %t.cir 2>&1 | FileCheck %s -check-prefix=CIR // CIR: IR Dump After IdiomRecognizer: cir-idiom-recognizer + +// The implicit-check-not on the RAISED run makes any surviving std::find call +// an error in the post-pass dump, so the test only passes if it was raised to +// cir.std.find. The FINAL run checks the lowered output and its implicit-check-not +// proves no raised operation leaked past LoweringPrepare. +// 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. + +namespace std { +template <class Iter, class T> +__attribute__((pure)) Iter find(Iter, Iter, const T &) noexcept; +} + +char *test_find(char *first, char *last, const char &value) { + return std::find(first, last, value); +} +// Raised to cir.std.find, then lowered back to the exact same call with its +// operands in source order and its attributes. +// RAISED: cir.std.find( +// RAISED-SAME: @_ZSt4findIPccET_S1_S1_RKT0_ +// FINAL: %[[FIRST_ADDR:.*]] = cir.alloca "first" +// FINAL: %[[LAST_ADDR:.*]] = cir.alloca "last" +// FINAL: %[[VALUE_ADDR:.*]] = cir.alloca "value" +// FINAL: %[[FIRST:.*]] = cir.load{{.*}} %[[FIRST_ADDR]] : +// FINAL: %[[LAST:.*]] = cir.load{{.*}} %[[LAST_ADDR]] : +// FINAL: %[[VALUE:.*]] = cir.load{{.*}} %[[VALUE_ADDR]] : +// FINAL: cir.call @_ZSt4findIPccET_S1_S1_RKT0_(%[[FIRST]], %[[LAST]], %[[VALUE]]) +// FINAL-SAME: nothrow side_effect(pure) +// FINAL-SAME: {llvm.noundef} +// FINAL-SAME: -> (!cir.ptr<!s8i> {llvm.noundef}) +// FINAL-NOT: cir.call @_ZSt4find + +// 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); +char *test_non_std_find(char *first, char *last, const char &value) { + return find(first, last, value); +} +// RAISED: cir.call @_Z4findPcS_RKc +// RAISED-NOT: cir.std.find +// FINAL: cir.call @_Z4findPcS_RKc + +// A member function named find in std is not std::find. The types are chosen +// so the call reaches the member exclusion itself. +namespace std { +struct string { + string *find(string *first, string *last); +}; +} + +std::string *test_member_find(std::string &s, std::string *f, std::string *l) { + return s.find(f, l); +} +// RAISED: cir.call @_ZNSt6string4findEPS_S0_ +// RAISED-NOT: cir.std.find +// FINAL: cir.call @_ZNSt6string4findEPS_S0_ diff --git a/clang/tools/cir-opt/cir-opt.cpp b/clang/tools/cir-opt/cir-opt.cpp index 6742985e149e8..22efa07cec8a0 100644 --- a/clang/tools/cir-opt/cir-opt.cpp +++ b/clang/tools/cir-opt/cir-opt.cpp @@ -67,6 +67,10 @@ int main(int argc, char **argv) { return mlir::createCXXABILoweringPass(); }); + ::mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { + return mlir::createIdiomRecognizerPass(); + }); + ::mlir::registerPass([]() -> std::unique_ptr<::mlir::Pass> { return mlir::createCallConvLoweringPass(); }); _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
