How to use C api with fixed size C's Array?

Hello. I have a C module with one C structure. Here is:

#import <simd/simd.h>

#define STENCIL 8
#define STENCIL3D STENCIL * 3
struct Stencil3x3 {
    simd_float3 offsets[STENCIL3D];
};

Also I have a Swift module which uses that structure

import CoreStructures

struct CArrayQuestion {
    static var count2d = 8
    static var count3d = count2d * 3
    func makeStencil() {
        let array = Array(repeating: SIMD3<Float>.random(in: 0...1), count: CArrayQuestion.count3d)
        let cStencil = Stencil3x3(offsets: <#T##(simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3, simd_float3)#>)
    }
    func run() {
        makeStencil()
    }
}

As you can see, to initialize that structure I need somehow convert my array of type [SIMD3] to a tuple of type (SIMD3, SIMD3, SIMD3 ... size). My question is: is there any best practices to use such api? Also I attach a package with this example

Thanks for any advices.

You can use unsafe pointer API to initialize your C structure with an array of a fixed size.

Here is a simplified example. It is very unsafe. However, it will work for most C structs with a simple memory layout and no indirection.

C declaration

struct CFixedArray8 {
    float storage[8];
};

Swift extension

extension CFixedArray8 {
    init?(array: [Float]) {
        let arrayByteSize = array.count * MemoryLayout<Float>.stride
        let cArrayByteSize = MemoryLayout<Self>.size
        guard arrayByteSize == cArrayByteSize else { return nil }
        guard let cArray = array.withUnsafeBytes({ ptr -> CFixedArray8? in
            guard let baseAddress = ptr.baseAddress else { return nil }
            let cPtr = baseAddress.assumingMemoryBound(to: Self.self)
            return cPtr.pointee
        }) else { return nil }
        self = cArray
    }
}

Usage example

let array = (0..<8).map { _ in Float.random(in: 0...1) }
let cArray = CFixedArray8(array: array)!
print(cArray)
1 Like

You may want to check out these videos on unsafe memory operations:

The second video (Safely manage pointers in Swift) discusses creating a pointer to a homogeneous tuple’s type around the 19:20 mark, though it may be worth watching both videos to understand pointer safety and avoid unexpected behavior within your program in the future.

1 Like

Thanks. What if the array from the line let array = (0..<8).map { _ in Float.random(in: 0...1) } will end their life and the cArray will keep their life by copy to another function(or even by pass back to a C backend), will cArray be legal still?

I wouldn’t use that code as it isn’t not only unsafe it is correct. You cannot safely escape the pointer out of the withUnsafeBytes block that is a violation of its contract.

I would watch the WWDC videos shared above by @1-877-547-7272 and then I would do this (I am on my phone so I can’t give you the real code at the moment sorry).

Allocate a pointer of Float (or whatever type is appropriate) with capacity to the arity of your tuple. Then use a generic function to infer and do the cast for you. This generic function will take/capture the pointer and will take the function you want to call as an argument. Because the signature of the function will have your generic tuple type in it, the compiler will know what it is. You then can bind the pointer to that type and pass it along. If your arity is wrong with what you allocated and you don’t have enough space you will almost certainly crash and if not you will corrupt memory.

My understanding is that returning cPtr.pointee from the closure makes a copy because CFixedArray8 is a value type. Therefore, the initial array lifetime doesn't affect cArray in any way. You can verify that with this:

var array = [Float](repeating: 0, count: 8)
let cArray = CFixedArray8(array: array)!
array[0] = -2
print(cArray) // CFixedArray8(storage: (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0))

@ksemianov’s code is actually incorrect. It does correctly avoid escaping the pointer from the closure and it may work in some cases, however, it is considered a programmer error in Swift to use UnsafeRawPointer.assumingMemoryBound(to:) when the memory at the pointer’s location is not actually bound to that type. For more information, watch the videos I linked in my previous post.

The way I would write your code is:

import CoreStructures

struct CArrayQuestion {
    func makeStencil() {
        let array = Array(repeating: SIMD3<Float>.random(in: 0...1), count: Int(STENCIL3D))
        // FYI the above code will not fill this array with random data. Instead, it will generate one
        // random SIMD<Float> instance and repeat that instance for the rest of the array.
        let cStencil = array.withUnsafeBytes {
            return $0.load(as: Stencil3x3.self) // This is allowed because Stencil3x3 only has one
                                                // stored property (a tuple of 24 SIMD3<Float>
                                                // instances), so its layout is the same as its
                                                // stored property.
        }
    }
    func run() {
        makeStencil()
    }
}

I have added comments to the code to clarify what it’s doing.

You may not even need to create an array in the first place. This version of the makeStencil() function will work the same way as it does in the above code, but it will be faster due to it requiring fewer allocations and it will be safer because it doesn’t require any unsafe code:

func makeStencil() {
    let val = SIMD3<Float>.random(in: 0...1)
    let cStencil = Stencil3x3(offsets: (
        val, val, val, val, val, val, val, val,
        val, val, val, val, val, val, val, val,
        val, val, val, val, val, val, val, val))
}

I hope this helps!

1 Like

@1-877-547-7272’s approach work well for the specific case you’ve described but I thought I’d follow up with something more general…

My favourite technique for dealing with fixed-sized arrays is the primitives in SR-11156 Add UnsafePointer<Aggregate>[KeyPath] API that applies the field’s offset. These allow you to safely get the address of the first element of the tuple and you can index from there. For example:

extension Stencil3x3 {
    subscript(i: Int) -> simd_float3 {
        get {
            let count = MemoryLayout.size(ofValue: self.offsets) / MemoryLayout.size(ofValue: self.offsets.0)
            return withUnsafePointer(to: self) { p -> simd_float3 in
                // `p` is a pointer to the entire C struct, so we know that all
                // the items within `p` are laid out as if they are in C. Thus
                // we can get the address of the first element of the
                // fixed-sized array tuple and know that all the remaining
                // elements can be accessed by pointer indexing.
                let start = p[\.offsets.0]
                let buf = UnsafeBufferPointer(start: start, count: count)
                return buf[i]
            }
        }
        set {
            // … left as an exercise for the reader … (-:
            fatalError()
        }
    }
}

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

1 Like

I totally don't understand how it works, but it looks beauty and goes to production.

1 Like

Ah sorry I missed the pointee return on my phone. I am sorry. If that is the case then it is much better.

Terms of Service

Privacy Policy

Cookie Policy