From e5accd5d575824d01df90952d1dcf017e1ff7660 Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Wed, 22 Oct 2025 11:03:31 +0100 Subject: [PATCH 1/2] [IRGen] Fix call generation to respect C calling conventions. On 32-bit Windows, a number of different calling conventions are in use, but Swift ignores them when generating call instructions in IRGen, and generates calls that always use the default calling convention, `cdecl`. This results in stack corruption, because `stdcall` uses the `ret ` instruction, which adds its argument to the stack pointer on return. rdar://163178024 --- lib/AST/ExtInfo.cpp | 7 +++--- lib/IRGen/GenCall.cpp | 25 ++++++++++++++++++- lib/SIL/IR/SILFunctionType.cpp | 7 ++++++ test/IRGen/Inputs/calling_conventions.h | 16 ++++++++++++ test/IRGen/Inputs/module.modulemap | 8 ++++++ test/IRGen/Inputs/win32_calling_conventions.h | 22 ++++++++++++++++ test/IRGen/calling_conventions.swift | 17 +++++++++++++ test/IRGen/win32_calling_conventions.swift | 25 +++++++++++++++++++ 8 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 test/IRGen/Inputs/calling_conventions.h create mode 100644 test/IRGen/Inputs/win32_calling_conventions.h create mode 100644 test/IRGen/calling_conventions.swift create mode 100644 test/IRGen/win32_calling_conventions.swift diff --git a/lib/AST/ExtInfo.cpp b/lib/AST/ExtInfo.cpp index aa93acc455579..52ba4b73efdf9 100644 --- a/lib/AST/ExtInfo.cpp +++ b/lib/AST/ExtInfo.cpp @@ -86,7 +86,8 @@ UnexpectedClangTypeError::checkClangType(SILFunctionTypeRepresentation silRep, if (isBlock && !type->isBlockPointerType()) return {{Kind::NotBlockPointer, type}}; if (!isBlock && !(type->isFunctionPointerType() - || type->isFunctionReferenceType())) + || type->isFunctionReferenceType() + || type->isFunctionType())) return {{Kind::NotFunctionPointerOrReference, type}}; return std::nullopt; } @@ -121,8 +122,8 @@ void UnexpectedClangTypeError::dump() { return; } case Kind::NotFunctionPointerOrReference: { - e << ("Expected function pointer/reference type for @convention(c) function" - " but found:\n"); + e << ("Expected function or function pointer/reference type for " + "@convention(c) function but found:\n"); type->dump(); return; } diff --git a/lib/IRGen/GenCall.cpp b/lib/IRGen/GenCall.cpp index e2c6bdc616c27..91caaad6491ed 100644 --- a/lib/IRGen/GenCall.cpp +++ b/lib/IRGen/GenCall.cpp @@ -1631,6 +1631,20 @@ void SignatureExpansion::expandExternalSignatureTypes() { // Generate function info for this signature. auto extInfo = clang::FunctionType::ExtInfo(); + if (auto clangTy = FnType->getClangTypeInfo().getType()) { + const clang::FunctionType *fnTy = nullptr; + + if (auto theFnTy = clangTy->getAs()) + fnTy = theFnTy; + else if (auto ptrTy = clangTy->getAs()) + fnTy = ptrTy->getPointeeType()->getAs(); + else if (auto refTy = clangTy->getAs()) + fnTy = refTy->getPointeeType()->getAs(); + + if (fnTy) + extInfo = fnTy->getExtInfo(); + } + bool isCXXMethod = FnType->getRepresentation() == SILFunctionTypeRepresentation::CXXMethod; auto &FI = isCXXMethod ? @@ -2438,9 +2452,18 @@ Signature SignatureExpansion::getSignature() { (FnType->getLanguage() == SILFunctionLanguage::C) && "C function type without C function info"); - auto callingConv = + // If we have foreign type information from Clang, take the calling + // convention from there. Otherwise, pick one based on the function + // type representation. + llvm::CallingConv::ID callingConv; + + if (ForeignInfo.ClangInfo != nullptr) { + callingConv = ForeignInfo.ClangInfo->getEffectiveCallingConvention(); + } else { + callingConv = expandCallingConv(IGM, FnType->getRepresentation(), FnType->isAsync(), FnType->isCalleeAllocatedCoroutine()); + } Signature result; result.Type = llvmType; diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index 7bf39297ee23e..eab2041f177e8 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -4033,6 +4033,13 @@ static CanSILFunctionType getSILFunctionTypeForClangDecl( if (auto func = dyn_cast(clangDecl)) { auto clangType = func->getType().getTypePtr(); + + if (clangType) { + // Pass the Clang type through, so we can extract information from it + // later on. + extInfoBuilder = extInfoBuilder.withClangFunctionType(clangType); + } + AbstractionPattern origPattern = foreignInfo.self.isImportAsMember() ? AbstractionPattern::getCFunctionAsMethod(origType, clangType, diff --git a/test/IRGen/Inputs/calling_conventions.h b/test/IRGen/Inputs/calling_conventions.h new file mode 100644 index 0000000000000..f80aca0fe0af5 --- /dev/null +++ b/test/IRGen/Inputs/calling_conventions.h @@ -0,0 +1,16 @@ +#ifndef CALLING_CONVENTIONS_H_ +#define CALLING_CONVENTIONS_H_ + +int cdecl_fn(int a, int b, int c); + +typedef int (*cdecl_fn_ptr_t)(int a, int b, int c); + +extern cdecl_fn_ptr_t cdecl_fn_ptr; + +int __attribute__((swiftcall)) swiftcc_fn(int a, int b, int c); + +typedef int (__attribute__((swiftcall)) *swiftcc_fn_ptr_t)(int a, int b, int c); + +extern swiftcc_fn_ptr_t swiftcc_fn_ptr; + +#endif /* CALLING_CONVENTIONS_H_ */ diff --git a/test/IRGen/Inputs/module.modulemap b/test/IRGen/Inputs/module.modulemap index 2e8f20135147f..fb8a337deb5b8 100644 --- a/test/IRGen/Inputs/module.modulemap +++ b/test/IRGen/Inputs/module.modulemap @@ -43,3 +43,11 @@ module CFBridgedType { module RawLayoutCXX { header "raw_layout_cxx.h" } + +module Win32CallingConventions { + header "win32_calling_conventions.h" +} + +module CallingConventions { + header "calling_conventions.h" +} diff --git a/test/IRGen/Inputs/win32_calling_conventions.h b/test/IRGen/Inputs/win32_calling_conventions.h new file mode 100644 index 0000000000000..f3f44a4a740f1 --- /dev/null +++ b/test/IRGen/Inputs/win32_calling_conventions.h @@ -0,0 +1,22 @@ +#ifndef STDCALL_FUNCTION_H_ +#define STDCALL_FUNCTION_H_ + +int __stdcall stdcall_fn(int a, int b, int c); + +typedef int (__stdcall *stdcall_fn_ptr_t)(int a, int b, int c); + +extern stdcall_fn_ptr_t stdcall_fn_ptr; + +int __fastcall fastcall_fn(int a, int b, int c); + +typedef int (__fastcall *fastcall_fn_ptr_t)(int a, int b, int c); + +extern fastcall_fn_ptr_t fastcall_fn_ptr; + +int __vectorcall vectorcall_fn(int a, int b, int c); + +typedef int (__vectorcall *vectorcall_fn_ptr_t)(int a, int b, int c); + +extern vectorcall_fn_ptr_t vectorcall_fn_ptr; + +#endif /* STDCALL_FUNCTION_H_ */ diff --git a/test/IRGen/calling_conventions.swift b/test/IRGen/calling_conventions.swift new file mode 100644 index 0000000000000..1b7e9cd81618c --- /dev/null +++ b/test/IRGen/calling_conventions.swift @@ -0,0 +1,17 @@ +// RUN: %target-swift-frontend -module-name calling_conventions -use-clang-function-types -parse-as-library -I %S/Inputs -emit-ir %s | %FileCheck %s + +import CallingConventions + +func foo() { + // CHECK: call i32 @cdecl_fn(i32 1, i32 2, i32 3) + _ = cdecl_fn(1, 2, 3) + + // CHECK: call i32 %{{[0-9]+}}(i32 4, i32 5, i32 6) + _ = cdecl_fn_ptr(4, 5, 6) + + // CHECK: call swiftcc i32 @swiftcc_fn(i32 1, i32 2, i32 3) + _ = swiftcc_fn(1, 2, 3) + + // CHECK: call swiftcc i32 %{{[0-9]+}}(i32 4, i32 5, i32 6) + _ = swiftcc_fn_ptr(4, 5, 6) +} diff --git a/test/IRGen/win32_calling_conventions.swift b/test/IRGen/win32_calling_conventions.swift new file mode 100644 index 0000000000000..9d308fb5e62d9 --- /dev/null +++ b/test/IRGen/win32_calling_conventions.swift @@ -0,0 +1,25 @@ +// RUN: %swift-frontend-plain -target i686-unknown-windows-msvc -module-name win32_calling_conventions -use-clang-function-types -parse-as-library -I %S/Inputs -emit-ir %s | %FileCheck %s + +// REQUIRES: OS=windows-msvc + +import Win32CallingConventions + +func foo() { + // CHECK: call x86_stdcallcc i32 @cdecl_fn(i32 1, i32 2, i32 3) + stdcall_fn(1, 2, 3) + + // CHECK: call x86_stdcallcc i32 %{{[0-9]+}}(i32 4, i32 5, i32 6) + stdcall_fn_ptr(4, 5, 6) + + // CHECK: call x86_fastcallcc i32 @cdecl_fn(i32 1, i32 2, i32 3) + fastcall_fn(1, 2, 3) + + // CHECK: call x86_fastcallcc i32 %{{[0-9]+}}(i32 4, i32 5, i32 6) + fastcall_fn_ptr(4, 5, 6) + + // CHECK: call x86_vectorcallcc i32 @cdecl_fn(i32 1, i32 2, i32 3) + vectorcall_fn(1, 2, 3) + + // CHECK: call x86_vectorcallcc i32 %{{[0-9]+}}(i32 4, i32 5, i32 6) + vectorcall_fn_ptr(4, 5, 6) +} From ed97eea0af385914a1ad97be92c7d4c7df46253d Mon Sep 17 00:00:00 2001 From: Alastair Houghton Date: Thu, 23 Oct 2025 16:59:25 +0100 Subject: [PATCH 2/2] [Tests] Fix unexploded-calls test. We're now passing through the calling convention to LLVM IR (which is correct, but we weren't doing previously). rdar://163178024 --- test/IRGen/unexploded-calls.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/IRGen/unexploded-calls.swift b/test/IRGen/unexploded-calls.swift index 661cec671b196..c43701d7b2d12 100644 --- a/test/IRGen/unexploded-calls.swift +++ b/test/IRGen/unexploded-calls.swift @@ -28,6 +28,6 @@ public func g(_ s : S) { // CHECK: [[ALLOCA]].g._value = getelementptr inbounds{{.*}} %TSf, ptr [[ALLOCA]].g, i32 0, i32 0 // CHECK: store float %1, ptr [[ALLOCA]].g._value, align 4 // CHECK: %[[LOAD:.*]] = load %struct.S, ptr [[ALLOCA]], align 4 -// CHECK: call void @f(%struct.S %[[LOAD]]) +// CHECK: call arm_aapcs_vfpcc void @f(%struct.S %[[LOAD]]) // CHECK: }