https://github.com/koparasy created 
https://github.com/llvm/llvm-project/pull/230123

Adds calling convention on the call sites. 

Note:  I opted to execlude support for runtime calling convention (classic 
getRuntimeCC()) on calls to runtime functions created in CIR passes (EH, 
__cxa_atexit, __cxa_guard_*, dynamic_cast, global init). These keep the default 
C calling convention and are marked with TODO(cir) and 
`MissingFeatures::opFuncCallingConv()` for a follow-up.

>From 6b265b223c2d102bc1b90eecc7c5dcafe686f4b6 Mon Sep 17 00:00:00 2001
From: Konstantinos Parasyris <[email protected]>
Date: Thu, 8 Oct 2026 05:44:41 -0700
Subject: [PATCH] [CIR] Emit calling convention on call sites

---
 .../CIR/Dialect/Builder/CIRBaseBuilder.h      | 15 +++--
 .../clang/CIR/Dialect/IR/CIRDialect.td        |  1 +
 clang/include/clang/CIR/Dialect/IR/CIROps.td  | 10 ++-
 .../clang/CIR/Interfaces/CIROpInterfaces.td   | 11 ++++
 clang/lib/CIR/CodeGen/CIRGenCXX.cpp           |  8 ++-
 clang/lib/CIR/CodeGen/CIRGenCall.cpp          | 29 ++++-----
 clang/lib/CIR/CodeGen/CIRGenExpr.cpp          |  5 +-
 clang/lib/CIR/CodeGen/CIRGenModule.cpp        |  1 +
 clang/lib/CIR/CodeGen/CIRGenVTables.cpp       |  5 +-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       | 19 ++++++
 .../CIR/Dialect/Transforms/CXXABILowering.cpp | 11 ++++
 .../CIR/Dialect/Transforms/EHABILowering.cpp  | 10 +++
 .../Dialect/Transforms/LoweringPrepare.cpp    | 31 +++++++--
 .../TargetLowering/LowerItaniumCXXABI.cpp     |  5 ++
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |  7 +-
 .../CIR/CodeGen/spir-call-calling-conv.cpp    | 65 +++++++++++++++++++
 .../CIR/CodeGenOpenCL/call-calling-conv.cl    | 31 +++++++++
 .../CodeGenSYCL/address-space-conversions.cpp | 34 +++++-----
 .../CIR/CodeGenSYCL/call-calling-conv.cpp     | 38 +++++++++++
 .../CIR/CodeGenSYCL/local-temporaries.cpp     | 22 +++----
 clang/test/CIR/IR/call-calling-conv.cir       | 39 +++++++++++
 clang/test/CIR/Lowering/call-calling-conv.cir | 40 ++++++++++++
 .../CIR/Transforms/flatten-preserve-attrs.cir | 20 ++++++
 23 files changed, 391 insertions(+), 66 deletions(-)
 create mode 100644 clang/test/CIR/CodeGen/spir-call-calling-conv.cpp
 create mode 100644 clang/test/CIR/CodeGenOpenCL/call-calling-conv.cl
 create mode 100644 clang/test/CIR/CodeGenSYCL/call-calling-conv.cpp
 create mode 100644 clang/test/CIR/IR/call-calling-conv.cir
 create mode 100644 clang/test/CIR/Lowering/call-calling-conv.cir

diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h 
b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 36d583cfe9fbe..badab8e711cfe 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -577,9 +577,12 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
                            mlir::Type returnType, mlir::ValueRange operands,
                            llvm::ArrayRef<mlir::NamedAttribute> attrs = {},
                            llvm::ArrayRef<mlir::NamedAttrList> argAttrs = {},
-                           llvm::ArrayRef<mlir::NamedAttribute> resAttrs = {}) 
{
+                           llvm::ArrayRef<mlir::NamedAttribute> resAttrs = {},
+                           cir::CallingConv callingConv = cir::CallingConv::C) 
{
     auto op = cir::CallOp::create(*this, loc, callee, returnType, operands);
     op->setAttrs(attrs);
+    if (callingConv != cir::CallingConv::C)
+      op.setCallingConv(callingConv);
 
     if (!argAttrs.empty()) {
       llvm::SmallVector<mlir::Attribute> argDictAttrs;
@@ -605,10 +608,11 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
                            mlir::ValueRange operands,
                            llvm::ArrayRef<mlir::NamedAttribute> attrs = {},
                            llvm::ArrayRef<mlir::NamedAttrList> argAttrs = {},
-                           llvm::ArrayRef<mlir::NamedAttribute> resAttrs = {}) 
{
+                           llvm::ArrayRef<mlir::NamedAttribute> resAttrs = {},
+                           cir::CallingConv callingConv = cir::CallingConv::C) 
{
     return createCallOp(loc, mlir::SymbolRefAttr::get(callee),
                         callee.getFunctionType().getReturnType(), operands,
-                        attrs, argAttrs, resAttrs);
+                        attrs, argAttrs, resAttrs, callingConv);
   }
 
   cir::CallOp
@@ -616,12 +620,13 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
                        cir::FuncType funcType, mlir::ValueRange operands,
                        llvm::ArrayRef<mlir::NamedAttribute> attrs = {},
                        llvm::ArrayRef<mlir::NamedAttrList> argAttrs = {},
-                       llvm::ArrayRef<mlir::NamedAttribute> resAttrs = {}) {
+                       llvm::ArrayRef<mlir::NamedAttribute> resAttrs = {},
+                       cir::CallingConv callingConv = cir::CallingConv::C) {
     llvm::SmallVector<mlir::Value> resOperands{indirectTarget};
     resOperands.append(operands.begin(), operands.end());
 
     return createCallOp(loc, mlir::SymbolRefAttr(), funcType.getReturnType(),
-                        resOperands, attrs, argAttrs, resAttrs);
+                        resOperands, attrs, argAttrs, resAttrs, callingConv);
   }
 
   cir::CallOp createCallOp(mlir::Location loc, mlir::SymbolRefAttr callee,
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td 
b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
index 70647fa194329..2086646949a11 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRDialect.td
@@ -47,6 +47,7 @@ def CIR_Dialect : Dialect {
     static llvm::StringRef getIntTypeWidthAttrName() { return 
"cir.int_type_width"; }
     static llvm::StringRef getOptInfoAttrName() { return "cir.opt_info"; }
     static llvm::StringRef getCalleeAttrName() { return "callee"; }
+    static llvm::StringRef getCallingConvAttrName() { return "calling_conv"; }
     static llvm::StringRef getNoThrowAttrName() { return "nothrow"; }
     static llvm::StringRef getNoReturnAttrName() { return "noreturn"; }
     static llvm::StringRef getMemoryEffectsAttrName() { return 
"memory_effects"; }
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td 
b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 7b7b5e1412476..4897b4bb5872b 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -4682,7 +4682,9 @@ class CIR_CallOpBase<string mnemonic, list<Trait> 
extra_traits = []>
       UnitAttr:$musttail,
       OptionalAttr<CIR_MemoryEffectsAttr>:$memory_effects,
       OptionalAttr<DictArrayAttr>:$arg_attrs,
-      OptionalAttr<DictArrayAttr>:$res_attrs
+      OptionalAttr<DictArrayAttr>:$res_attrs,
+      DefaultValuedAttr<CIR_CallingConvAttr, "cir::CallingConv::C">
+        :$calling_conv
       );
 }
 
@@ -4701,10 +4703,16 @@ def CIR_CallOp : CIR_CallOpBase<"call", 
[NoRegionArguments]> {
     empty. The first operand of this operation must be a pointer to the callee
     function. The rest operands are arguments to the callee function.
 
+    The `calling_conv` attribute specifies the calling convention used at the
+    call site. It defaults to `CallingConv::C`, which is omitted when printed.
+    Other calling conventions are printed as `cc(<mnemonic>)`, e.g.
+    `cc(spir_function)`.
+
     Example:
 
     ```
     %0 = cir.call @foo()
+    cir.call @bar() cc(spir_function) : () -> ()
     ```
   }];
 
diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td 
b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
index 52aa1b9fe1b43..1563fc5aa00da 100644
--- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
@@ -73,6 +73,17 @@ let cppNamespace = "::cir" in {
                       /*defaultImplementation=*/[{
                         return $_op.getInlineKindAttrName();
                       }]>,
+      InterfaceMethod<"Return the calling convention of the call site",
+                      "cir::CallingConv", "getCallingConv", (ins), [{}],
+                      /*defaultImplementation=*/[{
+                        return $_op.getCallingConv();
+                      }]>,
+      InterfaceMethod<"Set the calling convention of the call site",
+                      "void", "setCallingConv",
+                      (ins "cir::CallingConv":$callingConv), [{}],
+                      /*defaultImplementation=*/[{
+                        $_op.setCallingConv(callingConv);
+                      }]>,
     ];
   }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
