Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
JoaoBaptMG opened this issue Oct 2, 2024 · 4 comments
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels

Comments

@JoaoBaptMG
Copy link

JoaoBaptMG commented Oct 2, 2024

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 JoaoBaptMG added bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels labels Oct 2, 2024
@JoaoBaptMG
Copy link
Author

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)

@JoaoBaptMG
Copy link
Author

Any news here? It has been a few months already.

@hjyamauchi
Copy link
Contributor

I'm curious - does this reproduce with a newer release/6.0 build like 6.0.3 or the main branch build like this?

Thought that was for arm64, not x86_64, I fixed an issue about the registers used to return a struct as a value here a while ago and wonder if that helped with this at all. It might not, though.

@JoaoBaptMG
Copy link
Author

It is solved now! When running that same code again on Swift 6.0.3, it gives:

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A deviation from expected or documented behavior. Also: expected but undesirable behavior. triage needed This issue needs more specific labels
Projects
None yet
Development

No branches or pull requests

2 participants