(link to GitHub issue: Error in code generation for calling instance methods that return structs on Windows · Issue #76820 · swiftlang/swift · GitHub)
Description
I discovered a bug on the code generation when an instance method defined on a C++ class is called from Swift and returns a struct. From the Swift side, it pushes the struct's stack address on rcx
and the this
pointer on rdx
, but C++ expects this
on rcx
instead, and the returned struct goes on rdx
.
Reproduction
There are two targets, a C++ one and a Swift one.
Package.swift:
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "SwiftTest",
products: [
//library(name: "CppProj", targets: ["CppProj"]),
.executable(name: "SwiftProj", targets: ["SwiftProj"])
],
targets: [
.target(name: "CppProj",
swiftSettings: [.interoperabilityMode(.Cxx)]
),
.executableTarget(name: "SwiftProj",
dependencies: ["CppProj"],
swiftSettings: [.interoperabilityMode(.Cxx)]
)
]
)
Sources/CppProj/include/CppProj.hpp:
#pragma once
#include <swift/bridging>
struct MyStruct
{
unsigned long long val0, val1, val2, val3, val4;
};
class MyClass final
{
static MyClass instance;
public:
static MyClass* get();
void test() const;
MyStruct getStruct() const;
} SWIFT_IMMORTAL_REFERENCE;
Sources/CppProj/CppProj.cpp:
#include "CppProj.hpp"
#include <cstdio>
MyClass MyClass::instance;
MyClass* MyClass::get()
{
printf("instance = %p\n", &instance);
return &instance;
}
void MyClass::test() const
{
printf("this = %p\n", this);
}
MyStruct MyClass::getStruct() const
{
MyStruct str = {2, 4, 6, 8, (uintptr_t)this};
printf("this = %p\n", this);
return str;
}
Sources/SwiftProj/main.swift:
import CppProj
do
{
let instance = MyClass.get()!
instance.test()
let str = instance.getStruct()
debugPrint(str)
}
Expected behavior
The expected behavior would be to print instance
, this
and this
, all having the same value.
However, a sample run shows this:
instance = 00007FF6969241C8
this = 00007FF6969241C8
this = 0000007D782FFA90
Environment
Swift version 6.0.1 (swift-6.0.1-RELEASE)
Target: x86_64-unknown-windows-msvc
Additional information
I attached a debugger to the program and set breakpoints on line 21 (printf("this = %p\n", this);
) of CppProj.cpp, and inspected both stack frames.
Running di -f
on the MyClass::getStruct(void) const
stack frame shows:
SwiftProj.exe`struct MyStruct MyClass::getStruct(void) const:
0x7ff640c61300 <+0>: push rsi
0x7ff640c61301 <+1>: sub rsp, 0x20
0x7ff640c61305 <+5>: mov rsi, rdx
0x7ff640c61308 <+8>: mov rdx, rcx
0x7ff640c6130b <+11>: mov qword ptr [rsi], 0x2
0x7ff640c61312 <+18>: mov qword ptr [rsi + 0x8], 0x4
0x7ff640c6131a <+26>: mov qword ptr [rsi + 0x10], 0x6
0x7ff640c61322 <+34>: mov qword ptr [rsi + 0x18], 0x8
0x7ff640c6132a <+42>: mov qword ptr [rsi + 0x20], rcx
0x7ff640c6132e <+46>: lea rcx, [rip + 0x1f1b] ; "this = %p\n"
0x7ff640c61335 <+53>: call 0x7ff640c61350 ; ::printf(const char *const, ...) at stdio.h:956
0x7ff640c6133a <+58>: mov rax, rsi
0x7ff640c6133d <+61>: add rsp, 0x20
0x7ff640c61341 <+65>: pop rsi
0x7ff640c61342 <+66>: ret
Which clearly shows that this
is expected on rcx
, and the return value is expected on rdx
.
However, di -f
on the main.swift
stack frame, on the line that says let str = instance.getStruct()
, shows:
SwiftProj.exe`main:
0x7ff640c613c0 <+0>: push rsi
0x7ff640c613c1 <+1>: push rdi
0x7ff640c613c2 <+2>: push rbx
0x7ff640c613c3 <+3>: sub rsp, 0x80
0x7ff640c613ca <+10>: movaps xmmword ptr [rsp + 0x70], xmm7
0x7ff640c613cf <+15>: movaps xmmword ptr [rsp + 0x60], xmm6
0x7ff640c613d4 <+20>: call 0x7ff640c612b0 ; class MyClass * MyClass::get(void) at CppProj.cpp:8
0x7ff640c613d9 <+25>: test rax, rax
0x7ff640c613dc <+28>: je 0x7ff640c614d0 ; <+272> [inlined] Swift runtime failure: Unexpectedly found nil while unwrapping an Optional value at <compiler-generated>
0x7ff640c613e2 <+34>: mov rsi, rax
0x7ff640c613e5 <+37>: mov rcx, rax
0x7ff640c613e8 <+40>: call 0x7ff640c612e0 ; void MyClass::test(void) const at CppProj.cpp:14
0x7ff640c613ed <+45>: lea rcx, [rsp + 0x30]
0x7ff640c613f2 <+50>: mov rdx, rsi
0x7ff640c613f5 <+53>: call 0x7ff640c61300 ; struct MyStruct MyClass::getStruct(void) const at CppProj.cpp:19
0x7ff640c613fa <+58>: mov rdi, qword ptr [rsp + 0x50]
0x7ff640c613ff <+63>: movaps xmm6, xmmword ptr [rsp + 0x30]
0x7ff640c61404 <+68>: movaps xmm7, xmmword ptr [rsp + 0x40]
An interesting thing to notice here is that, while it passes this
on rcx
to void MyClass::test(void) const
, it passes this
on rdx
(which is wrong) to MyStruct MyClass::getStruct(void) const
, and the returned struct's address goes on rcx
.