Repository: llvm/llvm-project
Pull request #138873: [CIR] LLVMIR lowering for cir.call
Author: login: Lancern
Labels: name: clang
description: Clang issues not falling into any other category
name: ClangIR
description: Anything related to the ClangIR project
Created: 2025-05-07T13:47:19Z | Merged: 2025-05-09T13:16:29Z

This PR adds LLVMIR lowering support for the `cir.call` operation.


Diff:
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index eb75a073d1817..d49098768ad09 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -104,6 +104,8 @@ struct MissingFeatures {
   static bool opCallExtParameterInfo() { return false; }
   static bool opCallCIRGenFuncInfoParamInfo() { return false; }
   static bool opCallCIRGenFuncInfoExtParamInfo() { return false; }
+  static bool opCallLandingPad() { return false; }
+  static bool opCallContinueBlock() { return false; }
 
   // ScopeOp handling
   static bool opScopeCleanupRegion() { return false; }
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 6137adb1e9936..a69e269db3715 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -651,6 +651,50 @@ mlir::LogicalResult CIRToLLVMReturnOpLowering::matchAndRewrite(
   return mlir::LogicalResult::success();
 }
 
+static mlir::LogicalResult
+rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
+                    mlir::ConversionPatternRewriter &rewriter,
+                    const mlir::TypeConverter *converter,
+                    mlir::FlatSymbolRefAttr calleeAttr) {
+  llvm::SmallVector<mlir::Type, 8> llvmResults;
+  mlir::ValueTypeRange<mlir::ResultRange> cirResults = op->getResultTypes();
+
+  if (converter->convertTypes(cirResults, llvmResults).failed())
+    return mlir::failure();
+
+  assert(!cir::MissingFeatures::opCallCallConv());
+  assert(!cir::MissingFeatures::opCallSideEffect());
+
+  mlir::LLVM::LLVMFunctionType llvmFnTy;
+  if (calleeAttr) { // direct call
+    mlir::FunctionOpInterface fn =
+        mlir::SymbolTable::lookupNearestSymbolFrom<mlir::FunctionOpInterface>(
+            op, calleeAttr);
+    assert(fn && "Did not find function for call");
+    llvmFnTy = cast<mlir::LLVM::LLVMFunctionType>(
+        converter->convertType(fn.getFunctionType()));
+  } else { // indirect call
+    assert(!cir::MissingFeatures::opCallIndirect());
+    return op->emitError("Indirect calls are NYI");
+  }
+
+  assert(!cir::MissingFeatures::opCallLandingPad());
+  assert(!cir::MissingFeatures::opCallContinueBlock());
+  assert(!cir::MissingFeatures::opCallCallConv());
+  assert(!cir::MissingFeatures::opCallSideEffect());
+
+  rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(op, llvmFnTy, calleeAttr,
+                                                  callOperands);
+  return mlir::success();
+}
+
+mlir::LogicalResult CIRToLLVMCallOpLowering::matchAndRewrite(
+    cir::CallOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), rewriter,
+                             getTypeConverter(), op.getCalleeAttr());
+}
+
 mlir::LogicalResult CIRToLLVMLoadOpLowering::matchAndRewrite(
     cir::LoadOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
@@ -1589,6 +1633,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
                CIRToLLVMBinOpLowering,
                CIRToLLVMBrCondOpLowering,
                CIRToLLVMBrOpLowering,
+               CIRToLLVMCallOpLowering,
                CIRToLLVMCmpOpLowering,
                CIRToLLVMConstantOpLowering,
                CIRToLLVMFuncOpLowering,
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index f248ea31e7844..0da4f9ba2c791 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -64,6 +64,15 @@ class CIRToLLVMReturnOpLowering
                   mlir::ConversionPatternRewriter &) const override;
 };
 
+class CIRToLLVMCallOpLowering : public mlir::OpConversionPattern<cir::CallOp> {
+public:
+  using mlir::OpConversionPattern<cir::CallOp>::OpConversionPattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::CallOp op, OpAdaptor adaptor,
+                  mlir::ConversionPatternRewriter &rewriter) const override;
+};
+
 class CIRToLLVMAllocaOpLowering
     : public mlir::OpConversionPattern<cir::AllocaOp> {
   mlir::DataLayout const &dataLayout;
diff --git a/clang/test/CIR/CodeGen/call.cpp b/clang/test/CIR/CodeGen/call.cpp
index f6dc5e15933ed..3b1ab8b5fc498 100644
--- a/clang/test/CIR/CodeGen/call.cpp
+++ b/clang/test/CIR/CodeGen/call.cpp
@@ -1,13 +1,19 @@
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
 
 void f1() {}
 void f2() {
   f1();
 }
 
-// CHECK-LABEL: cir.func @_Z2f1v
-// CHECK-LABEL: cir.func @_Z2f2v
-// CHECK:         cir.call @_Z2f1v() : () -> ()
+// CIR-LABEL: cir.func @_Z2f1v
+// CIR-LABEL: cir.func @_Z2f2v
+// CIR:         cir.call @_Z2f1v() : () -> ()
+
+// LLVM-LABEL: define void @_Z2f2v() {
+// LLVM:         call void @_Z2f1v()
 
 int f3() { return 2; }
 int f4() {
@@ -15,9 +21,12 @@ int f4() {
   return x;
 }
 
-// CHECK-LABEL: cir.func @_Z2f3v() -> !s32i
-// CHECK-LABEL: cir.func @_Z2f4v() -> !s32i
-// CHECK:         cir.call @_Z2f3v() : () -> !s32i
+// CIR-LABEL: cir.func @_Z2f3v() -> !s32i
+// CIR-LABEL: cir.func @_Z2f4v() -> !s32i
+// CIR:         cir.call @_Z2f3v() : () -> !s32i
+
+// LLVM-LABEL: define i32 @_Z2f4v() {
+// LLVM:         %{{.+}} = call i32 @_Z2f3v()
 
 int f5(int a, int *b, bool c);
 int f6() {
@@ -25,8 +34,11 @@ int f6() {
   return f5(2, &b, false);
 }
 
-// CHECK-LABEL: cir.func @_Z2f6v() -> !s32i
-// CHECK:         %[[#b:]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init]
-// CHECK:         %[[#a:]] = cir.const #cir.int<2> : !s32i
-// CHECK-NEXT:    %[[#c:]] = cir.const #false
-// CHECK-NEXT:    %{{.+}} = cir.call @_Z2f5iPib(%[[#a]], %[[#b:]], %[[#c]]) : (!s32i, !cir.ptr<!s32i>, !cir.bool) -> !s32i
+// CIR-LABEL: cir.func @_Z2f6v() -> !s32i
+// CIR:         %[[#b:]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init]
+// CIR:         %[[#a:]] = cir.const #cir.int<2> : !s32i
+// CIR-NEXT:    %[[#c:]] = cir.const #false
+// CIR-NEXT:    %{{.+}} = cir.call @_Z2f5iPib(%[[#a]], %[[#b:]], %[[#c]]) : (!s32i, !cir.ptr<!s32i>, !cir.bool) -> !s32i
+
+// LLVM-LABEL: define i32 @_Z2f6v() {
+// LLVM:         %{{.+}} = call i32 @_Z2f5iPib(i32 2, ptr %{{.+}}, i1 false)


Reviews:
user: login: bcardosolopes
body: Pretty straightforward, LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-08T18:55:11Z
user: login: andykaylor
body: Looks good. Just a couple of nits.
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-08T20:12:51Z

Review comments:
diff_hunk: @@ -651,6 +651,50 @@ mlir::LogicalResult CIRToLLVMReturnOpLowering::matchAndRewrite(
   return mlir::LogicalResult::success();
 }
 
+static mlir::LogicalResult
+rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
+                    mlir::ConversionPatternRewriter &rewriter,
+                    const mlir::TypeConverter *converter,
+                    mlir::FlatSymbolRefAttr calleeAttr) {
+  llvm::SmallVector<mlir::Type, 8> llvmResults;
+  auto cirResults = op->getResultTypes();
path: clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
user: login: andykaylor
body: ```suggestion
  llvm::ArrayRef<mlir::Type> cirResults = op->getResultTypes();
```
created_at: 2025-05-08T20:09:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -651,6 +651,50 @@ mlir::LogicalResult CIRToLLVMReturnOpLowering::matchAndRewrite(
   return mlir::LogicalResult::success();
 }
 
+static mlir::LogicalResult
+rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
+                    mlir::ConversionPatternRewriter &rewriter,
+                    const mlir::TypeConverter *converter,
+                    mlir::FlatSymbolRefAttr calleeAttr) {
+  llvm::SmallVector<mlir::Type, 8> llvmResults;
+  auto cirResults = op->getResultTypes();
+
+  if (converter->convertTypes(cirResults, llvmResults).failed())
+    return mlir::failure();
+
+  assert(!cir::MissingFeatures::opCallCallConv());
+  assert(!cir::MissingFeatures::opCallSideEffect());
+
+  mlir::LLVM::LLVMFunctionType llvmFnTy;
+  if (calleeAttr) { // direct call
+    auto fn =
path: clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
user: login: andykaylor
body: ```suggestion
    cir::FuncOp fn =
```
I'm not sure this is the correct type. Mainly, don't use auto here.
created_at: 2025-05-08T20:10:56Z
author_association: CONTRIBUTOR

PR comments:
user: login: llvmbot
created_at: 2025-05-07T13:47:58Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Sirui Mu (Lancern)

<details>
<summary>Changes</summary>

This PR adds LLVMIR lowering support for the `cir.call` operation.

---
Full diff: https://github.com/llvm/llvm-project/pull/138873.diff


4 Files Affected:

- (modified) clang/include/clang/CIR/MissingFeatures.h (+2) 
- (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+45) 
- (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h (+9) 
- (modified) clang/test/CIR/CodeGen/call.cpp (+24-12) 


``````````diff
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index eb75a073d1817..d49098768ad09 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -104,6 +104,8 @@ struct MissingFeatures {
   static bool opCallExtParameterInfo() { return false; }
   static bool opCallCIRGenFuncInfoParamInfo() { return false; }
   static bool opCallCIRGenFuncInfoExtParamInfo() { return false; }
+  static bool opCallLandingPad() { return false; }
+  static bool opCallContinueBlock() { return false; }
 
   // ScopeOp handling
   static bool opScopeCleanupRegion() { return false; }
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 6137adb1e9936..8ba079a55f1c5 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -651,6 +651,50 @@ mlir::LogicalResult CIRToLLVMReturnOpLowering::matchAndRewrite(
   return mlir::LogicalResult::success();
 }
 
+static mlir::LogicalResult
+rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
+                    mlir::ConversionPatternRewriter &rewriter,
+                    const mlir::TypeConverter *converter,
+                    mlir::FlatSymbolRefAttr calleeAttr) {
+  llvm::SmallVector<mlir::Type, 8> llvmResults;
+  auto cirResults = op->getResultTypes();
+
+  if (converter->convertTypes(cirResults, llvmResults).failed())
+    return mlir::failure();
+
+  assert(!cir::MissingFeatures::opCallCallConv());
+  assert(!cir::MissingFeatures::opCallSideEffect());
+
+  mlir::LLVM::LLVMFunctionType llvmFnTy;
+  if (calleeAttr) { // direct call
+    auto fn =
+        mlir::SymbolTable::lookupNearestSymbolFrom<mlir::FunctionOpInterface>(
+            op, calleeAttr);
+    assert(fn && "Did not find function for call");
+    llvmFnTy = cast<mlir::LLVM::LLVMFunctionType>(
+        converter->convertType(fn.getFunctionType()));
+  } else { // indirect call
+    assert(!cir::MissingFeatures::opCallIndirect());
+    return op->emitError("Indirect calls are NYI");
+  }
+
+  assert(!cir::MissingFeatures::opCallLandingPad());
+  assert(!cir::MissingFeatures::opCallContinueBlock());
+  assert(!cir::MissingFeatures::opCallCallConv());
+  assert(!cir::MissingFeatures::opCallSideEffect());
+
+  rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(op, llvmFnTy, calleeAttr,
+                                                  callOperands);
+  return mlir::success();
+}
+
+mlir::LogicalResult CIRToLLVMCallOpLowering::matchAndRewrite(
+    cir::CallOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), rewriter,
+                             getTypeConverter(), op.getCalleeAttr());
+}
+
 mlir::LogicalResult CIRToLLVMLoadOpLowering::matchAndRewrite(
     cir::LoadOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
@@ -1589,6 +1633,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
                CIRToLLVMBinOpLowering,
                CIRToLLVMBrCondOpLowering,
                CIRToLLVMBrOpLowering,
+               CIRToLLVMCallOpLowering,
                CIRToLLVMCmpOpLowering,
                CIRToLLVMConstantOpLowering,
                CIRToLLVMFuncOpLowering,
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index f248ea31e7844..0da4f9ba2c791 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -64,6 +64,15 @@ class CIRToLLVMReturnOpLowering
                   mlir::ConversionPatternRewriter &) const override;
 };
 
+class CIRToLLVMCallOpLowering : public mlir::OpConversionPattern<cir::CallOp> {
+public:
+  using mlir::OpConversionPattern<cir::CallOp>::OpConversionPattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::CallOp op, OpAdaptor adaptor,
+                  mlir::ConversionPatternRewriter &rewriter) const override;
+};
+
 class CIRToLLVMAllocaOpLowering
     : public mlir::OpConversionPattern<cir::AllocaOp> {
   mlir::DataLayout const &dataLayout;
diff --git a/clang/test/CIR/CodeGen/call.cpp b/clang/test/CIR/CodeGen/call.cpp
index f6dc5e15933ed..3b1ab8b5fc498 100644
--- a/clang/test/CIR/CodeGen/call.cpp
+++ b/clang/test/CIR/CodeGen/call.cpp
@@ -1,13 +1,19 @@
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
 
 void f1() {}
 void f2() {
   f1();
 }
 
-// CHECK-LABEL: cir.func @_Z2f1v
-// CHECK-LABEL: cir.func @_Z2f2v
-// CHECK:         cir.call @_Z2f1v() : () -> ()
+// CIR-LABEL: cir.func @_Z2f1v
+// CIR-LABEL: cir.func @_Z2f2v
+// CIR:         cir.call @_Z2f1v() : () -> ()
+
+// LLVM-LABEL: define void @_Z2f2v() {
+// LLVM:         call void @_Z2f1v()
 
 int f3() { return 2; }
 int f4() {
@@ -15,9 +21,12 @@ int f4() {
   return x;
 }
 
-// CHECK-LABEL: cir.func @_Z2f3v() -> !s32i
-// CHECK-LABEL: cir.func @_Z2f4v() -> !s32i
-// CHECK:         cir.call @_Z2f3v() : () -> !s32i
+// CIR-LABEL: cir.func @_Z2f3v() -> !s32i
+// CIR-LABEL: cir.func @_Z2f4v() -> !s32i
+// CIR:         cir.call @_Z2f3v() : () -> !s32i
+
+// LLVM-LABEL: define i32 @_Z2f4v() {
+// LLVM:         %{{.+}} = call i32 @_Z2f3v()
 
 int f5(int a, int *b, bool c);
 int f6() {
@@ -25,8 +34,11 @@ int f6() {
   return f5(2, &b, false);
 }
 
-// CHECK-LABEL: cir.func @_Z2f6v() -> !s32i
-// CHECK:         %[[#b:]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init]
-// CHECK:         %[[#a:]] = cir.const #cir.int<2> : !s32i
-// CHECK-NEXT:    %[[#c:]] = cir.const #false
-// CHECK-NEXT:    %{{.+}} = cir.call @_Z2f5iPib(%[[#a]], %[[#b:]], %[[#c]]) : (!s32i, !cir.ptr<!s32i>, !cir.bool) -> !s32i
+// CIR-LABEL: cir.func @_Z2f6v() -> !s32i
+// CIR:         %[[#b:]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init]
+// CIR:         %[[#a:]] = cir.const #cir.int<2> : !s32i
+// CIR-NEXT:    %[[#c:]] = cir.const #false
+// CIR-NEXT:    %{{.+}} = cir.call @_Z2f5iPib(%[[#a]], %[[#b:]], %[[#c]]) : (!s32i, !cir.ptr<!s32i>, !cir.bool) -> !s32i
+
+// LLVM-LABEL: define i32 @_Z2f6v() {
+// LLVM:         %{{.+}} = call i32 @_Z2f5iPib(i32 2, ptr %{{.+}}, i1 false)

``````````

</details>

author_association: MEMBER
user: login: llvmbot
created_at: 2025-05-07T13:47:59Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clangir

Author: Sirui Mu (Lancern)

<details>
<summary>Changes</summary>

This PR adds LLVMIR lowering support for the `cir.call` operation.

---
Full diff: https://github.com/llvm/llvm-project/pull/138873.diff


4 Files Affected:

- (modified) clang/include/clang/CIR/MissingFeatures.h (+2) 
- (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+45) 
- (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h (+9) 
- (modified) clang/test/CIR/CodeGen/call.cpp (+24-12) 


``````````diff
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index eb75a073d1817..d49098768ad09 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -104,6 +104,8 @@ struct MissingFeatures {
   static bool opCallExtParameterInfo() { return false; }
   static bool opCallCIRGenFuncInfoParamInfo() { return false; }
   static bool opCallCIRGenFuncInfoExtParamInfo() { return false; }
+  static bool opCallLandingPad() { return false; }
+  static bool opCallContinueBlock() { return false; }
 
   // ScopeOp handling
   static bool opScopeCleanupRegion() { return false; }
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 6137adb1e9936..8ba079a55f1c5 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -651,6 +651,50 @@ mlir::LogicalResult CIRToLLVMReturnOpLowering::matchAndRewrite(
   return mlir::LogicalResult::success();
 }
 
+static mlir::LogicalResult
+rewriteCallOrInvoke(mlir::Operation *op, mlir::ValueRange callOperands,
+                    mlir::ConversionPatternRewriter &rewriter,
+                    const mlir::TypeConverter *converter,
+                    mlir::FlatSymbolRefAttr calleeAttr) {
+  llvm::SmallVector<mlir::Type, 8> llvmResults;
+  auto cirResults = op->getResultTypes();
+
+  if (converter->convertTypes(cirResults, llvmResults).failed())
+    return mlir::failure();
+
+  assert(!cir::MissingFeatures::opCallCallConv());
+  assert(!cir::MissingFeatures::opCallSideEffect());
+
+  mlir::LLVM::LLVMFunctionType llvmFnTy;
+  if (calleeAttr) { // direct call
+    auto fn =
+        mlir::SymbolTable::lookupNearestSymbolFrom<mlir::FunctionOpInterface>(
+            op, calleeAttr);
+    assert(fn && "Did not find function for call");
+    llvmFnTy = cast<mlir::LLVM::LLVMFunctionType>(
+        converter->convertType(fn.getFunctionType()));
+  } else { // indirect call
+    assert(!cir::MissingFeatures::opCallIndirect());
+    return op->emitError("Indirect calls are NYI");
+  }
+
+  assert(!cir::MissingFeatures::opCallLandingPad());
+  assert(!cir::MissingFeatures::opCallContinueBlock());
+  assert(!cir::MissingFeatures::opCallCallConv());
+  assert(!cir::MissingFeatures::opCallSideEffect());
+
+  rewriter.replaceOpWithNewOp<mlir::LLVM::CallOp>(op, llvmFnTy, calleeAttr,
+                                                  callOperands);
+  return mlir::success();
+}
+
+mlir::LogicalResult CIRToLLVMCallOpLowering::matchAndRewrite(
+    cir::CallOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  return rewriteCallOrInvoke(op.getOperation(), adaptor.getOperands(), rewriter,
+                             getTypeConverter(), op.getCalleeAttr());
+}
+
 mlir::LogicalResult CIRToLLVMLoadOpLowering::matchAndRewrite(
     cir::LoadOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
@@ -1589,6 +1633,7 @@ void ConvertCIRToLLVMPass::runOnOperation() {
                CIRToLLVMBinOpLowering,
                CIRToLLVMBrCondOpLowering,
                CIRToLLVMBrOpLowering,
+               CIRToLLVMCallOpLowering,
                CIRToLLVMCmpOpLowering,
                CIRToLLVMConstantOpLowering,
                CIRToLLVMFuncOpLowering,
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index f248ea31e7844..0da4f9ba2c791 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -64,6 +64,15 @@ class CIRToLLVMReturnOpLowering
                   mlir::ConversionPatternRewriter &) const override;
 };
 
+class CIRToLLVMCallOpLowering : public mlir::OpConversionPattern<cir::CallOp> {
+public:
+  using mlir::OpConversionPattern<cir::CallOp>::OpConversionPattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::CallOp op, OpAdaptor adaptor,
+                  mlir::ConversionPatternRewriter &rewriter) const override;
+};
+
 class CIRToLLVMAllocaOpLowering
     : public mlir::OpConversionPattern<cir::AllocaOp> {
   mlir::DataLayout const &dataLayout;
diff --git a/clang/test/CIR/CodeGen/call.cpp b/clang/test/CIR/CodeGen/call.cpp
index f6dc5e15933ed..3b1ab8b5fc498 100644
--- a/clang/test/CIR/CodeGen/call.cpp
+++ b/clang/test/CIR/CodeGen/call.cpp
@@ -1,13 +1,19 @@
-// RUN: %clang_cc1 -std=c++20 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
 
 void f1() {}
 void f2() {
   f1();
 }
 
-// CHECK-LABEL: cir.func @_Z2f1v
-// CHECK-LABEL: cir.func @_Z2f2v
-// CHECK:         cir.call @_Z2f1v() : () -> ()
+// CIR-LABEL: cir.func @_Z2f1v
+// CIR-LABEL: cir.func @_Z2f2v
+// CIR:         cir.call @_Z2f1v() : () -> ()
+
+// LLVM-LABEL: define void @_Z2f2v() {
+// LLVM:         call void @_Z2f1v()
 
 int f3() { return 2; }
 int f4() {
@@ -15,9 +21,12 @@ int f4() {
   return x;
 }
 
-// CHECK-LABEL: cir.func @_Z2f3v() -> !s32i
-// CHECK-LABEL: cir.func @_Z2f4v() -> !s32i
-// CHECK:         cir.call @_Z2f3v() : () -> !s32i
+// CIR-LABEL: cir.func @_Z2f3v() -> !s32i
+// CIR-LABEL: cir.func @_Z2f4v() -> !s32i
+// CIR:         cir.call @_Z2f3v() : () -> !s32i
+
+// LLVM-LABEL: define i32 @_Z2f4v() {
+// LLVM:         %{{.+}} = call i32 @_Z2f3v()
 
 int f5(int a, int *b, bool c);
 int f6() {
@@ -25,8 +34,11 @@ int f6() {
   return f5(2, &b, false);
 }
 
-// CHECK-LABEL: cir.func @_Z2f6v() -> !s32i
-// CHECK:         %[[#b:]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init]
-// CHECK:         %[[#a:]] = cir.const #cir.int<2> : !s32i
-// CHECK-NEXT:    %[[#c:]] = cir.const #false
-// CHECK-NEXT:    %{{.+}} = cir.call @_Z2f5iPib(%[[#a]], %[[#b:]], %[[#c]]) : (!s32i, !cir.ptr<!s32i>, !cir.bool) -> !s32i
+// CIR-LABEL: cir.func @_Z2f6v() -> !s32i
+// CIR:         %[[#b:]] = cir.alloca !s32i, !cir.ptr<!s32i>, ["b", init]
+// CIR:         %[[#a:]] = cir.const #cir.int<2> : !s32i
+// CIR-NEXT:    %[[#c:]] = cir.const #false
+// CIR-NEXT:    %{{.+}} = cir.call @_Z2f5iPib(%[[#a]], %[[#b:]], %[[#c]]) : (!s32i, !cir.ptr<!s32i>, !cir.bool) -> !s32i
+
+// LLVM-LABEL: define i32 @_Z2f6v() {
+// LLVM:         %{{.+}} = call i32 @_Z2f5iPib(i32 2, ptr %{{.+}}, i1 false)

``````````

</details>

author_association: MEMBER