swiftlang / swift

The Swift Programming Language
https://swift.org
Apache License 2.0
67.69k stars 10.39k forks source link

Error in code generation for calling instance methods that return structs on Windows #76820

Open JoaoBaptMG opened 2 months ago

JoaoBaptMG commented 2 months ago

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.

JoaoBaptMG commented 2 months ago

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)