Skip to content

Conversation

@henrybw
Copy link

@henrybw henrybw commented Sep 7, 2025

The MSVC ABI usually returns vector types directly, but on x86 and x86-64, there seems to be a special case for C++ member functions, which return vector types indirectly (with the exception of member functions using the __vectorcall calling convention, which return vector types > 64 bits directly).

This is an ABI change and has the potential to cause backward compatibility issues with previous Clang releases.

Fixes #104.

The MSVC ABI almost always returns vector types directly, but on x86 and x86_64,
there seems to be a special case for member functions, which return vector types
indirectly.

Fixes llvm#104.
@github-actions
Copy link

github-actions bot commented Sep 7, 2025

Thank you for submitting a Pull Request (PR) to the LLVM Project!

This PR will be automatically labeled and the relevant teams will be notified.

If you wish to, you can add reviewers by using the "Reviewers" section on this page.

If this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using @ followed by their GitHub username.

If you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.

If you have further questions, they may be answered by the LLVM GitHub User Guide.

You can also ask questions in a comment on this PR, on the LLVM Discord or on the forums.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels Sep 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Sep 7, 2025

@llvm/pr-subscribers-clang

Author: Henry Baba-Weiss (henrybw)

Changes

The MSVC ABI almost always returns vector types directly, but on x86 and x86_64, there seems to be a special case for member functions, which return vector types indirectly.

Fixes #104.


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

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+3)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+14-9)
  • (added) clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp (+36)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d1ef91b7e7c14..1be917221aa2c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -90,6 +90,9 @@ C++ Specific Potentially Breaking Changes
 ABI Changes in This Version
 ---------------------------
 
+- Fixed Microsoft calling convention for returning vector types from C++ member
+  functions. Such vector types should be returned indirectly. (GH#104)
+
 AST Dumping Potentially Breaking Changes
 ----------------------------------------
 - How nested name specifiers are dumped and printed changes, keeping track of clang AST changes.
diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 88f0648660965..2d76eebcecd08 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -1168,15 +1168,20 @@ static bool isTrivialForMSVC(const CXXRecordDecl *RD, QualType Ty,
 }
 
 bool MicrosoftCXXABI::classifyReturnType(CGFunctionInfo &FI) const {
-  const CXXRecordDecl *RD = FI.getReturnType()->getAsCXXRecordDecl();
-  if (!RD)
-    return false;
-
-  bool isTrivialForABI = RD->canPassInRegisters() &&
-                         isTrivialForMSVC(RD, FI.getReturnType(), CGM);
-
-  // MSVC always returns structs indirectly from C++ instance methods.
-  bool isIndirectReturn = !isTrivialForABI || FI.isInstanceMethod();
+  bool isIndirectReturn = false;
+  if (const CXXRecordDecl *RD = FI.getReturnType()->getAsCXXRecordDecl()) {
+    bool isTrivialForABI = RD->canPassInRegisters() &&
+                           isTrivialForMSVC(RD, FI.getReturnType(), CGM);
+
+    // MSVC always returns structs indirectly from C++ instance methods.
+    isIndirectReturn = !isTrivialForABI || FI.isInstanceMethod();
+  } else if (isa<VectorType>(FI.getReturnType())) {
+    // On x86, MSVC seems to only return vector types indirectly from non-
+    // vectorcall C++ instance methods.
+    isIndirectReturn =
+        CGM.getTarget().getTriple().isX86() && FI.isInstanceMethod() &&
+        FI.getCallingConvention() != llvm::CallingConv::X86_VectorCall;
+  }
 
   if (isIndirectReturn) {
     CharUnits Align = CGM.getContext().getTypeAlignInChars(FI.getReturnType());
diff --git a/clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp b/clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp
new file mode 100644
index 0000000000000..e046fb4bb3169
--- /dev/null
+++ b/clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -ffreestanding -emit-llvm %s -o - -triple=i686-pc-windows-msvc | FileCheck %s
+// RUN: %clang_cc1 -ffreestanding -emit-llvm %s -o - -triple=x86_64-pc-windows-msvc | FileCheck %s
+
+// To match the MSVC ABI, vector types must be returned indirectly from member
+// functions (as long as they do not use the vectorcall calling convention),
+// but must be returned directly everywhere else.
+
+#include <xmmintrin.h>
+
+struct Foo {
+  __m128 method_m128();
+  __m128 __vectorcall vectorcall_method_m128();
+};
+
+__m128 Foo::method_m128() {
+  return __m128{};
+// GH104
+// CHECK: store <4 x float>
+// CHECK: ret void
+}
+
+__m128 __vectorcall Foo::vectorcall_method_m128() {
+  return __m128{};
+// CHECK: ret <4 x float>
+}
+
+__m128 func_m128() {
+  return __m128{};
+// CHECK: ret <4 x float>
+}
+
+__m128 __vectorcall vectorcall_func_m128() {
+  return __m128{};
+// CHECK: ret <4 x float>
+}
+

@llvmbot
Copy link
Member

llvmbot commented Sep 7, 2025

@llvm/pr-subscribers-clang-codegen

Author: Henry Baba-Weiss (henrybw)

Changes

The MSVC ABI almost always returns vector types directly, but on x86 and x86_64, there seems to be a special case for member functions, which return vector types indirectly.

Fixes #104.


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

3 Files Affected:

  • (modified) clang/docs/ReleaseNotes.rst (+3)
  • (modified) clang/lib/CodeGen/MicrosoftCXXABI.cpp (+14-9)
  • (added) clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp (+36)
diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index d1ef91b7e7c14..1be917221aa2c 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -90,6 +90,9 @@ C++ Specific Potentially Breaking Changes
 ABI Changes in This Version
 ---------------------------
 
+- Fixed Microsoft calling convention for returning vector types from C++ member
+  functions. Such vector types should be returned indirectly. (GH#104)
+
 AST Dumping Potentially Breaking Changes
 ----------------------------------------
 - How nested name specifiers are dumped and printed changes, keeping track of clang AST changes.
diff --git a/clang/lib/CodeGen/MicrosoftCXXABI.cpp b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
index 88f0648660965..2d76eebcecd08 100644
--- a/clang/lib/CodeGen/MicrosoftCXXABI.cpp
+++ b/clang/lib/CodeGen/MicrosoftCXXABI.cpp
@@ -1168,15 +1168,20 @@ static bool isTrivialForMSVC(const CXXRecordDecl *RD, QualType Ty,
 }
 
 bool MicrosoftCXXABI::classifyReturnType(CGFunctionInfo &FI) const {
-  const CXXRecordDecl *RD = FI.getReturnType()->getAsCXXRecordDecl();
-  if (!RD)
-    return false;
-
-  bool isTrivialForABI = RD->canPassInRegisters() &&
-                         isTrivialForMSVC(RD, FI.getReturnType(), CGM);
-
-  // MSVC always returns structs indirectly from C++ instance methods.
-  bool isIndirectReturn = !isTrivialForABI || FI.isInstanceMethod();
+  bool isIndirectReturn = false;
+  if (const CXXRecordDecl *RD = FI.getReturnType()->getAsCXXRecordDecl()) {
+    bool isTrivialForABI = RD->canPassInRegisters() &&
+                           isTrivialForMSVC(RD, FI.getReturnType(), CGM);
+
+    // MSVC always returns structs indirectly from C++ instance methods.
+    isIndirectReturn = !isTrivialForABI || FI.isInstanceMethod();
+  } else if (isa<VectorType>(FI.getReturnType())) {
+    // On x86, MSVC seems to only return vector types indirectly from non-
+    // vectorcall C++ instance methods.
+    isIndirectReturn =
+        CGM.getTarget().getTriple().isX86() && FI.isInstanceMethod() &&
+        FI.getCallingConvention() != llvm::CallingConv::X86_VectorCall;
+  }
 
   if (isIndirectReturn) {
     CharUnits Align = CGM.getContext().getTypeAlignInChars(FI.getReturnType());
diff --git a/clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp b/clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp
new file mode 100644
index 0000000000000..e046fb4bb3169
--- /dev/null
+++ b/clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp
@@ -0,0 +1,36 @@
+// RUN: %clang_cc1 -ffreestanding -emit-llvm %s -o - -triple=i686-pc-windows-msvc | FileCheck %s
+// RUN: %clang_cc1 -ffreestanding -emit-llvm %s -o - -triple=x86_64-pc-windows-msvc | FileCheck %s
+
+// To match the MSVC ABI, vector types must be returned indirectly from member
+// functions (as long as they do not use the vectorcall calling convention),
+// but must be returned directly everywhere else.
+
+#include <xmmintrin.h>
+
+struct Foo {
+  __m128 method_m128();
+  __m128 __vectorcall vectorcall_method_m128();
+};
+
+__m128 Foo::method_m128() {
+  return __m128{};
+// GH104
+// CHECK: store <4 x float>
+// CHECK: ret void
+}
+
+__m128 __vectorcall Foo::vectorcall_method_m128() {
+  return __m128{};
+// CHECK: ret <4 x float>
+}
+
+__m128 func_m128() {
+  return __m128{};
+// CHECK: ret <4 x float>
+}
+
+__m128 __vectorcall vectorcall_func_m128() {
+  return __m128{};
+// CHECK: ret <4 x float>
+}
+

@henrybw
Copy link
Author

henrybw commented Sep 7, 2025

cc @RKSimon @rnk @efriedma-quic

Copy link
Contributor

@Fznamznon Fznamznon left a comment

Choose a reason for hiding this comment

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

Hi, thank you for the patch. I wonder if it can make clang ABI incompatible with its previous versions? Perhaps it makes sense to put the new behavior under -fclang-abi-compat flag.

…ctly

[clang][CodeGen][MSVC] Return vector types from methods indirectly

The MSVC ABI almost always returns vector types directly, but on x86 and x86_64,
there seems to be a special case for member functions, which return vector types
indirectly.

This is an ABI change and has the potential to cause backward compatibility
issues with previous Clang releases.

Fixes llvm#104.
@llvmbot llvmbot added the clang:frontend Language frontend issues, e.g. anything involving "Sema" label Sep 9, 2025
@henrybw
Copy link
Author

henrybw commented Sep 9, 2025

Hi, thank you for the patch. I wonder if it can make clang ABI incompatible with its previous versions? Perhaps it makes sense to put the new behavior under -fclang-abi-compat flag.

Thanks for the review! Good catch: indeed, this change will break ABI compatibility and should respect -fclang-abi-compat. I have updated the patch (and release notes) to add ABI version 21, and now only enable this behavior for newer versions of the ABI.

I wasn't sure if this warranted adding a test case to clang/test/CodeGenCXX/clang-abi-compat.cpp, but if that makes more sense than adding it to my new clang/test/CodeGenCXX/microsoft-abi-vector-types.cpp test, I'd be happy to move it.

@RKSimon RKSimon requested a review from rnk September 9, 2025 08:32
Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

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

I worry that the real invariant here is that instance methods must always pass this in the first argument register (RCX), and we've only covered a few of the cases that enable indirect return. Like, I wonder if we can hit this via member pointers.

Regardless, I don't want to expand scope, I'd rather approve the PR and handle it in a follow-up. Please update the test and we can land this.

// functions (as long as they do not use the vectorcall calling convention),
// but must be returned directly everywhere else.

#include <xmmintrin.h>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please extend this test to cover arm64. I fed it to an AI tool and it did the straightforward rewrite, which I think you can adapt with some ifdefs to make this a single test case:
https://godbolt.org/z/xWon6163j

This demonstrates that ARM64 passes vectors directly. Honestly, I found that surprising, because if you look into arm(64)_neon.h, you see that MSVC defines vectors as "intrinsic_type" structs, so it makes sense that "records" go down the path of "instance methods return structs indirectly".

Copy link
Author

Choose a reason for hiding this comment

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

Done. (I was also surprised by MSVC returning vectors directly on ARM64, but that's also why I constrained this behavior to only apply to x86/x86-64 targets.)

@rnk
Copy link
Collaborator

rnk commented Sep 11, 2025

I worry that the real invariant here is that instance methods must always pass this in the first argument register (RCX), and we've only covered a few of the cases that enable indirect return. Like, I wonder if we can hit this via member pointers.

I guess not

…ctly

Simplify the test by conditionally defining the platform's vector type.
@henrybw henrybw requested a review from rnk September 12, 2025 01:48
@henrybw
Copy link
Author

henrybw commented Sep 26, 2025

I worry that the real invariant here is that instance methods must always pass this in the first argument register (RCX), and we've only covered a few of the cases that enable indirect return. Like, I wonder if we can hit this via member pointers.

Regardless, I don't want to expand scope, I'd rather approve the PR and handle it in a follow-up. Please update the test and we can land this.

To follow up on this, I dug into this some more, and found a couple of concerning discoveries:

  • On x86-64, MSVC does not return __m64 vectors directly from vectorcall member functions: https://godbolt.org/z/aa3E918xY. This is not consistent with how MSVC returns __m128, __m256, and __m512 vectors from vectorcall member functions. But, it means that my patch does not adhere to the MSVC vectorcall ABI for __m64.
  • On ARM64, MSVC returns 64-bit and 128-bit vectors directly from member functions, matching Clang. However, MSVC also returns 256-bit and 512-bit vectors directly, whereas Clang returns them indirectly: https://godbolt.org/z/YP6M34zvq, https://godbolt.org/z/bzadn9Yre

(Note: I'm not as familiar with ARM64 as I am with x86/x86-64, so if the ABI discrepancy there is actually user error on my part, then my apologies.)

Given the discrepancy between how __m64 and __m128 are returned from x86-64 vectorcalls, I wonder if this has something to do with how MSVC handles types that can map to SSE registers (and is not specific to just vector types).

Anyway, given these findings, do we still think it's a good idea to land this PR? I'm wary of causing further churn in the Clang ABI, because my patch amounts to a partial fix, and I'd be happy to back this out in favor of a more complete fix. (This is my first time contributing to LLVM, so I'm not sure what the right call here is.)

@rnk rnk enabled auto-merge (squash) September 30, 2025 21:55
@rnk rnk disabled auto-merge September 30, 2025 21:56
Copy link
Collaborator

@rnk rnk left a comment

Choose a reason for hiding this comment

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

I think it is probably worth handling the __m64 case before landing. If we had landed, I wouldn't insist on additional ABI compat checks before fixing the bug. We're mostly focused on stable ABIs between releases.

// On x86, MSVC seems to only return vector types indirectly from non-
// vectorcall C++ instance methods.
isIndirectReturn =
CGM.getTarget().getTriple().isX86() && FI.isInstanceMethod() &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it is worth expanding this out into if blocks. I think you can put the ABI ver check into the else if condition above, and combine the vector call check with a size check for > 64 bits to be ABI compatible with __vectorcall __m64. Please add a test for that as well.

Copy link
Author

Choose a reason for hiding this comment

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

Done.

@rnk
Copy link
Collaborator

rnk commented Sep 30, 2025

Thanks for researching the __m64 case!

…ctly

[clang][CodeGen][MSVC] Match how MSVC returns vector types from member functions

The MSVC ABI usually returns vector types directly, but on x86 and x86-64, there
seems to be a special case for C++ member functions, which return vector types
indirectly (with the exception of member functions using the `__vectorcall`
calling convention, which return vector types > 64 bits directly).

This is an ABI change and has the potential to cause backward compatibility
issues with previous Clang releases.

Fixes llvm#104.
…ctly

Rework the test to also cover 64-bit vector types.
…ctly

Tweak wording of ABI note and update release note to say Clang now matches MSVC.
@henrybw henrybw changed the title [clang][CodeGen][MSVC] Return vector types from methods indirectly [clang][CodeGen][MSVC] Match how MSVC returns vector types from member functions Oct 3, 2025
@henrybw
Copy link
Author

henrybw commented Oct 3, 2025

Thanks for researching the __m64 case!

Thanks for the reviews! I have updated the PR to handle the __vectorcall __m64 case correctly, and added test cases for 64-bit vectors on x86, x86-64, and ARM64. I have also reworded the commit message and the release note slightly to reflect the updated changes.

Looks like I'll also need to rebase this before landing (my changes to the Clang ABI versions header have a merge conflict), but I wanted to get this latest iteration out for review first before rebasing, so I don't blow away the context of the existing review comments.

@henrybw henrybw requested a review from rnk October 3, 2025 04:16
@henrybw
Copy link
Author

henrybw commented Oct 14, 2025

ping

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:codegen IR generation bugs: mangling, exceptions, etc. 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.

Calling convention differs from MSVC for member functions returning SIMD types

4 participants