index eae118ddf1104..e842500e5ba56 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp
@@ -206,9 +206,11 @@ static void emitDeclDestroy(CIRGenFunction &cgf, const 
VarDecl *vd,
         mlir::cast<cir::PointerType>(thisAddr.getType()).getAddrSpace());
     if (realPtrTy != thisAddr.getType())
       thisAddr = builder.createBitcast(thisAddr.getLoc(), thisAddr, realPtrTy);
-    builder.createCallOp(cgf.getLoc(vd->getSourceRange()),
-                         mlir::FlatSymbolRefAttr::get(fnOp.getSymNameAttr()),
-                         mlir::ValueRange{thisAddr});
+    // Make sure the call and the callee agree on calling convention.
+    builder.createCallOp(cgf.getLoc(vd->getSourceRange()), fnOp,
+                         mlir::ValueRange{thisAddr}, /*attrs=*/{},
+                         /*argAttrs=*/{}, /*resAttrs=*/{},
+                         fnOp.getCallingConv());
     assert(fnOp && "expected cir.func");
     // TODO(cir): This doesn't do anything but check for unhandled conditions.
     // What it is meant to do should really be happening in LoweringPrepare.
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp 
b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 4f4e10aa218be..6e67dc1372297 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -1165,14 +1165,13 @@ void CIRGenFunction::emitNonNullArgCheck(RValue rv, 
QualType argType,
   cgm.errorNYI("non-null arg check is NYI");
 }
 
-static cir::CIRCallOpInterface
-emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc,
-               cir::FuncType indirectFuncTy, mlir::Value indirectFuncVal,
-               cir::FuncOp directFuncOp,
-               const SmallVectorImpl<mlir::Value> &cirCallArgs, bool isInvoke,
-               const mlir::NamedAttrList &attrs,
-               llvm::ArrayRef<mlir::NamedAttrList> argAttrs,
-               const mlir::NamedAttrList &retAttrs) {
+static cir::CIRCallOpInterface emitCallLikeOp(
+    CIRGenFunction &cgf, mlir::Location callLoc, cir::FuncType indirectFuncTy,
+    mlir::Value indirectFuncVal, cir::FuncOp directFuncOp,
+    const SmallVectorImpl<mlir::Value> &cirCallArgs, bool isInvoke,
+    const mlir::NamedAttrList &attrs,
+    llvm::ArrayRef<mlir::NamedAttrList> argAttrs,
+    const mlir::NamedAttrList &retAttrs, cir::CallingConv callingConv) {
   CIRGenBuilderTy &builder = cgf.getBuilder();
 
   assert(!cir::MissingFeatures::opCallSurroundingTry());
@@ -1181,13 +1180,12 @@ emitCallLikeOp(CIRGenFunction &cgf, mlir::Location 
callLoc,
 
   cir::CallOp op;
   if (indirectFuncTy) {
-    // TODO(cir): Set calling convention for indirect calls.
-    assert(!cir::MissingFeatures::opCallCallConv());
     op = builder.createIndirectCallOp(callLoc, indirectFuncVal, indirectFuncTy,
-                                      cirCallArgs, attrs, argAttrs, retAttrs);
+                                      cirCallArgs, attrs, argAttrs, retAttrs,
+                                      callingConv);
   } else {
     op = builder.createCallOp(callLoc, directFuncOp, cirCallArgs, attrs,
-                              argAttrs, retAttrs);
+                              argAttrs, retAttrs, callingConv);
   }
 
   return op;
@@ -1341,7 +1339,6 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo 
&funcInfo,
   if (auto calleeFuncOp = dyn_cast<cir::FuncOp>(calleePtr))
     funcName = calleeFuncOp.getName();
 
-  assert(!cir::MissingFeatures::opCallCallConv());
   assert(!cir::MissingFeatures::opCallAttrs());
   cir::CallingConv callingConv;
   cgm.constructAttributeList(funcName, funcInfo, callee.getAbstractInfo(),
@@ -1405,9 +1402,9 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo 
&funcInfo,
   bool isInvoke = !cannotThrow && isCatchOrCleanupRequired();
 
   mlir::Location callLoc = loc;
-  cir::CIRCallOpInterface theCall =
-      emitCallLikeOp(*this, loc, indirectFuncTy, indirectFuncVal, directFuncOp,
-                     cirCallArgs, isInvoke, attrs, argAttrs, retAttrs);
+  cir::CIRCallOpInterface theCall = emitCallLikeOp(
+      *this, loc, indirectFuncTy, indirectFuncVal, directFuncOp, cirCallArgs,
+      isInvoke, attrs, argAttrs, retAttrs, callingConv);
 
   if (callOp)
     *callOp = theCall;
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp 
b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index c03f7b355b695..8948c0723fbc2 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -2026,7 +2026,10 @@ static void pushTemporaryCleanup(CIRGenFunction &cgf,
     } else {
       GlobalDecl gd(referenceTemporaryDtor, Dtor_Complete);
       cir::FuncOp dtorFn = cgm.getAddrAndTypeOfCXXStructor(gd).second;
-      builder.createCallOp(loc, dtorFn, mlir::ValueRange{tempAddr});
+      // Make sure the call and the callee agree on calling convention.
+      builder.createCallOp(loc, dtorFn, mlir::ValueRange{tempAddr},
+                           /*attrs=*/{}, /*argAttrs=*/{}, /*resAttrs=*/{},
+                           dtorFn.getCallingConv());
     }
     break;
   }
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp 
b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 1b17ad636829d..793f81c3b0952 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -2156,6 +2156,7 @@ void 
CIRGenModule::replaceUsesOfNonProtoTypeWithRealFunction(
         realCallOp = builder.createIndirectCallOp(
             noProtoCallOp.getLoc(), casted, callFnType, callOperands);
       }
+      realCallOp.setCallingConv(noProtoCallOp.getCallingConv());
 
       // Replace old no proto call with fixed call.
       noProtoCallOp.replaceAllUsesWith(realCallOp);
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp 
b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index f4c386762a1d3..12a7e9a2a66e0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -829,7 +829,10 @@ void CIRGenFunction::emitMustTailThunk(GlobalDecl gd,
   cir::FuncType calleeTy = callee.getFunctionType();
   mlir::Type retTy = calleeTy.getReturnType();
 
-  cir::CallOp call = builder.createCallOp(loc, callee, args);
+  assert(!cir::MissingFeatures::opCallAttrs());
+  cir::CallOp call =
+      builder.createCallOp(loc, callee, args, /*attrs=*/{}, /*argAttrs=*/{},
+                           /*resAttrs=*/{}, curFnInfo->getCallingConvention());
   call->setAttr(cir::CIRDialect::getMustTailAttrName(),
                 mlir::UnitAttr::get(builder.getContext()));
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp 
b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 55654daab782a..7fc3d2d17a8a3 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1388,6 +1388,20 @@ static mlir::ParseResult 
parseCallCommon(mlir::OpAsmParser &parser,
     return ::mlir::failure();
   }
 
+  if (parser.parseOptionalKeyword("cc").succeeded()) {
+    cir::CallingConv callingConv;
+    if (parser.parseLParen().failed())
+      return failure();
+    if (parseCIRKeyword<cir::CallingConv>(parser, callingConv).failed())
+      return parser.emitError(parser.getCurrentLocation(),
+                              "unknown calling convention");
+    if (parser.parseRParen().failed())
+      return failure();
+    result.addAttribute(
+        CIRDialect::getCallingConvAttrName(),
+        cir::CallingConvAttr::get(parser.getContext(), callingConv));
+  }
+
   if (parser.parseOptionalKeyword("musttail").succeeded())
     result.addAttribute(CIRDialect::getMustTailAttrName(),
                         mlir::UnitAttr::get(parser.getContext()));
@@ -1495,6 +1509,10 @@ static void printCallCommon(mlir::Operation *op,
     printer << tryCall.getUnwindDest();
   }
 
+  cir::CallingConv callingConv = callLikeOp.getCallingConv();
+  if (callingConv != cir::CallingConv::C)
+    printer << " cc(" << stringifyCallingConv(callingConv) << ")";
+
   if (op->hasAttr(CIRDialect::getMustTailAttrName()))
     printer << " musttail";
 
@@ -1509,6 +1527,7 @@ static void printCallCommon(mlir::Operation *op,
 
   llvm::StringRef elidedAttrs[] = {
       CIRDialect::getCalleeAttrName(),
+      CIRDialect::getCallingConvAttrName(),
       CIRDialect::getMustTailAttrName(),
       CIRDialect::getNoThrowAttrName(),
       CIRDialect::getNoUnwindAttrName(),
diff --git a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp 
b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
index af25d662dbb5d..a6bf33fddafc7 100644
--- a/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CXXABILowering.cpp
@@ -702,6 +702,15 @@ mlir::LogicalResult 
CIRDeleteArrayOpABILowering::matchAndRewrite(
   // exception handling flow will be connected to the cleanup region here to
   // call the delete operator on the exception path.
   mlir::FlatSymbolRefAttr dtorFn = op.getElementDtorAttr();
+
+  // Make sure the calls and the callees agree on calling convention.
+  auto getCalleeCallingConv = [&](mlir::FlatSymbolRefAttr callee) {
+    if (auto fn =
+            mlir::SymbolTable::lookupNearestSymbolFrom<cir::FuncOp>(op, 
callee))
+      return fn.getCallingConv();
+    return cir::CallingConv::C;
+  };
+
   cir::CleanupKind cleanupKind =
       op.getDtorMayThrow() ? cir::CleanupKind::All : cir::CleanupKind::Normal;
   cir::CleanupScopeOp::create(
@@ -717,6 +726,7 @@ mlir::LogicalResult 
CIRDeleteArrayOpABILowering::matchAndRewrite(
                     bb.getInsertionBlock()->addArgument(eltPtrTy, ll);
                 auto dtorCall = cir::CallOp::create(
                     bb, ll, dtorFn, cir::VoidType(), mlir::ValueRange{arg});
+                dtorCall.setCallingConv(getCalleeCallingConv(dtorFn));
                 if (!op.getDtorMayThrow())
                   dtorCall.setNothrowAttr(bb.getUnitAttr());
                 cir::YieldOp::create(bb, ll);
@@ -750,6 +760,7 @@ mlir::LogicalResult 
CIRDeleteArrayOpABILowering::matchAndRewrite(
 
         auto deleteCall =
             cir::CallOp::create(b, l, deleteFn, cir::VoidType(), callArgs);
+        deleteCall.setCallingConv(getCalleeCallingConv(deleteFn));
         // operator delete[] is implicitly nothrow per [basic.stc.dynamic],
         // matching classic CodeGen's `nounwind` attribute on the call.
         deleteCall.setNothrowAttr(b.getUnitAttr());
diff --git a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp 
b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
index 77ce7434c8ed3..8e458d0c353a9 100644
--- a/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/EHABILowering.cpp
@@ -218,6 +218,8 @@ void ItaniumEHLowering::ensureRuntimeDecls(mlir::Location 
loc) {
 ///     unreachable;
 ///   }
 void ItaniumEHLowering::ensureClangCallTerminate(mlir::Location loc) {
+  // TODO(cir): set the runtime calling convention on the runtime calls below.
+  assert(!cir::MissingFeatures::opFuncCallingConv());
   if (clangCallTerminateFunc)
     return;
 
@@ -299,6 +301,8 @@ void 
ItaniumEHLowering::ensureCxaCallUnexpectedDecl(mlir::Location loc) {
 /// Create a terminate landing pad block at the end of the specified function.
 mlir::Block *ItaniumEHLowering::buildTerminateBlock(cir::FuncOp funcOp,
                                                     mlir::Location loc) {
+  // TODO(cir): set the runtime calling convention on the runtime calls below.
+  assert(!cir::MissingFeatures::opFuncCallingConv());
   assert(clangCallTerminateFunc &&
          "ensureClangCallTerminate must run before buildTerminateBlock");
   mlir::Region &body = funcOp.getRegion();
@@ -492,6 +496,8 @@ mlir::LogicalResult ItaniumEHLowering::lowerEhInitiate(
     cir::EhInitiateOp initiateOp,
     llvm::ArrayRef<cir::EhDispatchOp> reachedDispatches, bool reachesCleanup,
     EhTokenMap &ehTokenMap) {
+  // TODO(cir): set the runtime calling convention on the runtime calls below.
+  assert(!cir::MissingFeatures::opFuncCallingConv());
   mlir::Value rootToken = initiateOp.getEhToken();
 
   // The catch clauses for this landing pad come from the dispatches its
@@ -823,6 +829,8 @@ 
ItaniumEHLowering::resolveCatchCopyThunk(cir::ConstructCatchParamOp op) {
 mlir::LogicalResult
 ItaniumEHLowering::lowerConstructCatchParam(cir::ConstructCatchParamOp op,
                                             mlir::Value exnPtr) {
+  // TODO(cir): set the runtime calling convention on the runtime calls below.
+  assert(!cir::MissingFeatures::opFuncCallingConv());
   mlir::Location loc = op.getLoc();
   mlir::Value paramAddr = op.getParamAddr();
   cir::PointerType paramAddrType =
@@ -910,6 +918,8 @@ 
ItaniumEHLowering::lowerConstructCatchParam(cir::ConstructCatchParamOp op,
 /// type_info and dtor pointers from their symbol attributes, bitcasting
 /// each to !cir.ptr<!void> as required by the runtime function signature.
 mlir::LogicalResult ItaniumEHLowering::lowerTryThrow(cir::TryThrowOp op) {
+  // TODO(cir): set the runtime calling convention on the runtime calls below.
+  assert(!cir::MissingFeatures::opFuncCallingConv());
   mlir::Location loc = op.getLoc();
   mlir::Block *normalDest = op.getNormalDest();
   mlir::Block *unwindDest = op.getUnwindDest();
diff --git a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp 
b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
index d90f65f64e61b..be761e91e90c5 100644
--- a/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/LoweringPrepare.cpp
@@ -400,6 +400,9 @@ struct LoweringPreparePass
                                    cir::GlobalOp global,
                                    mlir::Region &dtorRegion, bool tls,
                                    mlir::Block &entryBB) {
+    // TODO(cir): set the runtime calling convention on the __cxa_atexit call.
+    assert(!cir::MissingFeatures::opFuncCallingConv());
+
     // Create a variable that binds the atexit to this shared object.
     builder.setInsertionPointToStart(&mlirModule.getBodyRegion().front());
     cir::GlobalOp handle = getOrCreateRuntimeVariable(
@@ -466,6 +469,9 @@ struct LoweringPreparePass
                             cir::PointerType guardPtrTy, bool threadsafe) {
     auto loc = globalOp->getLoc();
 
+    // TODO(cir): set the runtime calling convention on the __cxa_guard_* 
calls.
+    assert(!cir::MissingFeatures::opFuncCallingConv());
+
     // The semantics of dynamic initialization of variables with static or
     // thread storage duration depends on whether they are declared at
     // block-scope. The initialization of such variables at block-scope can be
@@ -737,6 +743,8 @@ static mlir::Value buildComplexBinOpLibCall(
     libFunc = pass.buildRuntimeFunction(builder, libFuncName, loc, libFuncTy);
   }
 
+  // TODO(cir): set the runtime calling convention to this call.
+  assert(!cir::MissingFeatures::opFuncCallingConv());
   cir::CallOp call =
       builder.createCallOp(loc, libFunc, {lhsReal, lhsImag, rhsReal, rhsImag});
   return call.getResult();
@@ -1239,7 +1247,9 @@ cir::FuncOp 
LoweringPreparePass::getOrCreateDtorFunc(CIRBaseBuilderTy &builder,
       mlir::cast<cir::GetGlobalOp>(dtorBlock.getOperations().front());
   builder.setInsertionPointAfter(origGGop);
   mlir::Value ggopResult = origGGop.getResult();
-  dtorCall = builder.createCallOp(op.getLoc(), dtorFunc, ggopResult);
+  dtorCall = builder.createCallOp(op.getLoc(), dtorFunc, ggopResult,
+                                  /*attrs=*/{}, /*argAttrs=*/{},
+                                  /*resAttrs=*/{}, dtorFunc.getCallingConv());
 
   // Add a yield after the call.
   auto finalYield = cir::YieldOp::create(builder, op.getLoc());
@@ -1722,13 +1732,18 @@ void 
LoweringPreparePass::defineGlobalThreadLocalWrapper(cir::GlobalOp op,
           builder, aliasLoc, cir::CmpOpKind::ne, funcLoad, nullCheck);
       cir::IfOp::create(builder, aliasLoc, cmp, /*withElseRegion=*/false,
                         [&](mlir::OpBuilder &, mlir::Location loc) {
-                          builder.createCallOp(aliasLoc, initAlias, {});
+                          builder.createCallOp(aliasLoc, initAlias, {},
+                                               /*attrs=*/{},
+                                               /*argAttrs=*/{}, 
/*resAttrs=*/{},
+                                               initAlias.getCallingConv());
                           cir::YieldOp::create(builder, aliasLoc);
                         });
     } else {
       // If this IS a definition, we know the alias exists, so we can just emit
       // a call to it.
-      builder.createCallOp(aliasLoc, initAlias, {});
+      builder.createCallOp(aliasLoc, initAlias, {}, /*attrs=*/{},
+                           /*argAttrs=*/{}, /*resAttrs=*/{},
+                           initAlias.getCallingConv());
     }
   }
   cir::GetGlobalOp get = builder.createGetGlobal(op, /*tls=*/true);
@@ -1908,7 +1923,8 @@ void LoweringPreparePass::lowerGetGlobalOp(GetGlobalOp 
op) {
   cir::CallOp call = builder.createCallOp(
       wrapperFunc.getLoc(),
       mlir::FlatSymbolRefAttr::get(wrapperFunc.getSymNameAttr()),
-      wrapperFunc.getFunctionType().getReturnType(), {});
+      wrapperFunc.getFunctionType().getReturnType(), {}, /*attrs=*/{},
+      /*argAttrs=*/{}, /*resAttrs=*/{}, wrapperFunc.getCallingConv());
   op->replaceAllUsesWith(call);
   op.erase();
 }
@@ -2061,7 +2077,8 @@ void LoweringPreparePass::buildCXXGlobalTlsFunc() {
   // Emit the body of the guarded spot.
   builder.setInsertionPointToEnd(&ifOperation.getThenRegion().front());
   for (cir::FuncOp initFunc : globalThreadLocalInitializers)
-    builder.createCallOp(loc, initFunc, {});
+    builder.createCallOp(loc, initFunc, {}, /*attrs=*/{}, /*argAttrs=*/{},
+                         /*resAttrs=*/{}, initFunc.getCallingConv());
   cir::YieldOp::create(builder, loc);
 
   builder.setInsertionPointAfter(ifOperation);
@@ -2089,7 +2106,9 @@ cir::FuncOp 
LoweringPreparePass::buildGlobalInitCallerFunc(
                                         fnType, linkage);
   builder.setInsertionPointToStart(fn.addEntryBlock());
   for (cir::FuncOp init : initializers)
-    builder.createCallOp(init.getLoc(), init, {});
+    builder.createCallOp(init.getLoc(), init, {}, /*attrs=*/{},
+                         /*argAttrs=*/{}, /*resAttrs=*/{},
+                         init.getCallingConv());
   cir::ReturnOp::create(builder, fn.getLoc());
   globalCtorList.emplace_back(fnName, priority);
   return fn;
diff --git 
a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp 
b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
index 26dbe40c2c93f..aef66b0468490 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerItaniumCXXABI.cpp
@@ -705,6 +705,8 @@ mlir::Value LowerItaniumCXXABI::lowerMethodToBoolCast(
 
 static void buildBadCastCall(mlir::OpBuilder &builder, mlir::Location loc,
                              mlir::FlatSymbolRefAttr badCastFuncRef) {
+  // TODO(cir): set the runtime calling convention to this call.
+  assert(!cir::MissingFeatures::opFuncCallingConv());
   auto callOp = cir::CallOp::create(builder, loc, badCastFuncRef,
                                     /*resType=*/cir::VoidType(),
                                     /*operands=*/mlir::ValueRange{});
@@ -739,6 +741,9 @@ static mlir::Value 
buildDynamicCastAfterNullCheck(cir::DynamicCastOp op,
   mlir::FlatSymbolRefAttr dynCastFuncRef = castInfo.getRuntimeFunc();
   mlir::Value dynCastFuncArgs[4] = {srcPtr, srcRtti, destRtti, offsetHint};
 
+  // TODO(cir): set the runtime calling convention to this call.
+  assert(!cir::MissingFeatures::opFuncCallingConv());
+
   mlir::Value castedPtr = cir::CallOp::create(builder, loc, dynCastFuncRef,
                                               voidPtrTy, dynCastFuncArgs)
                               .getResult();
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp 
b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index a254d79836229..4cb0935fd54a3 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2279,8 +2279,6 @@ static mlir::LogicalResult rewriteCallOrInvoke(
   if (converter->convertTypes(cirResults, llvmResults).failed())
     return mlir::failure();
 
-  assert(!cir::MissingFeatures::opCallCallConv());
-
   mlir::LLVM::MemoryEffectsAttr memoryEffects;
   bool noUnwind = false;
   bool willReturn = false;
@@ -2344,17 +2342,19 @@ static mlir::LogicalResult rewriteCallOrInvoke(
         converter->convertType(calleeFuncTy));
   }
 
-  assert(!cir::MissingFeatures::opCallCallConv());
+  mlir::LLVM::CConv cconv = convertCallingConv(call.getCallingConv());
 
   if (landingPadBlock) {
     assert(!cir::MissingFeatures::opCallInvokeAttrs());
     auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::InvokeOp>(
         op, llvmFnTy, calleeAttr, callOperands, continueBlock,
         mlir::ValueRange{}, landingPadBlock, mlir::ValueRange{});
+    newOp.setCConv(cconv);
     setLoweredCallAttributes(newOp, attributes);
   } else {
     auto newOp = rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(
         op, llvmFnTy, calleeAttr, callOperands);
+    newOp.setCConv(cconv);
     setLoweredCallAttributes(newOp, attributes);
     if (memoryEffects)
       newOp.setMemoryEffectsAttr(memoryEffects);
@@ -2385,7 +2385,6 @@ mlir::LogicalResult 
CIRToLLVMCallOpLowering::matchAndRewrite(
 mlir::LogicalResult CIRToLLVMTryCallOpLowering::matchAndRewrite(
     cir::TryCallOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
-  assert(!cir::MissingFeatures::opCallCallConv());
   return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), 
rewriter,
                              getTypeConverter(), dataLayout, symbolTables,
                              op.getCalleeAttr(), op.getNormalDest(),
diff --git a/clang/test/CIR/CodeGen/spir-call-calling-conv.cpp 
b/clang/test/CIR/CodeGen/spir-call-calling-conv.cpp
new file mode 100644
index 0000000000000..af36535e440cb
--- /dev/null
+++ b/clang/test/CIR/CodeGen/spir-call-calling-conv.cpp
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -triple spir64 -disable-llvm-passes -fclangir -emit-cir 
-mmlir --mlir-print-ir-before=cir-lowering-prepare %s -o %t.cir 2>&1 | 
FileCheck %s --check-prefix=CIR-BEFORE-LPP
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple spir64 -disable-llvm-passes -fclangir -emit-llvm %s 
-o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM,LLVM-CIR
+// RUN: %clang_cc1 -triple spir64 -disable-llvm-passes -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=LLVM,OGCG
+
+// Calls that ClangIR builds directly to C++ functions on SPIR use the callee's
+// spir_func calling convention.
+
+struct S { S(); ~S(); int x; };
+
+// Global variable and reference temporary destructors.
+S g;
+const S &r = S();
+
+// CIR-BEFORE-LPP: cir.global external @g = ctor : !rec_S {
+// CIR-BEFORE-LPP:   cir.call @_ZN1SC1Ev(%{{.*}}) cc(spir_function)
+// CIR-BEFORE-LPP: } dtor {
+// CIR-BEFORE-LPP:   cir.call @_ZN1SD1Ev(%{{.*}}) cc(spir_function)
+// CIR-BEFORE-LPP: cir.global external @r = ctor : !cir.ptr<!rec_S> {
+// CIR-BEFORE-LPP:   cir.call @_ZN1SC1Ev(%{{.*}}) cc(spir_function)
+// CIR-BEFORE-LPP: } dtor {
+// CIR-BEFORE-LPP:   cir.call @_ZN1SD1Ev(%{{.*}}) cc(spir_function)
+
+// TODO(cir): The global init functions and the __cxa_atexit call do not use
+// the runtime calling convention yet.
+// LLVM-CIR: define internal void @__cxx_global_var_init()
+// OGCG:     define internal spir_func void @__cxx_global_var_init()
+// LLVM:       call spir_func void @_ZN1SC1Ev(ptr noundef nonnull align 4 
dereferenceable(4) @g)
+// LLVM-CIR:   call i32 @__cxa_atexit(ptr @_ZN1SD1Ev, ptr @g, ptr 
@__dso_handle)
+// OGCG:       call spir_func i32 @__cxa_atexit(ptr @_ZN1SD1Ev, ptr @g, ptr 
@__dso_handle)
+// LLVM-CIR: define internal void @__cxx_global_var_init.1()
+// OGCG:     define internal spir_func void @__cxx_global_var_init.1()
+// LLVM:       call spir_func void @_ZN1SC1Ev(ptr noundef nonnull align 4 
dereferenceable(4) @_ZGR1r_)
+// LLVM-CIR:   call i32 @__cxa_atexit(ptr @_ZN1SD1Ev, ptr @_ZGR1r_, ptr 
@__dso_handle)
+// OGCG:       call spir_func i32 @__cxa_atexit(ptr @_ZN1SD1Ev, ptr @_ZGR1r_, 
ptr @__dso_handle)
+
+// Array delete: the element destructor and operator delete[].
+void del(S *p) { delete[] p; }
+
+// CIR: cir.func {{.*}}@_Z3delP1S({{.*}}) cc(spir_function)
+// CIR:   cir.call @_ZN1SD1Ev(%{{.*}}) cc(spir_function) nothrow
+// CIR:   cir.call @_ZdaPvm(%{{.*}}, %{{.*}}) cc(spir_function) nothrow
+
+// LLVM: define dso_local spir_func void @_Z3delP1S(
+// LLVM:   call spir_func void @_ZN1SD1Ev(ptr {{.*}}%{{.*}})
+// LLVM:   call spir_func void @_ZdaPvm(ptr {{.*}}%{{.*}}, i64 {{.*}}%{{.*}})
+
+// This-adjusting thunk.
+struct A { virtual void f(); int a; };
+struct D { virtual void g(); int d; };
+struct E : A, D { void g() override; };
+void E::g() {}
+
+// CIR: cir.func {{.*}}@_ZThn16_N1E1gEv({{.*}}) cc(spir_function)
+// CIR:   cir.call @_ZN1E1gEv(%{{.*}}) cc(spir_function)
+
+// LLVM: define dso_local spir_func void @_ZThn16_N1E1gEv(
+// LLVM:   call spir_func void @_ZN1E1gEv(ptr noundef nonnull align 8 
dereferenceable(28) %{{.*}})
+
+// LLVM-CIR: define internal void @_GLOBAL__sub_I_spir_call_calling_conv.cpp()
+// OGCG:     define internal spir_func void 
@_GLOBAL__sub_I_spir_call_calling_conv.cpp()
+// LLVM-CIR:   call void @__cxx_global_var_init()
+// OGCG:       call spir_func void @__cxx_global_var_init()
diff --git a/clang/test/CIR/CodeGenOpenCL/call-calling-conv.cl 
b/clang/test/CIR/CodeGenOpenCL/call-calling-conv.cl
new file mode 100644
index 0000000000000..f1b19bea41699
--- /dev/null
+++ b/clang/test/CIR/CodeGenOpenCL/call-calling-conv.cl
@@ -0,0 +1,31 @@
+// RUN: %clang_cc1 -triple spir64 -cl-std=CL2.0 -disable-llvm-passes -fclangir 
-emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple spir64 -cl-std=CL2.0 -disable-llvm-passes -fclangir 
-emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefix=LLVM
+// RUN: %clang_cc1 -triple spir64 -cl-std=CL2.0 -disable-llvm-passes 
-emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=LLVM,OGCG
+
+// Calls to non-kernel functions on SPIR use the spir_func calling convention.
+
+int helper(global int *p) { return *p + 1; }
+void store(global int *p, int v) { *p = v; }
+
+kernel void k(global int *p) { store(p, helper(p)); }
+
+// CIR: cir.func {{.*}}@helper({{.*}}) -> !s32i cc(spir_function)
+// CIR: cir.func {{.*}}@store({{.*}}) cc(spir_function)
+// CIR: cir.func {{.*}}@k({{.*}}) cc(spir_kernel)
+// CIR:   %[[R:.*]] = cir.call @helper(%{{.*}}) cc(spir_function)
+// CIR:   cir.call @store(%{{.*}}, %[[R]]) cc(spir_function)
+
+// LLVM: define dso_local spir_func i32 @helper(
+// LLVM: define dso_local spir_func void @store(
+// LLVM: define dso_local spir_kernel void @k(
+
+// TODO(cir): CIR does not yet emit the __clang_ocl_kern_imp_ kernel stub, so
+// classic CodeGen calls helper/store from there.
+// OGCG:   call spir_func void @__clang_ocl_kern_imp_k(
+// OGCG: define dso_local spir_func void @__clang_ocl_kern_imp_k(
+
+// LLVM:   %[[R:.*]] = call spir_func i32 @helper(ptr addrspace(1) noundef 
%{{.*}})
+// LLVM:   call spir_func void @store(ptr addrspace(1) noundef %{{.*}}, i32 
noundef %[[R]])
diff --git a/clang/test/CIR/CodeGenSYCL/address-space-conversions.cpp 
b/clang/test/CIR/CodeGenSYCL/address-space-conversions.cpp
index b2a5468adaa61..6519caebfbd98 100644
--- a/clang/test/CIR/CodeGenSYCL/address-space-conversions.cpp
+++ b/clang/test/CIR/CodeGenSYCL/address-space-conversions.cpp
@@ -8,8 +8,6 @@
 // Port of clang/test/CodeGenSYCL/address-space-conversions.cpp. Local
 // variables and parameters are allocated in the private address space and
 // accessed through the generic address space.
-//
-// TODO(cir): Calls are missing the spir_func calling convention.
 
 void bar(int &Data) {}
 void bar2(int &Data) {}
@@ -148,42 +146,42 @@ void tmpl(T t) {}
 // LLVM-CIR-NEXT: store ptr addrspace(1) %[[TMP15]], ptr addrspace(4) 
%[[GLOB_ASCAST]], align 8
 // LLVM-CIR-NEXT: %[[TMP16:.*]] = load ptr addrspace(1), ptr addrspace(4) 
%[[GLOB_ASCAST]], align 8
 // LLVM-CIR-NEXT: %[[TMP17:.*]] = addrspacecast ptr addrspace(1) %[[TMP16]] to 
ptr addrspace(4)
-// LLVM-CIR-NEXT: call void @_Z3barRi(ptr addrspace(4) noundef align 4 
dereferenceable(4) %[[TMP17]])
+// LLVM-CIR-NEXT: call spir_func void @_Z3barRi(ptr addrspace(4) noundef align 
4 dereferenceable(4) %[[TMP17]])
 // LLVM-CIR-NEXT: %[[TMP18:.*]] = load ptr addrspace(1), ptr addrspace(4) 
%[[GLOB_ASCAST]], align 8
 // LLVM-CIR-NEXT: %[[TMP19:.*]] = addrspacecast ptr addrspace(1) %[[TMP18]] to 
ptr addrspace(4)
-// LLVM-CIR-NEXT: call void @_Z4bar2Ri(ptr addrspace(4) noundef align 4 
dereferenceable(4) %[[TMP19]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4bar2Ri(ptr addrspace(4) noundef 
align 4 dereferenceable(4) %[[TMP19]])
 // LLVM-CIR-NEXT: %[[TMP20:.*]] = load ptr addrspace(3), ptr addrspace(4) 
%[[LOC_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z3barRU3AS3i(ptr addrspace(3) noundef align 4 
dereferenceable(4) %[[TMP20]])
+// LLVM-CIR-NEXT: call spir_func void @_Z3barRU3AS3i(ptr addrspace(3) noundef 
align 4 dereferenceable(4) %[[TMP20]])
 // LLVM-CIR-NEXT: %[[TMP21:.*]] = load ptr addrspace(3), ptr addrspace(4) 
%[[LOC_ASCAST]], align 8
 // LLVM-CIR-NEXT: %[[TMP22:.*]] = addrspacecast ptr addrspace(3) %[[TMP21]] to 
ptr addrspace(4)
-// LLVM-CIR-NEXT: call void @_Z4bar2Ri(ptr addrspace(4) noundef align 4 
dereferenceable(4) %[[TMP22]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4bar2Ri(ptr addrspace(4) noundef 
align 4 dereferenceable(4) %[[TMP22]])
 // LLVM-CIR-NEXT: %[[TMP23:.*]] = load ptr addrspace(4), ptr addrspace(4) 
%[[NOAS_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z3barRi(ptr addrspace(4) noundef align 4 
dereferenceable(4) %[[TMP23]])
+// LLVM-CIR-NEXT: call spir_func void @_Z3barRi(ptr addrspace(4) noundef align 
4 dereferenceable(4) %[[TMP23]])
 // LLVM-CIR-NEXT: %[[TMP24:.*]] = load ptr addrspace(4), ptr addrspace(4) 
%[[NOAS_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z4bar2Ri(ptr addrspace(4) noundef align 4 
dereferenceable(4) %[[TMP24]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4bar2Ri(ptr addrspace(4) noundef 
align 4 dereferenceable(4) %[[TMP24]])
 // LLVM-CIR-NEXT: %[[TMP25:.*]] = load ptr addrspace(1), ptr addrspace(4) 
%[[GLOB_ASCAST]], align 8
 // LLVM-CIR-NEXT: %[[TMP26:.*]] = addrspacecast ptr addrspace(1) %[[TMP25]] to 
ptr addrspace(4)
-// LLVM-CIR-NEXT: call void @_Z3fooPi(ptr addrspace(4) noundef %[[TMP26]])
+// LLVM-CIR-NEXT: call spir_func void @_Z3fooPi(ptr addrspace(4) noundef 
%[[TMP26]])
 // LLVM-CIR-NEXT: %[[TMP27:.*]] = load ptr addrspace(1), ptr addrspace(4) 
%[[GLOB_ASCAST]], align 8
 // LLVM-CIR-NEXT: %[[TMP28:.*]] = addrspacecast ptr addrspace(1) %[[TMP27]] to 
ptr addrspace(4)
-// LLVM-CIR-NEXT: call void @_Z4foo2Pi(ptr addrspace(4) noundef %[[TMP28]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4foo2Pi(ptr addrspace(4) noundef 
%[[TMP28]])
 // LLVM-CIR-NEXT: %[[TMP29:.*]] = load ptr addrspace(3), ptr addrspace(4) 
%[[LOC_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z3fooPU3AS3i(ptr addrspace(3) noundef %[[TMP29]])
+// LLVM-CIR-NEXT: call spir_func void @_Z3fooPU3AS3i(ptr addrspace(3) noundef 
%[[TMP29]])
 // LLVM-CIR-NEXT: %[[TMP30:.*]] = load ptr addrspace(3), ptr addrspace(4) 
%[[LOC_ASCAST]], align 8
 // LLVM-CIR-NEXT: %[[TMP31:.*]] = addrspacecast ptr addrspace(3) %[[TMP30]] to 
ptr addrspace(4)
-// LLVM-CIR-NEXT: call void @_Z4foo2Pi(ptr addrspace(4) noundef %[[TMP31]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4foo2Pi(ptr addrspace(4) noundef 
%[[TMP31]])
 // LLVM-CIR-NEXT: %[[TMP32:.*]] = load ptr addrspace(4), ptr addrspace(4) 
%[[NOAS_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z3fooPi(ptr addrspace(4) noundef %[[TMP32]])
+// LLVM-CIR-NEXT: call spir_func void @_Z3fooPi(ptr addrspace(4) noundef 
%[[TMP32]])
 // LLVM-CIR-NEXT: %[[TMP33:.*]] = load ptr addrspace(4), ptr addrspace(4) 
%[[NOAS_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z4foo2Pi(ptr addrspace(4) noundef %[[TMP33]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4foo2Pi(ptr addrspace(4) noundef 
%[[TMP33]])
 // LLVM-CIR-NEXT: %[[TMP34:.*]] = load ptr addrspace(1), ptr addrspace(4) 
%[[GLOB_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z4tmplIPU3AS1iEvT_(ptr addrspace(1) noundef 
%[[TMP34]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4tmplIPU3AS1iEvT_(ptr addrspace(1) 
noundef %[[TMP34]])
 // LLVM-CIR-NEXT: %[[TMP35:.*]] = load ptr addrspace(3), ptr addrspace(4) 
%[[LOC_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z4tmplIPU3AS3iEvT_(ptr addrspace(3) noundef 
%[[TMP35]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4tmplIPU3AS3iEvT_(ptr addrspace(3) 
noundef %[[TMP35]])
 // LLVM-CIR-NEXT: %[[TMP36:.*]] = load ptr, ptr addrspace(4) %[[PRIV_ASCAST]], 
align 8
-// LLVM-CIR-NEXT: call void @_Z4tmplIPU3AS0iEvT_(ptr noundef %[[TMP36]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4tmplIPU3AS0iEvT_(ptr noundef 
%[[TMP36]])
 // LLVM-CIR-NEXT: %[[TMP37:.*]] = load ptr addrspace(4), ptr addrspace(4) 
%[[NOAS_ASCAST]], align 8
-// LLVM-CIR-NEXT: call void @_Z4tmplIPiEvT_(ptr addrspace(4) noundef 
%[[TMP37]])
+// LLVM-CIR-NEXT: call spir_func void @_Z4tmplIPiEvT_(ptr addrspace(4) noundef 
%[[TMP37]])
 // LLVM-CIR-NEXT: ret void
 
 // OGCG-LABEL: define {{.*}}spir_func void @_Z6usagesv(
diff --git a/clang/test/CIR/CodeGenSYCL/call-calling-conv.cpp 
b/clang/test/CIR/CodeGenSYCL/call-calling-conv.cpp
new file mode 100644
index 0000000000000..4e0d257f71bcb
--- /dev/null
+++ b/clang/test/CIR/CodeGenSYCL/call-calling-conv.cpp
@@ -0,0 +1,38 @@
+// RUN: %clang_cc1 -triple spir64 -fsycl-is-device -disable-llvm-passes 
-fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple spir64 -fsycl-is-device -disable-llvm-passes 
-fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s --check-prefixes=LLVM,LLVM-CIR
+// RUN: %clang_cc1 -triple spir64 -fsycl-is-device -disable-llvm-passes 
-emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s --check-prefixes=LLVM,OGCG
+
+// Calls in SYCL device code use the spir_func calling convention, matching the
+// calling convention of the callee.
+
+template <typename KN, typename F>
+[[clang::sycl_kernel_entry_point(KN)]] void kernel(F f) { f(); }
+
+template <typename KN, typename... Ts>
+void sycl_kernel_launch(const char *, Ts...) {}
+
+int run2() { return 1; }
+void run() { (void)run2(); }
+
+struct KN;
+void use() { kernel<KN>([] { run(); }); }
+
+// CIR: cir.func {{.*}}@_ZTS2KN({{.*}}) cc(spir_kernel)
+// CIR:   cir.call @_ZZ3usevENKUlvE_clEv(%{{.*}}) cc(spir_function)
+// CIR: cir.func {{.*}}@_ZZ3usevENKUlvE_clEv({{.*}}) cc(spir_function)
+// CIR:   cir.call @_Z3runv() cc(spir_function)
+// CIR: cir.func {{.*}}@_Z3runv() cc(spir_function)
+// CIR:   cir.call @_Z4run2v() cc(spir_function)
+// CIR: cir.func {{.*}}@_Z4run2v() -> {{.*}} cc(spir_function)
+
+// LLVM-CIR: define dso_local spir_kernel void @_ZTS2KN(
+// OGCG:     define dso_local spir_kernel void @_ZTS2KN(
+// LLVM:       call spir_func void @_ZZ3usevENKUlvE_clEv(ptr addrspace(4) 
noundef align 1 dereferenceable_or_null(1) %{{.*}})
+// LLVM:     define internal spir_func void @_ZZ3usevENKUlvE_clEv(
+// LLVM:       call spir_func void @_Z3runv()
+// LLVM:     define dso_local spir_func void @_Z3runv()
+// LLVM:       call spir_func noundef i32 @_Z4run2v()
+// LLVM:     define dso_local spir_func noundef i32 @_Z4run2v()
diff --git a/clang/test/CIR/CodeGenSYCL/local-temporaries.cpp 
b/clang/test/CIR/CodeGenSYCL/local-temporaries.cpp
index 0c4649d075176..868745b3dd434 100644
--- a/clang/test/CIR/CodeGenSYCL/local-temporaries.cpp
+++ b/clang/test/CIR/CodeGenSYCL/local-temporaries.cpp
@@ -53,7 +53,7 @@ void take_ref(const S &s);
 // CIR-NEXT: cir.store align(4) %[[TMP0]], %[[REF_TMP0_ASCAST]] : !s32i, 
!cir.ptr<!s32i, target_address_space(4)>
 // CIR-NEXT: cir.store align(8) %[[REF_TMP0_ASCAST]], %[[R_ASCAST]] : 
!cir.ptr<!s32i, target_address_space(4)>, !cir.ptr<!cir.ptr<!s32i, 
target_address_space(4)>, target_address_space(4)>
 // CIR-NEXT: %[[TMP1:.*]] = cir.load %[[R_ASCAST]] : !cir.ptr<!cir.ptr<!s32i, 
target_address_space(4)>, target_address_space(4)>, !cir.ptr<!s32i, 
target_address_space(4)>
-// CIR-NEXT: cir.call @_Z9use_constPKi(%[[TMP1]]) nothrow nounwind 
{convergent} : (!cir.ptr<!s32i, target_address_space(4)> {llvm.noundef}) -> ()
+// CIR-NEXT: cir.call @_Z9use_constPKi(%[[TMP1]]) cc(spir_function) nothrow 
nounwind {convergent} : (!cir.ptr<!s32i, target_address_space(4)> 
{llvm.noundef}) -> ()
 // CIR-NEXT: cir.return
 
 // CIR-LABEL: cir.func {{.*}}@_Z8agg_tempv(
@@ -65,7 +65,7 @@ void take_ref(const S &s);
 // CIR-NEXT: %[[TMP2:.*]] = cir.get_member %[[REF_TMP0_ASCAST]][1] {name = 
"b"} : !cir.ptr<!rec_S, target_address_space(4)> -> !cir.ptr<!s32i, 
target_address_space(4)>
 // CIR-NEXT: %[[TMP3:.*]] = cir.const #cir.int<2> : !s32i
 // CIR-NEXT: cir.store align(4) %[[TMP3]], %[[TMP2]] : !s32i, !cir.ptr<!s32i, 
target_address_space(4)>
-// CIR-NEXT: cir.call @_Z8take_refRK1S(%[[REF_TMP0_ASCAST]]) nothrow nounwind 
{convergent} : (!cir.ptr<!rec_S, target_address_space(4)> {llvm.align = 4 : 
i64, llvm.dereferenceable = 8 : i64, llvm.noundef}) -> ()
+// CIR-NEXT: cir.call @_Z8take_refRK1S(%[[REF_TMP0_ASCAST]]) cc(spir_function) 
nothrow nounwind {convergent} : (!cir.ptr<!rec_S, target_address_space(4)> 
{llvm.align = 4 : i64, llvm.dereferenceable = 8 : i64, llvm.noundef}) -> ()
 // CIR-NEXT: cir.return
 
 // CIR-LABEL: cir.func {{.*}}@_Z10array_tempv(
@@ -79,7 +79,7 @@ void take_ref(const S &s);
 // CIR-NEXT: %[[TMP4:.*]] = cir.const #cir.int<4> : !s32i
 // CIR-NEXT: cir.store align(4) %[[TMP4]], %[[TMP3]] : !s32i, !cir.ptr<!s32i, 
target_address_space(4)>
 // CIR-NEXT: %[[TMP5:.*]] = cir.cast array_to_ptrdecay %[[REF_TMP0_ASCAST]] : 
!cir.ptr<!cir.array<!s32i x 2>, target_address_space(4)> -> !cir.ptr<!s32i, 
target_address_space(4)>
-// CIR-NEXT: cir.call @_Z3usePi(%[[TMP5]]) nothrow nounwind {convergent} : 
(!cir.ptr<!s32i, target_address_space(4)> {llvm.noundef}) -> ()
+// CIR-NEXT: cir.call @_Z3usePi(%[[TMP5]]) cc(spir_function) nothrow nounwind 
{convergent} : (!cir.ptr<!s32i, target_address_space(4)> {llvm.noundef}) -> ()
 // CIR-NEXT: cir.return
 
 // CIR-LABEL: cir.func {{.*}}@_Z10lambda_refv(
@@ -91,7 +91,7 @@ void take_ref(const S &s);
 // CIR-NEXT: cir.store align(4) %[[TMP0]], %[[X_ASCAST]] : !s32i, 
!cir.ptr<!s32i, target_address_space(4)>
 // CIR-NEXT: %[[TMP1:.*]] = cir.get_member %[[L_ASCAST]][0] {name = "x"} : 
!cir.ptr<!rec_anon2E0, target_address_space(4)> -> !cir.ptr<!cir.ptr<!s32i, 
target_address_space(4)>, target_address_space(4)>
 // CIR-NEXT: cir.store align(8) %[[X_ASCAST]], %[[TMP1]] : !cir.ptr<!s32i, 
target_address_space(4)>, !cir.ptr<!cir.ptr<!s32i, target_address_space(4)>, 
target_address_space(4)>
-// CIR-NEXT: cir.call @_ZZ10lambda_refvENKUlvE_clEv(%[[L_ASCAST]]) nothrow 
nounwind {convergent} : (!cir.ptr<!rec_anon2E0, target_address_space(4)> 
{llvm.align = 8 : i64, llvm.dereferenceable_or_null = 8 : i64, llvm.noundef}) 
-> ()
+// CIR-NEXT: cir.call @_ZZ10lambda_refvENKUlvE_clEv(%[[L_ASCAST]]) 
cc(spir_function) nothrow nounwind {convergent} : (!cir.ptr<!rec_anon2E0, 
target_address_space(4)> {llvm.align = 8 : i64, llvm.dereferenceable_or_null = 
8 : i64, llvm.noundef}) -> ()
 // CIR-NEXT: cir.return
 
 // CIR-LABEL: cir.func {{.*}}@_Z4nrvov(
@@ -100,7 +100,7 @@ void take_ref(const S &s);
 // CIR-NEXT: %[[TMP0:.*]] = cir.const #cir.zero : !rec_S
 // CIR-NEXT: cir.store align(4) %[[TMP0]], %[[__RETVAL_ASCAST]] : !rec_S, 
!cir.ptr<!rec_S, target_address_space(4)>
 // CIR-NEXT: %[[TMP1:.*]] = cir.get_member %[[__RETVAL_ASCAST]][0] {name = 
"a"} : !cir.ptr<!rec_S, target_address_space(4)> -> !cir.ptr<!s32i, 
target_address_space(4)>
-// CIR-NEXT: cir.call @_Z3usePi(%[[TMP1]]) nothrow nounwind {convergent} : 
(!cir.ptr<!s32i, target_address_space(4)> {llvm.noundef}) -> ()
+// CIR-NEXT: cir.call @_Z3usePi(%[[TMP1]]) cc(spir_function) nothrow nounwind 
{convergent} : (!cir.ptr<!s32i, target_address_space(4)> {llvm.noundef}) -> ()
 // CIR-NEXT: %[[TMP2:.*]] = cir.load %[[__RETVAL]] : !cir.ptr<!rec_S>, !rec_S
 // CIR-NEXT: cir.return %[[TMP2]] : !rec_S
 
@@ -112,7 +112,7 @@ void take_ref(const S &s);
 // LLVM-NEXT: store i32 42, ptr addrspace(4) %[[R_ASCAST]], align 4
 // LLVM-NEXT: store ptr addrspace(4) %[[R_ASCAST]], ptr addrspace(4) 
%[[REF_TMP_ASCAST]], align 8
 // LLVM-NEXT: %[[TMP0:.*]] = load ptr addrspace(4), ptr addrspace(4) 
%[[REF_TMP_ASCAST]], align 8
-// LLVM-NEXT: call void @_Z9use_constPKi(ptr addrspace(4) noundef %[[TMP0]])
+// LLVM-NEXT: call spir_func void @_Z9use_constPKi(ptr addrspace(4) noundef 
%[[TMP0]])
 // LLVM-NEXT: ret void
 
 // LLVM-LABEL: define {{.*}}spir_func void @_Z8agg_tempv(
@@ -122,7 +122,7 @@ void take_ref(const S &s);
 // LLVM-NEXT: store i32 1, ptr addrspace(4) %[[TMP1]], align 4
 // LLVM-NEXT: %[[TMP2:.*]] = getelementptr inbounds nuw %[[TMP0]], ptr 
addrspace(4) %[[REF_TMP_ASCAST]], i32 0, i32 1
 // LLVM-NEXT: store i32 2, ptr addrspace(4) %[[TMP2]], align 4
-// LLVM-NEXT: call void @_Z8take_refRK1S(ptr addrspace(4) noundef align 4 
dereferenceable(8) %[[REF_TMP_ASCAST]])
+// LLVM-NEXT: call spir_func void @_Z8take_refRK1S(ptr addrspace(4) noundef 
align 4 dereferenceable(8) %[[REF_TMP_ASCAST]])
 // LLVM-NEXT: ret void
 
 // LLVM-LABEL: define {{.*}}spir_func void @_Z10array_tempv(
@@ -133,7 +133,7 @@ void take_ref(const S &s);
 // LLVM-NEXT: %[[TMP1:.*]] = getelementptr i32, ptr addrspace(4) %[[TMP0]], 
i64 1
 // LLVM-NEXT: store i32 4, ptr addrspace(4) %[[TMP1]], align 4
 // LLVM-NEXT: %[[TMP2:.*]] = getelementptr i32, ptr addrspace(4) 
%[[REF_TMP_ASCAST]], i32 0
-// LLVM-NEXT: call void @_Z3usePi(ptr addrspace(4) noundef %[[TMP2]])
+// LLVM-NEXT: call spir_func void @_Z3usePi(ptr addrspace(4) noundef %[[TMP2]])
 // LLVM-NEXT: ret void
 
 // LLVM-LABEL: define {{.*}}spir_func void @_Z10lambda_refv(
@@ -144,7 +144,7 @@ void take_ref(const S &s);
 // LLVM-NEXT: store i32 0, ptr addrspace(4) %[[X_ASCAST]], align 4
 // LLVM-NEXT: %[[TMP1:.*]] = getelementptr inbounds nuw %[[TMP0]], ptr 
addrspace(4) %[[L_ASCAST]], i32 0, i32 0
 // LLVM-NEXT: store ptr addrspace(4) %[[X_ASCAST]], ptr addrspace(4) 
%[[TMP1]], align 8
-// LLVM-NEXT: call void @_ZZ10lambda_refvENKUlvE_clEv(ptr addrspace(4) noundef 
align 8 dereferenceable_or_null(8) %[[L_ASCAST]])
+// LLVM-NEXT: call spir_func void @_ZZ10lambda_refvENKUlvE_clEv(ptr 
addrspace(4) noundef align 8 dereferenceable_or_null(8) %[[L_ASCAST]])
 // LLVM-NEXT: ret void
 
 // LLVM-LABEL: define {{.*}}spir_func void @_ZZ10lambda_refvENKUlvE_clEv(
@@ -155,7 +155,7 @@ void take_ref(const S &s);
 // LLVM-NEXT: %[[TMP0:.*]] = load ptr addrspace(4), ptr addrspace(4) 
%[[THIS_ADDR_ASCAST]], align 8
 // LLVM-NEXT: %[[TMP1:.*]] = getelementptr inbounds nuw %[[TMP2:.*]], ptr 
addrspace(4) %[[TMP0]], i32 0, i32 0
 // LLVM-NEXT: %[[TMP3:.*]] = load ptr addrspace(4), ptr addrspace(4) 
%[[TMP1]], align 8
-// LLVM-NEXT: call void @_Z3usePi(ptr addrspace(4) noundef %[[TMP3]])
+// LLVM-NEXT: call spir_func void @_Z3usePi(ptr addrspace(4) noundef %[[TMP3]])
 // LLVM-NEXT: ret void
 
 // LLVM-LABEL: define {{.*}}spir_func %struct.S @_Z4nrvov(
@@ -163,7 +163,7 @@ void take_ref(const S &s);
 // LLVM-NEXT: %[[TMP2:.*]] = addrspacecast ptr %[[TMP0]] to ptr addrspace(4)
 // LLVM-NEXT: store %[[TMP1]] zeroinitializer, ptr addrspace(4) %[[TMP2]], 
align 4
 // LLVM-NEXT: %[[TMP3:.*]] = getelementptr inbounds nuw %[[TMP1]], ptr 
addrspace(4) %[[TMP2]], i32 0, i32 0
-// LLVM-NEXT: call void @_Z3usePi(ptr addrspace(4) noundef %[[TMP3]])
+// LLVM-NEXT: call spir_func void @_Z3usePi(ptr addrspace(4) noundef %[[TMP3]])
 // LLVM-NEXT: %[[TMP4:.*]] = load %[[TMP1]], ptr %[[TMP0]], align 4
 // LLVM-NEXT: ret %[[TMP1]] %[[TMP4]]
 
diff --git a/clang/test/CIR/IR/call-calling-conv.cir 
b/clang/test/CIR/IR/call-calling-conv.cir
new file mode 100644
index 0000000000000..753daaaf7530c
--- /dev/null
+++ b/clang/test/CIR/IR/call-calling-conv.cir
@@ -0,0 +1,39 @@
+// RUN: cir-opt %s --verify-roundtrip | FileCheck %s
+
+!s32i = !cir.int<s, 32>
+
+module {
+  cir.func private @spir_callee(!s32i) -> !s32i cc(spir_function)
+  cir.func private @c_callee()
+
+  cir.func @direct(%arg0: !s32i) -> !s32i cc(spir_function) {
+    %0 = cir.call @spir_callee(%arg0) cc(spir_function) nothrow : (!s32i) -> 
!s32i
+    cir.call @c_callee() : () -> ()
+    cir.return %0 : !s32i
+  }
+
+  // CHECK: cir.func @direct(%arg0: !s32i) -> !s32i cc(spir_function) {
+  // CHECK:   %[[R:.*]] = cir.call @spir_callee(%arg0) cc(spir_function) 
nothrow : (!s32i) -> !s32i
+  // CHECK:   cir.call @c_callee() : () -> ()
+
+  cir.func @indirect(%fn: !cir.ptr<!cir.func<(!s32i) -> !s32i>>, %arg0: !s32i) 
-> !s32i {
+    %0 = cir.call %fn(%arg0) cc(spir_function) : (!cir.ptr<!cir.func<(!s32i) 
-> !s32i>>, !s32i) -> !s32i
+    cir.return %0 : !s32i
+  }
+
+  // CHECK: cir.func @indirect(%arg0: !cir.ptr<!cir.func<(!s32i) -> !s32i>>, 
%arg1: !s32i) -> !s32i {
+  // CHECK:   %{{.*}} = cir.call %arg0(%arg1) cc(spir_function) : 
(!cir.ptr<!cir.func<(!s32i) -> !s32i>>, !s32i) -> !s32i
+
+  cir.func @try_call(%arg0: !s32i) {
+    %0 = cir.try_call @spir_callee(%arg0) ^normal, ^unwind cc(spir_function) : 
(!s32i) -> !s32i
+  ^normal:
+    cir.br ^end
+  ^unwind:
+    cir.br ^end
+  ^end:
+    cir.return
+  }
+
+  // CHECK: cir.func @try_call(%arg0: !s32i) {
+  // CHECK:   %{{.*}} = cir.try_call @spir_callee(%arg0) ^bb1, ^bb2 
cc(spir_function) : (!s32i) -> !s32i
+}
diff --git a/clang/test/CIR/Lowering/call-calling-conv.cir 
b/clang/test/CIR/Lowering/call-calling-conv.cir
new file mode 100644
index 0000000000000..dc067fe994194
--- /dev/null
+++ b/clang/test/CIR/Lowering/call-calling-conv.cir
@@ -0,0 +1,40 @@
+// RUN: cir-opt %s -cir-to-llvm -o %t.mlir
+// RUN: FileCheck --input-file=%t.mlir %s
+
+!s32i = !cir.int<s, 32>
+
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+  cir.func private @__gxx_personality_v0(...) -> !s32i
+  cir.func private @spir_callee(!s32i) -> !s32i cc(spir_function)
+  cir.func private @c_callee()
+
+  cir.func @direct(%arg0: !s32i) -> !s32i cc(spir_function) {
+    %0 = cir.call @spir_callee(%arg0) cc(spir_function) : (!s32i) -> !s32i
+    cir.call @c_callee() : () -> ()
+    cir.return %0 : !s32i
+  }
+
+  // CHECK: llvm.func spir_funccc @direct(
+  // CHECK:   llvm.call spir_funccc @spir_callee(%{{.*}}) : (i32) -> i32
+  // CHECK:   llvm.call @c_callee() : () -> ()
+
+  cir.func @indirect(%fn: !cir.ptr<!cir.func<(!s32i) -> !s32i>>, %arg0: !s32i) 
-> !s32i {
+    %0 = cir.call %fn(%arg0) cc(spir_function) : (!cir.ptr<!cir.func<(!s32i) 
-> !s32i>>, !s32i) -> !s32i
+    cir.return %0 : !s32i
+  }
+
+  // CHECK: llvm.func @indirect(
+  // CHECK:   llvm.call spir_funccc %{{.*}}(%{{.*}}) : !llvm.ptr, (i32) -> i32
+
+  cir.func @try_call(%arg0: !s32i) personality(@__gxx_personality_v0) {
+    %0 = cir.try_call @spir_callee(%arg0) ^normal, ^unwind cc(spir_function) : 
(!s32i) -> !s32i
+  ^normal:
+    cir.return
+  ^unwind:
+    %exception_ptr, %type_id = cir.eh.inflight_exception cleanup
+    cir.return
+  }
+
+  // CHECK: llvm.func @try_call(
+  // CHECK:   llvm.invoke spir_funccc @spir_callee(%{{.*}}) to ^{{.*}} unwind 
^{{.*}} : (i32) -> i32
+}
diff --git a/clang/test/CIR/Transforms/flatten-preserve-attrs.cir 
b/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
index db239bdc73aa2..42a27b7c710e1 100644
--- a/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
+++ b/clang/test/CIR/Transforms/flatten-preserve-attrs.cir
@@ -141,7 +141,27 @@ cir.func @test_preserve_all_attrs_indirect(
 // CHECK-SAME:        %[[OBJ:.*]]: !cir.ptr<!rec_SomeClass>
 // CHECK:         cir.try_call %[[FN_PTR]](%[[OBJ]]) ^{{bb[0-9]+}}, 
^{{bb[0-9]+}} {memory_effects = #cir.memory_effects<other = none, arg_mem = 
none, inaccessible_mem = none, errno_mem = none, target_mem0 = none, 
target_mem1 = none>} : (!cir.ptr<!cir.func<(!cir.ptr<!rec_SomeClass>) -> 
!s32i>>, !cir.ptr<!rec_SomeClass> {llvm.nonnull}) -> (!s32i {llvm.nonnull})
 
+// Test that the calling convention on a cir.call is preserved on the resulting
+// cir.try_call after flattening.
+cir.func @test_preserve_calling_conv() {
+  cir.scope {
+    cir.try {
+      cir.call @spirFunc() cc(spir_function) : () -> ()
+      cir.yield
+    } catch all (%eh_token : !cir.eh_token) {
+      %catch_token, %exn_ptr = cir.begin_catch %eh_token : !cir.eh_token -> 
(!cir.catch_token, !cir.ptr<!cir.void>)
+      cir.end_catch %catch_token : !cir.catch_token
+      cir.yield
+    }
+  }
+  cir.return
+}
+
+// CHECK-LABEL: cir.func @test_preserve_calling_conv()
+// CHECK:         cir.try_call @spirFunc() ^{{.*}}, ^{{.*}} cc(spir_function) 
: () -> ()
+
 cir.func private @generalFunc(!cir.ptr<!s32i>) -> !cir.ptr<!s32i>
 cir.func private @takesPtr(!cir.ptr<!s32i>)
 cir.func private @returnsPtr() -> !cir.ptr<!s32i>
 cir.func private @pureFunc() -> !s32i
+cir.func private @spirFunc() cc(spir_function)

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

Reply via email to