Skip to content

[CIR] Call to variadic functions #141942

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 31, 2025
Merged

[CIR] Call to variadic functions #141942

merged 1 commit into from
May 31, 2025

Conversation

Lancern
Copy link
Member

@Lancern Lancern commented May 29, 2025

This PR adds support for calling variadic functions in CIR.

@Lancern Lancern requested a review from andykaylor May 29, 2025 13:33
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels May 29, 2025
@llvmbot
Copy link
Member

llvmbot commented May 29, 2025

@llvm/pr-subscribers-clangir

Author: Sirui Mu (Lancern)

Changes

This PR adds support for calling variadic functions in CIR.


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

7 Files Affected:

  • (modified) clang/include/clang/CIR/MissingFeatures.h (-1)
  • (modified) clang/lib/CIR/CodeGen/CIRGenCall.cpp (+17-3)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h (+20)
  • (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+3-3)
  • (modified) clang/test/CIR/CodeGen/call.cpp (+14)
  • (modified) clang/test/CIR/IR/invalid-call.cir (+12)
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 56bf9b1130f12..f7385a672f6af 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -100,7 +100,6 @@ struct MissingFeatures {
   static bool opCallAttrs() { return false; }
   static bool opCallSurroundingTry() { return false; }
   static bool opCallASTAttr() { return false; }
-  static bool opCallVariadic() { return false; }
   static bool opCallObjCMethod() { return false; }
   static bool opCallExtParameterInfo() { return false; }
   static bool opCallCIRGenFuncInfoParamInfo() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 1317f8c6c073a..ea76ddb7f40ec 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -122,7 +122,7 @@ arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule &cgm,
 
   if (const auto *proto = dyn_cast<FunctionProtoType>(fnType)) {
     if (proto->isVariadic())
-      cgm.errorNYI("call to variadic function");
+      required = RequiredArgs::forPrototypePlus(proto, 0);
     if (proto->hasExtParameterInfos())
       cgm.errorNYI("call to functions with extra parameter info");
   } else if (cgm.getTargetCIRGenInfo().isNoProtoCallVariadic(
@@ -409,6 +409,18 @@ void CIRGenFunction::emitCallArg(CallArgList &args, const clang::Expr *e,
   args.add(emitAnyExprToTemp(e), argType);
 }
 
+QualType CIRGenFunction::getVarArgType(const Expr *arg) {
+  // System headers on Windows define NULL to 0 instead of 0LL on Win64. MSVC
+  // implicitly widens null pointer constants that are arguments to varargs
+  // functions to pointer-sized ints.
+  if (!getTarget().getTriple().isOSWindows())
+    return arg->getType();
+
+  assert(!cir::MissingFeatures::msabi());
+  cgm.errorNYI(arg->getSourceRange(), "getVarArgType: NYI for Windows target");
+  return {};
+}
+
 /// Similar to emitAnyExpr(), however, the result will always be accessible
 /// even if no aggregate location is provided.
 RValue CIRGenFunction::emitAnyExprToTemp(const Expr *e) {
@@ -429,18 +441,20 @@ void CIRGenFunction::emitCallArgs(
   assert(!cir::MissingFeatures::opCallCallConv());
 
   // First, if a prototype was provided, use those argument types.
-  assert(!cir::MissingFeatures::opCallVariadic());
+  bool isVariadic = false;
   if (prototype.p) {
     assert(!cir::MissingFeatures::opCallObjCMethod());
 
     const auto *fpt = cast<const FunctionProtoType *>(prototype.p);
+    isVariadic = fpt->isVariadic();
+    assert(!cir::MissingFeatures::opCallCallConv());
     argTypes.assign(fpt->param_type_begin() + paramsToSkip,
                     fpt->param_type_end());
   }
 
   // If we still have any arguments, emit them using the type of the argument.
   for (const clang::Expr *a : llvm::drop_begin(argRange, argTypes.size()))
-    argTypes.push_back(a->getType());
+    argTypes.push_back(isVariadic ? getVarArgType(a) : a->getType());
   assert(argTypes.size() == (size_t)(argRange.end() - argRange.begin()));
 
   // We must evaluate arguments from right to left in the MS C++ ABI, because
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 74f2e4043933d..0badde024b166 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -106,6 +106,7 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   CIRGenTypes &getTypes() const { return cgm.getTypes(); }
 
+  const TargetInfo &getTarget() const { return cgm.getTarget(); }
   mlir::MLIRContext &getMLIRContext() { return cgm.getMLIRContext(); }
 
 private:
@@ -791,6 +792,9 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   void emitOpenACCDeclare(const OpenACCDeclareDecl &d);
   void emitOpenACCRoutine(const OpenACCRoutineDecl &d);
+
+private:
+  QualType getVarArgType(const Expr *arg);
 };
 
 } // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
index 9886574fd463b..759d019de52f0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
@@ -39,6 +39,26 @@ class RequiredArgs {
 
   bool allowsOptionalArgs() const { return numRequired != ~0U; }
 
+  /// Compute the arguments required by the given formal prototype, given that
+  /// there may be some additional, non-formal arguments in play.
+  ///
+  /// If FD is not null, this will consider pass_object_size params in FD.
+  static RequiredArgs
+  forPrototypePlus(const clang::FunctionProtoType *prototype,
+                   unsigned additional) {
+    if (!prototype->isVariadic())
+      return All;
+
+    if (prototype->hasExtParameterInfos())
+      additional += llvm::count_if(
+          prototype->getExtParameterInfos(),
+          [](const clang::FunctionProtoType::ExtParameterInfo &extInfo) {
+            return extInfo.hasPassObjectSize();
+          });
+
+    return RequiredArgs(prototype->getNumParams() + additional);
+  }
+
   /// Compute the arguments required by the given formal prototype, given that
   /// there may be some additional, non-formal arguments in play.
   ///
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 9e2b2908b22d8..3e6d22879a78c 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -601,10 +601,10 @@ verifyCallCommInSymbolUses(mlir::Operation *op,
     unsigned numCallOperands = callIf.getNumArgOperands();
     unsigned numFnOpOperands = fnType.getNumInputs();
 
-    assert(!cir::MissingFeatures::opCallVariadic());
-
-    if (numCallOperands != numFnOpOperands)
+    if (!fnType.isVarArg() && numCallOperands != numFnOpOperands)
       return op->emitOpError("incorrect number of operands for callee");
+    if (fnType.isVarArg() && numCallOperands < numFnOpOperands)
+      return op->emitOpError("too few operands for callee");
 
     for (unsigned i = 0, e = numFnOpOperands; i != e; ++i)
       if (callIf.getArgOperand(i).getType() != fnType.getInput(i))
diff --git a/clang/test/CIR/CodeGen/call.cpp b/clang/test/CIR/CodeGen/call.cpp
index 792f57afd6bd1..741cadeb5c764 100644
--- a/clang/test/CIR/CodeGen/call.cpp
+++ b/clang/test/CIR/CodeGen/call.cpp
@@ -56,3 +56,17 @@ int f7(int (*ptr)(int, int)) {
 // LLVM-LABEL: define i32 @_Z2f7PFiiiE
 // LLVM:         %[[#ptr:]] = load ptr, ptr %{{.+}}
 // LLVM-NEXT:    %{{.+}} = call i32 %[[#ptr]](i32 1, i32 2)
+
+void f8(int a, ...);
+void f9() {
+  f8(1);
+  f8(1, 2, 3, 4);
+}
+
+// CIR-LABEL: cir.func @_Z2f9v()
+// CIR:         cir.call @_Z2f8iz(%{{.+}}) : (!s32i) -> ()
+// CIR:         cir.call @_Z2f8iz(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) : (!s32i, !s32i, !s32i, !s32i) -> ()
+
+// LLVM-LABEL: define void @_Z2f9v()
+// LLVM:         call void (i32, ...) @_Z2f8iz(i32 1)
+// LLVM:         call void (i32, ...) @_Z2f8iz(i32 1, i32 2, i32 3, i32 4)
diff --git a/clang/test/CIR/IR/invalid-call.cir b/clang/test/CIR/IR/invalid-call.cir
index 8a584bae70878..3ebb771ed72e7 100644
--- a/clang/test/CIR/IR/invalid-call.cir
+++ b/clang/test/CIR/IR/invalid-call.cir
@@ -68,3 +68,15 @@ cir.func @f11() {
   cir.call @f10(%0, %1) : (!s32i, !u32i) -> ()
   cir.return
 }
+
+// -----
+
+!s32i = !cir.int<s, 32>
+
+cir.func @f12(!s32i, !s32i, ...)
+cir.func @f13() {
+  %0 = cir.const #cir.int<1> : !s32i
+  // expected-error @below {{too few operands for callee}}
+  cir.call @f12(%0) : (!s32i) -> ()
+  cir.return
+}

@llvmbot
Copy link
Member

llvmbot commented May 29, 2025

@llvm/pr-subscribers-clang

Author: Sirui Mu (Lancern)

Changes

This PR adds support for calling variadic functions in CIR.


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

7 Files Affected:

  • (modified) clang/include/clang/CIR/MissingFeatures.h (-1)
  • (modified) clang/lib/CIR/CodeGen/CIRGenCall.cpp (+17-3)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+4)
  • (modified) clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h (+20)
  • (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+3-3)
  • (modified) clang/test/CIR/CodeGen/call.cpp (+14)
  • (modified) clang/test/CIR/IR/invalid-call.cir (+12)
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 56bf9b1130f12..f7385a672f6af 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -100,7 +100,6 @@ struct MissingFeatures {
   static bool opCallAttrs() { return false; }
   static bool opCallSurroundingTry() { return false; }
   static bool opCallASTAttr() { return false; }
-  static bool opCallVariadic() { return false; }
   static bool opCallObjCMethod() { return false; }
   static bool opCallExtParameterInfo() { return false; }
   static bool opCallCIRGenFuncInfoParamInfo() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 1317f8c6c073a..ea76ddb7f40ec 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -122,7 +122,7 @@ arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule &cgm,
 
   if (const auto *proto = dyn_cast<FunctionProtoType>(fnType)) {
     if (proto->isVariadic())
-      cgm.errorNYI("call to variadic function");
+      required = RequiredArgs::forPrototypePlus(proto, 0);
     if (proto->hasExtParameterInfos())
       cgm.errorNYI("call to functions with extra parameter info");
   } else if (cgm.getTargetCIRGenInfo().isNoProtoCallVariadic(
@@ -409,6 +409,18 @@ void CIRGenFunction::emitCallArg(CallArgList &args, const clang::Expr *e,
   args.add(emitAnyExprToTemp(e), argType);
 }
 
+QualType CIRGenFunction::getVarArgType(const Expr *arg) {
+  // System headers on Windows define NULL to 0 instead of 0LL on Win64. MSVC
+  // implicitly widens null pointer constants that are arguments to varargs
+  // functions to pointer-sized ints.
+  if (!getTarget().getTriple().isOSWindows())
+    return arg->getType();
+
+  assert(!cir::MissingFeatures::msabi());
+  cgm.errorNYI(arg->getSourceRange(), "getVarArgType: NYI for Windows target");
+  return {};
+}
+
 /// Similar to emitAnyExpr(), however, the result will always be accessible
 /// even if no aggregate location is provided.
 RValue CIRGenFunction::emitAnyExprToTemp(const Expr *e) {
@@ -429,18 +441,20 @@ void CIRGenFunction::emitCallArgs(
   assert(!cir::MissingFeatures::opCallCallConv());
 
   // First, if a prototype was provided, use those argument types.
-  assert(!cir::MissingFeatures::opCallVariadic());
+  bool isVariadic = false;
   if (prototype.p) {
     assert(!cir::MissingFeatures::opCallObjCMethod());
 
     const auto *fpt = cast<const FunctionProtoType *>(prototype.p);
+    isVariadic = fpt->isVariadic();
+    assert(!cir::MissingFeatures::opCallCallConv());
     argTypes.assign(fpt->param_type_begin() + paramsToSkip,
                     fpt->param_type_end());
   }
 
   // If we still have any arguments, emit them using the type of the argument.
   for (const clang::Expr *a : llvm::drop_begin(argRange, argTypes.size()))
-    argTypes.push_back(a->getType());
+    argTypes.push_back(isVariadic ? getVarArgType(a) : a->getType());
   assert(argTypes.size() == (size_t)(argRange.end() - argRange.begin()));
 
   // We must evaluate arguments from right to left in the MS C++ ABI, because
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 74f2e4043933d..0badde024b166 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -106,6 +106,7 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   CIRGenTypes &getTypes() const { return cgm.getTypes(); }
 
+  const TargetInfo &getTarget() const { return cgm.getTarget(); }
   mlir::MLIRContext &getMLIRContext() { return cgm.getMLIRContext(); }
 
 private:
@@ -791,6 +792,9 @@ class CIRGenFunction : public CIRGenTypeCache {
 
   void emitOpenACCDeclare(const OpenACCDeclareDecl &d);
   void emitOpenACCRoutine(const OpenACCRoutineDecl &d);
+
+private:
+  QualType getVarArgType(const Expr *arg);
 };
 
 } // namespace clang::CIRGen
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
index 9886574fd463b..759d019de52f0 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h
@@ -39,6 +39,26 @@ class RequiredArgs {
 
   bool allowsOptionalArgs() const { return numRequired != ~0U; }
 
+  /// Compute the arguments required by the given formal prototype, given that
+  /// there may be some additional, non-formal arguments in play.
+  ///
+  /// If FD is not null, this will consider pass_object_size params in FD.
+  static RequiredArgs
+  forPrototypePlus(const clang::FunctionProtoType *prototype,
+                   unsigned additional) {
+    if (!prototype->isVariadic())
+      return All;
+
+    if (prototype->hasExtParameterInfos())
+      additional += llvm::count_if(
+          prototype->getExtParameterInfos(),
+          [](const clang::FunctionProtoType::ExtParameterInfo &extInfo) {
+            return extInfo.hasPassObjectSize();
+          });
+
+    return RequiredArgs(prototype->getNumParams() + additional);
+  }
+
   /// Compute the arguments required by the given formal prototype, given that
   /// there may be some additional, non-formal arguments in play.
   ///
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 9e2b2908b22d8..3e6d22879a78c 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -601,10 +601,10 @@ verifyCallCommInSymbolUses(mlir::Operation *op,
     unsigned numCallOperands = callIf.getNumArgOperands();
     unsigned numFnOpOperands = fnType.getNumInputs();
 
-    assert(!cir::MissingFeatures::opCallVariadic());
-
-    if (numCallOperands != numFnOpOperands)
+    if (!fnType.isVarArg() && numCallOperands != numFnOpOperands)
       return op->emitOpError("incorrect number of operands for callee");
+    if (fnType.isVarArg() && numCallOperands < numFnOpOperands)
+      return op->emitOpError("too few operands for callee");
 
     for (unsigned i = 0, e = numFnOpOperands; i != e; ++i)
       if (callIf.getArgOperand(i).getType() != fnType.getInput(i))
diff --git a/clang/test/CIR/CodeGen/call.cpp b/clang/test/CIR/CodeGen/call.cpp
index 792f57afd6bd1..741cadeb5c764 100644
--- a/clang/test/CIR/CodeGen/call.cpp
+++ b/clang/test/CIR/CodeGen/call.cpp
@@ -56,3 +56,17 @@ int f7(int (*ptr)(int, int)) {
 // LLVM-LABEL: define i32 @_Z2f7PFiiiE
 // LLVM:         %[[#ptr:]] = load ptr, ptr %{{.+}}
 // LLVM-NEXT:    %{{.+}} = call i32 %[[#ptr]](i32 1, i32 2)
+
+void f8(int a, ...);
+void f9() {
+  f8(1);
+  f8(1, 2, 3, 4);
+}
+
+// CIR-LABEL: cir.func @_Z2f9v()
+// CIR:         cir.call @_Z2f8iz(%{{.+}}) : (!s32i) -> ()
+// CIR:         cir.call @_Z2f8iz(%{{.+}}, %{{.+}}, %{{.+}}, %{{.+}}) : (!s32i, !s32i, !s32i, !s32i) -> ()
+
+// LLVM-LABEL: define void @_Z2f9v()
+// LLVM:         call void (i32, ...) @_Z2f8iz(i32 1)
+// LLVM:         call void (i32, ...) @_Z2f8iz(i32 1, i32 2, i32 3, i32 4)
diff --git a/clang/test/CIR/IR/invalid-call.cir b/clang/test/CIR/IR/invalid-call.cir
index 8a584bae70878..3ebb771ed72e7 100644
--- a/clang/test/CIR/IR/invalid-call.cir
+++ b/clang/test/CIR/IR/invalid-call.cir
@@ -68,3 +68,15 @@ cir.func @f11() {
   cir.call @f10(%0, %1) : (!s32i, !u32i) -> ()
   cir.return
 }
+
+// -----
+
+!s32i = !cir.int<s, 32>
+
+cir.func @f12(!s32i, !s32i, ...)
+cir.func @f13() {
+  %0 = cir.const #cir.int<1> : !s32i
+  // expected-error @below {{too few operands for callee}}
+  cir.call @f12(%0) : (!s32i) -> ()
+  cir.return
+}

@Lancern Lancern force-pushed the cir/call-variant branch from 800e4b1 to 862cb3b Compare May 30, 2025 03:56
Copy link
Member

@bcardosolopes bcardosolopes left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Straight forward and small PR, LGTM

Copy link
Contributor

@andykaylor andykaylor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@Lancern Lancern merged commit 628a3f0 into llvm:main May 31, 2025
11 checks passed
@Lancern Lancern deleted the cir/call-variant branch May 31, 2025 03:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants