Passing an extern const C struct by pointer possible?

Hi All,

I'm trying to reference a extern const C struct in Swift code to pass to another C function which expects a pointer to the struct, but have been unable to do so.

The definition is:

extern const struct wl_interface wl_compositor_interface;

and the function header is:

static void* wl_registry_bind(
    struct wl_registry *wl_registry,
    uint32_t name,
    const struct wl_interface *interface,
    uint32_t version 
)

My code is:

window._compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 1)

but I get a cannot pass immutable value as inout argument: 'wl_compositor_interface' is a 'let' constant error.

I've posted a bug about this ([SR-9589] Clang crash when calling external C library function with UnsafePointer to C struct as argument (was Can't pass an imported C extern const struct to a C function which expects a struct pointer) · Issue #52036 · apple/swift · GitHub) explaining the specifics due to a compiler crash I uncovered while trying to figure out how to achieve this, but thought I'd post here to see if there was a way to achieve it curently.

1 Like

Does it expect the pointer value to be constant, or does it just want to dereference it? If it just wants to dereference it, you can get the pointer by doing:

window._compositor = withUnsafePointer(to: wl_compositor_interface) {
    wl_registry_bind(registry, name, $0, 1)
}

I have tried that but then I get the compiler crash I mentioned in my original post - Swift compiler crash trying to pass C extern const struct by reference · GitHub

In that case, until that crash is resolved, you may just need to take a local copy of the struct:

var myCopy = wl_compositor_interface
window._compositor = wl_registry_bind(registry, name, &myCopy, 1)

That gives me the same compiler crash unfortunately. You're right, I may just have to wait till that crash is resolved.

Did you ever discover a way to do this? In Swift 5.1.3, I don't get a compiler crash, but I haven't been able to discover a way to get a pointer to a C "extern const struct thingtype athing".

Calling withUnsafePointer(to:) gives me a pointer to a stack-allocated copy of the struct (which is not what I need); using an & gives me the spurious warning about passing an immutable value as an inout parameter.

My best workaround has been to introduce a whole lot of static inline C functions into a header which Swift can see — one for each struct variable, returning a pointer to that variable — which does compile to good code in release mode, but is ugly.

cc @Andrew_Trick

@Andrew_Trick Please help.

Having exactly same problem.

Output:

$ swift build
/home/ailion/swift-hello/Sources/swift-hello/main.swift:10:53: error: cannot pass immutable value as inout argument: 'wl_compositor_interface' is a 'let' constant
        compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1)
$ swift build
swift: /home/build-user/llvm-project/clang/lib/CodeGen/CGRecordLayout.h:186: unsigned int clang::CodeGen::CGRecordLayout::getLLVMFieldNo(const clang::FieldDecl *) const: Assertion `FieldInfo.count(FD) && "Invalid field for record!"' failed.
Stack dump:
0.      Program arguments: /usr/lib/swift/bin/swift -frontend -c -primary-file /home/ailion/swift-hello/Sources/swift-hello/main.swift -emit-module-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main~partial.swiftmodule -emit-module-doc-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main~partial.swiftdoc -emit-module-source-info-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main~partial.swiftsourceinfo -emit-dependencies-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main.d -emit-reference-dependencies-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main.swiftdeps -target x86_64-unknown-linux-gnu -disable-objc-interop -I /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug -color-diagnostics -enable-testing -g -module-cache-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/ModuleCache -swift-version 5 -Onone -D SWIFT_PACKAGE -D DEBUG -enable-anonymous-context-mangled-names -Xcc -fmodule-map-file=/home/ailion/swift-hello/Sources/CWaylandEGL/module.modulemap -Xcc -fmodule-map-file=/home/ailion/swift-hello/Sources/CWaylandClient/module.modulemap -module-name swift_hello -o /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main.swift.o -index-store-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/index/store -index-system-modules 
1.      Swift version 5.2.4 (swift-5.2.4-RELEASE)
2.      /usr/include/wayland-client-protocol.h:1095:1: Generating code for declaration 'wl_registry_bind'
/usr/lib/swift/bin/swift[0x4b9a6d4]
/usr/lib/swift/bin/swift[0x4b9838e]
/usr/lib/swift/bin/swift[0x4b9a996]
/usr/lib/libpthread.so.0(+0x14960)[0x7f932f8b9960]
/usr/lib/libc.so.6(gsignal+0x145)[0x7f932f2fd355]
/usr/lib/libc.so.6(abort+0x127)[0x7f932f2e6853]
/usr/lib/libc.so.6(+0x25727)[0x7f932f2e6727]
/usr/lib/libc.so.6(+0x34936)[0x7f932f2f5936]
/usr/lib/swift/bin/swift[0x2524d4f]
/usr/lib/swift/bin/swift[0x2525393]
/usr/lib/swift/bin/swift[0x250e942]
/usr/lib/swift/bin/swift[0x251a2bc]
/usr/lib/swift/bin/swift[0x250adc1]
/usr/lib/swift/bin/swift[0x2513f15]
/usr/lib/swift/bin/swift[0x2567ac9]
/usr/lib/swift/bin/swift[0x256f0bc]
/usr/lib/swift/bin/swift[0x255a435]
/usr/lib/swift/bin/swift[0x250a780]
/usr/lib/swift/bin/swift[0x250ba75]
/usr/lib/swift/bin/swift[0x24a7d1a]
/usr/lib/swift/bin/swift[0x24a6641]
/usr/lib/swift/bin/swift[0x24878bb]
/usr/lib/swift/bin/swift[0x2527671]
/usr/lib/swift/bin/swift[0x2526333]
/usr/lib/swift/bin/swift[0x256adb9]
/usr/lib/swift/bin/swift[0x2563702]
/usr/lib/swift/bin/swift[0x255a435]
/usr/lib/swift/bin/swift[0x250a780]
/usr/lib/swift/bin/swift[0x250a729]
/usr/lib/swift/bin/swift[0x25f7d7c]
/usr/lib/swift/bin/swift[0x26027c0]
/usr/lib/swift/bin/swift[0x2647328]
/usr/lib/swift/bin/swift[0x26641a3]
/usr/lib/swift/bin/swift[0x265c7f4]
/usr/lib/swift/bin/swift[0x2651a41]
/usr/lib/swift/bin/swift[0x2650979]
/usr/lib/swift/bin/swift[0x2420614]
/usr/lib/swift/bin/swift[0x58d9b8]
/usr/lib/swift/bin/swift[0x579554]
/usr/lib/swift/bin/swift[0x579861]
/usr/lib/swift/bin/swift[0x504c5e]
/usr/lib/swift/bin/swift[0x4fa439]
/usr/lib/swift/bin/swift[0x4f7705]
/usr/lib/swift/bin/swift[0x488066]
/usr/lib/libc.so.6(__libc_start_main+0xf2)[0x7f932f2e8002]
/usr/lib/swift/bin/swift[0x487c9e]
$ uname -r
5.6.16-1-MANJARO
$ swift -version
Swift version 5.2.4 (swift-5.2.4-RELEASE)
Target: x86_64-unknown-linux-gnu

Example code:

import CWaylandClient
import CWaylandEGL

var compositor: UnsafeMutableRawPointer? =  nil

func global_registry_handler(data: UnsafeMutableRawPointer?, registry: OpaquePointer?, id: UInt32, interface: UnsafePointer<Int8>?, version: UInt32) {
    print("Got a registry event")
    let interfaceStr = String(cString: interface!)
    if interfaceStr == "wl_compositor" {
        var w = wl_compositor_interface
        compositor = wl_registry_bind(registry, id, &w, 1)
    }
}

func global_registry_remove(data: UnsafeMutableRawPointer?, registry: OpaquePointer?, id: UInt32) {
    print("Got a registry remove event")
}

var display = wl_display_connect(nil);
if display == nil {
    print("Connect to display failed!")
    exit(1)
}
print("Connect to display succeeded!")

var registry = wl_display_get_registry(display)

var registry_listener = wl_registry_listener(global: global_registry_handler, global_remove: global_registry_remove)

wl_registry_add_listener(registry, &registry_listener, nil)

wl_display_dispatch(display)
wl_display_roundtrip(display)

wl_display_disconnect(display)
print("Hello, world!")

I see two issues in this thread.

  1. We still have an open bug on the compiler crash, although I haven't reproduced it:

Clang crash when calling external C library function with UnsafePointer to C struct as argument (was Can't pass an imported C extern const struct to a C function which expects a struct pointer)

rdar://problem/19103795

Reproducer: GitHub - tokyovigilante/WaylandTest

"root cause seems to be that both XDGShell and WaylandWSIWindow (via the CWaylandClient system module) include the wayland-client.h header"

So apparently at least there's a workaround for the compiler crash.

  1. Passing a pointer to an extern C declaration of a global:

This works fine for me though:

C code:

struct MyStruct {
  int field;
};

extern const struct MyStruct myGlobal;
void takePointer(const struct MyStruct *);

Swift code

withUnsafePointer(to: myGlobal) {
  takePointer($0)
}

So the equivalent workaround posted by @lukasa should work for you:

1 Like

Is it guaranteed that withUnsafePointer(to:) on an immutable C global won't make a temporary copy? (I think that would be a reasonable guarantee to provide, but I don't think there's anything in the compiler or stdlib that actually makes that happen today in -Onone.)

For comparison, we have promised that &global will not make a temporary copy for a mutable, known-stored global (C or Swift).

@jordan, we're generating a copy :(

I filed SR-13180: withUnsafePointer should guarantee pointer stability for globals and class properties
https://bugs.swift.org/browse/SR-13180

@andy1247008998 If you still get a runtime crash with the above workaround, then the C implementation must be sensitive to the pointer address of the global wl_compositor_interface. In that case, my only suggestion is to create a C shim function or dummy global that returns to address of the global for use in Swift as an UnsafePointer.

4 Likes

Thanks a lot.

window._compositor = withUnsafePointer(to: wl_compositor_interface) {
    wl_registry_bind(registry, name, $0, 1)
}

This workaround crashes too.

I will use C for my project, then call it from Swift without needing to deal with the global variables as well as many different types of pointers which are really a reasonable mess in Swift. Maybe I should lower my expectation of interoperability between Swift and C.

If you have a C target in your project, you can probably just do something like:

.h
extern const struct wl_interface *wl_compositor_interface_ptr;
.c
const struct wl_interface *wl_compositor_interface_ptr = &wl_compositor_interface;

I pushed my project on GitHub.

$ swift build
swift: /home/build-user/llvm-project/clang/lib/CodeGen/CGRecordLayout.h:186: unsigned int clang::CodeGen::CGRecordLayout::getLLVMFieldNo(const clang::FieldDecl *) const: Assertion `FieldInfo.count(FD) && "Invalid field for record!"' failed.
Stack dump:
0.      Program arguments: /usr/lib/swift/bin/swift -frontend -c -primary-file /home/ailion/swift-hello/Sources/swift-hello/main.swift -emit-module-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main~partial.swiftmodule -emit-module-doc-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main~partial.swiftdoc -emit-module-source-info-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main~partial.swiftsourceinfo -emit-dependencies-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main.d -emit-reference-dependencies-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main.swiftdeps -target x86_64-unknown-linux-gnu -disable-objc-interop -I /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug -color-diagnostics -enable-testing -g -module-cache-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/ModuleCache -swift-version 5 -Onone -D SWIFT_PACKAGE -D DEBUG -enable-anonymous-context-mangled-names -Xcc -fmodule-map-file=/home/ailion/swift-hello/Sources/CWaylandEGL/module.modulemap -Xcc -fmodule-map-file=/home/ailion/swift-hello/Sources/CWaylandClient/module.modulemap -module-name swift_hello -o /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/swift_hello.build/main.swift.o -index-store-path /home/ailion/swift-hello/.build/x86_64-unknown-linux-gnu/debug/index/store -index-system-modules 
1.      Swift version 5.2.4 (swift-5.2.4-RELEASE)
2.      /usr/include/wayland-client-protocol.h:1095:1: Generating code for declaration 'wl_registry_bind'
/usr/lib/swift/bin/swift[0x4b9a6d4]
/usr/lib/swift/bin/swift[0x4b9838e]
/usr/lib/swift/bin/swift[0x4b9a996]
/usr/lib/libpthread.so.0(+0x14960)[0x7f1928005960]
/usr/lib/libc.so.6(gsignal+0x145)[0x7f1927a49355]
/usr/lib/libc.so.6(abort+0x127)[0x7f1927a32853]
/usr/lib/libc.so.6(+0x25727)[0x7f1927a32727]
/usr/lib/libc.so.6(+0x34936)[0x7f1927a41936]
/usr/lib/swift/bin/swift[0x2524d4f]
/usr/lib/swift/bin/swift[0x2525393]
/usr/lib/swift/bin/swift[0x250e942]
/usr/lib/swift/bin/swift[0x251a2bc]
/usr/lib/swift/bin/swift[0x250adc1]
/usr/lib/swift/bin/swift[0x2513f15]
/usr/lib/swift/bin/swift[0x2567ac9]
/usr/lib/swift/bin/swift[0x256f0bc]
/usr/lib/swift/bin/swift[0x255a435]
/usr/lib/swift/bin/swift[0x250a780]
/usr/lib/swift/bin/swift[0x250ba75]
/usr/lib/swift/bin/swift[0x24a7d1a]
/usr/lib/swift/bin/swift[0x24a6641]
/usr/lib/swift/bin/swift[0x24878bb]
/usr/lib/swift/bin/swift[0x2527671]
/usr/lib/swift/bin/swift[0x2526333]
/usr/lib/swift/bin/swift[0x256adb9]
/usr/lib/swift/bin/swift[0x2563702]
/usr/lib/swift/bin/swift[0x255a435]
/usr/lib/swift/bin/swift[0x250a780]
/usr/lib/swift/bin/swift[0x250a729]
/usr/lib/swift/bin/swift[0x25f7d7c]
/usr/lib/swift/bin/swift[0x26027c0]
/usr/lib/swift/bin/swift[0x2647328]
/usr/lib/swift/bin/swift[0x26641a3]
/usr/lib/swift/bin/swift[0x265c7f4]
/usr/lib/swift/bin/swift[0x2651a41]
/usr/lib/swift/bin/swift[0x2650979]
/usr/lib/swift/bin/swift[0x2420614]
/usr/lib/swift/bin/swift[0x58d9b8]
/usr/lib/swift/bin/swift[0x579554]
/usr/lib/swift/bin/swift[0x579861]
/usr/lib/swift/bin/swift[0x504c5e]
/usr/lib/swift/bin/swift[0x4fa439]
/usr/lib/swift/bin/swift[0x4f7705]
/usr/lib/swift/bin/swift[0x488066]
/usr/lib/libc.so.6(__libc_start_main+0xf2)[0x7f1927a34002]
/usr/lib/swift/bin/swift[0x487c9e]

Crashes on Manjaro and Ubuntu 18.04 LTS (fresh installed).

You're hitting the known compiler bug. You can add your project link to this bug report

I don't understand the bug, but there's a workaround in the bug report that might work for you. I think that compiler crash was being confused with a feature request. I removed the incorrect radar link. Adding your information should make it clear that it's still an active bug.