Skip to content

Conversation

@hvdijk
Copy link
Contributor

@hvdijk hvdijk commented Jun 26, 2025

ASTContext::getDefaultCallingConvention() was documented as returning "the default calling convention for the current target", but did not do this, and was never intended to do this, it has always been controlled by command-line options to deviate from the target default.

This commit changes ASTContext::getDefaultCallingConvention() to reflect the fact that it returns the context's default calling convention, not the target's default calling convention. The IsBuiltin parameter, which was used to return the target's default calling convention rather than the context's, is removed in favor of
getTargetInfo().getDefaultCallingConv() which is more explicit of the intent.

ASTContext::getDefaultCallingConvention() was documented as returning
"the default calling convention for the current target", but did not do
this, and was never intended to do this, it has always been controlled
by command-line options to deviate from the target default.

This commit changes ASTContext::getDefaultCallingConvention() to reflect
the fact that it returns the context's default calling convention, not
the target's default calling convention. The IsBuiltin parameter, which
was used to return the target's default calling convention rather than
the context's, is removed in favor of
getTargetInfo().getDefaultCallingConv() which is more explicit of the
intent.
@llvmbot llvmbot added clang Clang issues not falling into any other category backend:RISC-V clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jun 26, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 26, 2025

@llvm/pr-subscribers-clang

@llvm/pr-subscribers-backend-risc-v

Author: Harald van Dijk (hvdijk)

Changes

ASTContext::getDefaultCallingConvention() was documented as returning "the default calling convention for the current target", but did not do this, and was never intended to do this, it has always been controlled by command-line options to deviate from the target default.

This commit changes ASTContext::getDefaultCallingConvention() to reflect the fact that it returns the context's default calling convention, not the target's default calling convention. The IsBuiltin parameter, which was used to return the target's default calling convention rather than the context's, is removed in favor of
getTargetInfo().getDefaultCallingConv() which is more explicit of the intent.


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

5 Files Affected:

  • (modified) clang/include/clang/AST/ASTContext.h (+7-3)
  • (modified) clang/lib/AST/ASTContext.cpp (+31-37)
  • (modified) clang/lib/Sema/SemaExprCXX.cpp (+2-2)
  • (modified) clang/lib/Sema/SemaLookup.cpp (+2-1)
  • (modified) clang/lib/Sema/SemaRISCV.cpp (+1-1)
diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 2b9cd035623cc..d374ec6e72a71 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -2914,10 +2914,14 @@ class ASTContext : public RefCountedBase<ASTContext> {
   NestedNameSpecifier *
   getCanonicalNestedNameSpecifier(NestedNameSpecifier *NNS) const;
 
-  /// Retrieves the default calling convention for the current target.
+  /// Retrieves the default calling convention for the current context.
+  ///
+  /// The context's default calling convention may differ from the current
+  /// target's default calling convention if the -fdefault-calling-conv option
+  /// is used; to get the target's default calling convention, e.g. for built-in
+  /// functions, call getTargetInfo().getDefaultCallingConv() instead.
   CallingConv getDefaultCallingConvention(bool IsVariadic,
-                                          bool IsCXXMethod,
-                                          bool IsBuiltin = false) const;
+                                          bool IsCXXMethod) const;
 
   /// Retrieves the "canonical" template name that refers to a
   /// given template.
diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp
index e7abb18330e17..aa55018ddd249 100644
--- a/clang/lib/AST/ASTContext.cpp
+++ b/clang/lib/AST/ASTContext.cpp
@@ -12677,10 +12677,9 @@ QualType ASTContext::GetBuiltinType(unsigned Id,
 
   bool Variadic = (TypeStr[0] == '.');
 
-  FunctionType::ExtInfo EI(getDefaultCallingConvention(
-      Variadic, /*IsCXXMethod=*/false, /*IsBuiltin=*/true));
-  if (BuiltinInfo.isNoReturn(Id)) EI = EI.withNoReturn(true);
-
+  FunctionType::ExtInfo EI(Target->getDefaultCallingConv());
+  if (BuiltinInfo.isNoReturn(Id))
+    EI = EI.withNoReturn(true);
 
   // We really shouldn't be making a no-proto type here.
   if (ArgTypes.empty() && Variadic && !getLangOpts().requiresStrictPrototypes())
@@ -13064,43 +13063,38 @@ void ASTContext::forEachMultiversionedFunctionVersion(
 }
 
 CallingConv ASTContext::getDefaultCallingConvention(bool IsVariadic,
-                                                    bool IsCXXMethod,
-                                                    bool IsBuiltin) const {
+                                                    bool IsCXXMethod) const {
   // Pass through to the C++ ABI object
   if (IsCXXMethod)
     return ABI->getDefaultMethodCallConv(IsVariadic);
 
-  // Builtins ignore user-specified default calling convention and remain the
-  // Target's default calling convention.
-  if (!IsBuiltin) {
-    switch (LangOpts.getDefaultCallingConv()) {
-    case LangOptions::DCC_None:
-      break;
-    case LangOptions::DCC_CDecl:
-      return CC_C;
-    case LangOptions::DCC_FastCall:
-      if (getTargetInfo().hasFeature("sse2") && !IsVariadic)
-        return CC_X86FastCall;
-      break;
-    case LangOptions::DCC_StdCall:
-      if (!IsVariadic)
-        return CC_X86StdCall;
-      break;
-    case LangOptions::DCC_VectorCall:
-      // __vectorcall cannot be applied to variadic functions.
-      if (!IsVariadic)
-        return CC_X86VectorCall;
-      break;
-    case LangOptions::DCC_RegCall:
-      // __regcall cannot be applied to variadic functions.
-      if (!IsVariadic)
-        return CC_X86RegCall;
-      break;
-    case LangOptions::DCC_RtdCall:
-      if (!IsVariadic)
-        return CC_M68kRTD;
-      break;
-    }
+  switch (LangOpts.getDefaultCallingConv()) {
+  case LangOptions::DCC_None:
+    break;
+  case LangOptions::DCC_CDecl:
+    return CC_C;
+  case LangOptions::DCC_FastCall:
+    if (getTargetInfo().hasFeature("sse2") && !IsVariadic)
+      return CC_X86FastCall;
+    break;
+  case LangOptions::DCC_StdCall:
+    if (!IsVariadic)
+      return CC_X86StdCall;
+    break;
+  case LangOptions::DCC_VectorCall:
+    // __vectorcall cannot be applied to variadic functions.
+    if (!IsVariadic)
+      return CC_X86VectorCall;
+    break;
+  case LangOptions::DCC_RegCall:
+    // __regcall cannot be applied to variadic functions.
+    if (!IsVariadic)
+      return CC_X86RegCall;
+    break;
+  case LangOptions::DCC_RtdCall:
+    if (!IsVariadic)
+      return CC_M68kRTD;
+    break;
   }
   return Target->getDefaultCallingConv();
 }
diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp
index 4a86cbd0633b6..cfdf855f65ff1 100644
--- a/clang/lib/Sema/SemaExprCXX.cpp
+++ b/clang/lib/Sema/SemaExprCXX.cpp
@@ -3467,8 +3467,8 @@ void Sema::DeclareGlobalAllocationFunction(DeclarationName Name,
     }
   }
 
-  FunctionProtoType::ExtProtoInfo EPI(Context.getDefaultCallingConvention(
-      /*IsVariadic=*/false, /*IsCXXMethod=*/false, /*IsBuiltin=*/true));
+  FunctionProtoType::ExtProtoInfo EPI(
+      Context.getTargetInfo().getDefaultCallingConv());
 
   QualType BadAllocType;
   bool HasBadAllocExceptionSpec = Name.isAnyOperatorNew();
diff --git a/clang/lib/Sema/SemaLookup.cpp b/clang/lib/Sema/SemaLookup.cpp
index aa7191d2814fe..6d6e07a2c03c7 100644
--- a/clang/lib/Sema/SemaLookup.cpp
+++ b/clang/lib/Sema/SemaLookup.cpp
@@ -22,6 +22,7 @@
 #include "clang/AST/ExprCXX.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/LangOptions.h"
+#include "clang/Basic/TargetInfo.h"
 #include "clang/Lex/HeaderSearch.h"
 #include "clang/Lex/ModuleLoader.h"
 #include "clang/Lex/Preprocessor.h"
@@ -777,7 +778,7 @@ static void GetOpenCLBuiltinFctOverloads(
     std::vector<QualType> &FunctionList, SmallVector<QualType, 1> &RetTypes,
     SmallVector<SmallVector<QualType, 1>, 5> &ArgTypes) {
   FunctionProtoType::ExtProtoInfo PI(
-      Context.getDefaultCallingConvention(false, false, true));
+      Context.getTargetInfo().getDefaultCallingConv());
   PI.Variadic = false;
 
   // Do not attempt to create any FunctionTypes if there are no return types,
diff --git a/clang/lib/Sema/SemaRISCV.cpp b/clang/lib/Sema/SemaRISCV.cpp
index 9eab0c2a0df6a..c8b925156ed0b 100644
--- a/clang/lib/Sema/SemaRISCV.cpp
+++ b/clang/lib/Sema/SemaRISCV.cpp
@@ -417,7 +417,7 @@ void RISCVIntrinsicManagerImpl::CreateRVVIntrinsicDecl(LookupResult &LR,
     ArgTypes.push_back(RVVType2Qual(Context, Sigs[i]));
 
   FunctionProtoType::ExtProtoInfo PI(
-      Context.getDefaultCallingConvention(false, false, true));
+      Context.getTargetInfo().getDefaultCallingConv());
 
   PI.Variadic = false;
 

@hvdijk
Copy link
Contributor Author

hvdijk commented Jun 26, 2025

Additional context: although I see this as a code cleanup for LLVM, my primary motivation for submitting this is to make it easier for downstream users to have some built-ins use other calling conventions.

@hvdijk hvdijk requested a review from erichkeane June 30, 2025 14:32
@hvdijk
Copy link
Contributor Author

hvdijk commented Jun 30, 2025

@erichkeane Since you commited 0002281 which was the last significant change to the function, would you be able to comment on whether this seems reasonable to you?

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

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

This seems reasonable to me, though my knowledge of this is limited (I might be in the blame becasue I did a huge refactor here in the past).

@efriedma-quic perhaps has a comment?

@efriedma-quic
Copy link
Collaborator

"IsBuiltin" doesn't really mean whether something is "builtin", in whatever sense. It means "bypass Windows calling convention flags", which happens to apply to some functions we consider builtin. I suspect we're actually applying the bit too widely; it should probably only affect some very narrow set of functions, not arbitrary libcalls.

I think refactoring like this makes things less clear.

@hvdijk
Copy link
Contributor Author

hvdijk commented Jun 30, 2025

The way "IsBuiltin" is currently used, it means "the declaration is provided by Clang internally rather than by (or in addition to) a source or header file": those are the only places where IsBuiltin is set to true. For all of those, the fact that options such as -mrtd are ignored seems correct to me: this tries to handle the case where the library (possibly but not necessary libc) is built without -mrtd, but the application is built with -mrtd. And likewise for other calling convention-affecting options. For arbitrary functions that are declared by the library's header files, the header files can specify __cdecl to support such use, but when the declarations are within the compiler itself, the current IsBuiltin approach is the only way that I can see that can let this work.

If you want to reduce which declarations IsBuiltin is applied to, which ones should it not get applied to, and how should those functions be handled instead?

@efriedma-quic
Copy link
Collaborator

rvv intrinsics don't have a real calling convention at all; they're internally lowered by the compiler, so the calling convention doesn't actually matter. I suspect the same is true for opencl builtins.

Looking again, I guess for C library libcalls, we assume the user's C library actually marks everything __cdecl. Or the user is building with -ffreestanding. I guess that's self-consistent, so we actually do want to pass IsBuiltin.

This leaves two places where IsBuiltin actually has an effect we want. And each of those probably needs a comment explaining what's happening, so we don't copy-paste the code elsewhere. I guess if we have that comment, it doesn't matter that much whether we call getDefaultCallingConvention with IsBuiltin=false, or bypass it to use getTargetInfo().getDefaultCallingConv(), but going through getDefaultCallingConvention() with the flag makes it more obvious we're doing something unusual.

@hvdijk
Copy link
Contributor Author

hvdijk commented Jun 30, 2025

The calling convention doesn't just apply at the LLVM level though. For that, you're right it wouldn't make a difference for RVV intrinsics, but it also applies at the Clang level to determine whether Clang passes arguments by value or by reference, and whether it promotes arguments. Would it not still be important for that for RVV intrinsics?

For OpenCL builtins, this definitely does makes a difference. The OpenCL builtins aren't like LLVM intrinsics, they're library functions provided by libclc or equivalent and the same argument as for libc functions applies: libclc would be expected to be compiled without calling convention-affecting options, and if a user's kernel is compiled with calling convention-affecting options, things again break if the caller and callee side don't agree on which ABI to use for built-ins.

I do think the effect of IsBuiltin is exactly what we want in all places it's currently used.

I guess if we have that comment, it doesn't matter that much whether we call getDefaultCallingConvention with IsBuiltin=false, or bypass it to use getTargetInfo().getDefaultCallingConv(), but going through getDefaultCallingConvention() with the flag makes it more obvious we're doing something unusual.

If we want this for all built-in functions, getTargetInfo().getDefaultCallingConv() becomes the normal thing to call there, it wouldn't be unusual and wouldn't need to be called out specifically.

If we only want this for some built-in functions, we have a bigger mismatch between documentation and implementation. If the parameter is called IsBuiltin, I wouldn't know how to document that we should set that to true for some built-ins, and to false for other built-ins. We'd need to make some other changes there to clean up that inconsistency, which I'd be happy to submit a PR for except I don't actually know what that would need to look like yet :)

@efriedma-quic
Copy link
Collaborator

Would it not still be important for that for RVV intrinsics?

Pretty sure we bypass all the normal calling convention lowering, in this context.

The OpenCL builtins aren't like LLVM intrinsics, they're library functions provided by libclc

Oh, okay.


It's probably fine to use "builtin" mode for all builtins; the expected semantics just wasn't really clear at first glance.

Really what I don't want happening is someone copy-pastes getTargetInfo().getDefaultCallingConv() because it looks right, and doesn't realize there another whole method they're supposed to use for things that aren't actually "builtins".

@hvdijk
Copy link
Contributor Author

hvdijk commented Jul 2, 2025

Would it not still be important for that for RVV intrinsics?

Pretty sure we bypass all the normal calling convention lowering, in this context.

Ah, this is confusing. There's two types of RVV intrinsics that use this code. Many use ManualCodegen which indeed bypasses the normal calling convention lowering. But not all: for e.g. vzext, this results in a call to @llvm.riscv.vzext.* LLVM intrinsics and does use the calling convention. But then, the RISC-V calling convention attributes cannot be specified by command-line options to be the default calling convention, so it still doesn't actually make a difference at the moment.

So, I think IsBuiltin=true is currently set in all the right places, we should not change behavior.

Really what I don't want happening is someone copy-pastes getTargetInfo().getDefaultCallingConv() because it looks right, and doesn't realize there another whole method they're supposed to use for things that aren't actually "builtins".

That makes sense. I amended ASTContext::getDefaultCallingConvention's documentation to point out getTargetInfo().getDefaultCallingConv() in this PR, would it be acceptable if I likewise point out in the TargetInfo::getDefaultCallingConv() documentation when not to use it? At the moment, that documentation looks wrong anyway: it is documented as

  /// Gets the default calling convention for the given target and
  /// declaration context.
  virtual CallingConv getDefaultCallingConv() const {
    // Not all targets will specify an explicit calling convention that we can
    // express.  This will always do the right thing, even though it's not
    // an explicit calling convention.
    return CC_C;
  }

but there is no declaration context for it to take into account.

Copy link
Collaborator

@efriedma-quic efriedma-quic left a comment

Choose a reason for hiding this comment

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

LGTM

@hvdijk hvdijk merged commit 66e707e into llvm:main Jul 15, 2025
7 checks passed
@hvdijk hvdijk deleted the remove-isbuiltin branch July 15, 2025 22:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:RISC-V clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants