Why can’t i load a function from a raw pointer?

I have a static array of function objects, of the type

struct Variant { ... }

struct T 
{
    typealias Delegate = ...
    typealias Method = (Self) -> (Self.Delegate, [Variant?]) -> Variant?

    static
    let methods:[Method]
}

i am sending UnsafeMutableRawPointers to the functions to a C API. the C API does not do anything with the raw pointers, it just passes the raw pointers back to swift at a later point.

T.methods.withUnsafeBufferPointer 
{
    guard let base:UnsafePointer<T.Method> = $0.baseAddress 
    else 
    {
        return 
    }
    
    for i:Int in 0 ..< T.methods.count 
    {
        C_API.register(method: 
            UnsafeMutableRawPointer.init(mutating: base + i)) 
    }  
}

then, from swift, i load the function objects back from the raw pointers:

let method:T.Method = pointer.load(as: T.Method.self)

unfortunately, the function object no longer seems to be valid, and crashes when i try to call it.

if instead, i encode an Int index in the bitPattern of the raw pointer, and then retrieve the function object from the T.methods array through a normal subscript, it works as expected. what am i missing here?

My guess is your C function copies one word from the pointer you pass it, and gives you back that one word later on. That's not enough, because normal Swift function references are “thick”. They consist of two words: a pointer to the first instruction of the function, and a context, which (in the case of a closure) points to the all of the bindings captured by the function. The context is always a value that can be retained and released (though it may be a special value that retain and release know to ignore). When you say pointer.load(as: T.Method.self), if you haven't ensured that both words (instruction pointer and context) got copied to the memory starting at pointer, you'll probably have garbage as the context, leading to a crash.

7 Likes

Right. To broaden that point a bit: Swift's function types are not the same ABI as either C's function pointer types (void (*)(int)) or the block pointer types you see on Apple platforms (void (^)(int)). They differ in size, they differ in calling convention, and they differ in how they're expected to be used. It's possible to write C code that matches those conventions (as long as you're using a version of Clang that supports __attribute__((swiftcall))), but you'd need to read up about the Swift ABI to figure out how to do it.

Fortunately, Swift also allows you to work with C function pointers directly using @convention(c) function types; for example, the Swift type @convention(c) (Int) -> Float has the same ABI as the C function pointer type float (*)(size_t). The best way to make sure you're matching a particular C function pointer type in ABI is to work with an imported typedef of it instead of trying to duplicate it in a Swift declaration, though.

11 Likes

that’s not really the problem i think i’m running into. the entire width of the function object is being stored in the array, the raw pointer is being used to dereference that function object, not store it. the array

static 
let methods:[Method]

stores complete function objects, which work when accessed through an array subscript. but for some reason, they do not work when they are loaded from a pointer into the static array’s storage. are static arrays guaranteed to not be deinitialized until the program exits?

Oh, I see.

Well, you aren’t allowed to persist pointers outside of withUnsafeBufferPointer like that, but that’s more an abstract violation of the model than something that I’d specifically expect to misbehave here. Nothing stands out.

3 Likes