[Windows] Error in code generation for instance methods that return structs

(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.

3 Likes

Yes, stret calls are slightly different on Windows than on Unix. There is special handling for this in the compiler, but seems that it might be insufficient.

CC: @Alex_L @egor.zhdan @hjyamauchi

I think the 'this' pointer should come before the sret param in a C++ method with MSVC so it sounds like the C++ side is correct and the Swift side is not. It might be worth checking whether it is fixed in the main branch build.

I'm not sure how to check if it's working on main, but one workaround that I found after reading how names are translated from C to Swift is, essentially, to declare it as a free function and "name" it using SWIFT_NAME, aka:

class MyClass final
{
    static MyClass instance;

public:
    static MyClass* get();
    void test() const;
    friend MyStruct MyClass_getStruct(const MyClass* self) SWIFT_NAME(MyClass.getStruct(self:));
} SWIFT_IMMORTAL_REFERENCE;
MyStruct MyClass_getStruct(const MyClass* self)
{
    MyStruct str = {2, 4, 6, 8, (uintptr_t)self};
    printf("self = %p\n", self);
    return str;
}

Will correctly return the following value:

instance = 00007FF7A74B41C8
this = 00007FF7A74B41C8
this = 00007FF7A74B41C8
__C.MyStruct(val0: 2, val1: 4, val2: 6, val3: 8, val4: 140701640376776)