OpaquePointer errors with complex functions in Accelerate

I'm trying to use complex types for functions in Accelerate such as cblas_cgemv. See example below. I set the preprocessor macros in Xcode to ACCELERATE_NEW_LAPACK=1 and ACCELERATE_LAPACK_ILP64=1 for the new BLAS and LAPACK headers. This fails to build because Xcode expects OpaquePointer types. I have no idea how to work with opaque types in Swift so any suggestions would be welcome.

import Accelerate

let a = [DSPComplex(real: 1.0, imag: 2.0), DSPComplex(real: 3.0, imag: 4.0),
         DSPComplex(real: 5.0, imag: 6.0), DSPComplex(real: 7.0, imag: 8.0)]

let x = [DSPComplex(real: 1.0, imag: 2.0), DSPComplex(real: 3.0, imag: 4.0)]

let m = 2       // rows in matrix A
let n = 2       // columns in matrix A
let alpha = 1   // scale factor for αAX
let beta = 1    // scale factor for βY

var y = [DSPComplex](repeating: DSPComplex(), count: m)

// Calculate Y ← αAX + βY
cblas_cgemv(CblasRowMajor, CblasNoTrans, m, n, alpha, a, m, x, 1, beta, y, 1)

print(y)

Treat the following as wrong code (I would be very surprised it works!) but it it is at least "compilable" and should get you started to see where to be heading to:

// WRONG CODE BELOW!
let a = [DSPComplex(real: 1.0, imag: 2.0), DSPComplex(real: 3.0, imag: 4.0), DSPComplex(real: 5.0, imag: 6.0), DSPComplex(real: 7.0, imag: 8.0)]
let x = [DSPComplex(real: 1.0, imag: 2.0), DSPComplex(real: 3.0, imag: 4.0)]
let m = 2
let n = 2
var y = [DSPComplex](repeating: DSPComplex(), count: m)
let alpha = [DSPComplex(real: 1.0, imag: 2.0)]
let beta = [DSPComplex(real: 1.0, imag: 2.0)]

a.withUnsafeBufferPointer { ap in
    x.withUnsafeBytes { xp in
        alpha.withUnsafeBufferPointer { alphaP in
            beta.withUnsafeBytes { betaP in
                // try below with withUnsafeBufferPointer and withUnsafeBytes:
                y.withUnsafeMutableBufferPointer { yp in
                    cblas_cgemv(
                        CblasRowMajor,
                        CblasNoTrans,
                        m,
                        n,
                        .init(alphaP.baseAddress!),
                        .init(ap.baseAddress!),
                        m,
                        .init(xp.baseAddress!),
                        1,
                        .init(betaP.baseAddress!),
                        .init(yp.baseAddress!),
                        1
                    )
                }
            }
        }
    }
}

Interestingly compiler didn't force me to use the mutable point version for the output parameter "y"... would be interesting to test it with immutable pointer and see what would happen in that case.

Another point: the code compiles with both "withUnsafeBufferPointer" and "withUnsafeBytes", I don't know which one is better in this case, so I arbitrarily chose them for different variables (again, to see what'd happen when you actually run it).

If I don't use the new LAPACK headers, then I can run the following code:

import Accelerate

let a = [DSPComplex(real: 1.0, imag: 2.0), DSPComplex(real: 3.0, imag: 4.0),
         DSPComplex(real: 5.0, imag: 6.0), DSPComplex(real: 7.0, imag: 8.0)]

let x = [DSPComplex(real: 1.0, imag: 2.0), DSPComplex(real: 3.0, imag: 4.0)]

let m: Int32 = 2
let n: Int32 = 2
let alpha = [DSPComplex(real: 1.0, imag: 0.0)]
let beta = [DSPComplex(real: 1.0, imag: 0.0)]

var y = [DSPComplex](repeating: DSPComplex(), count: Int(m))

cblas_cgemv(CblasRowMajor, CblasNoTrans, m, n, alpha, a, m, x, 1, beta, &y, 1)

print(y)

This prints the following:

[__C.DSPComplex(real: -10.0, imag: 28.0), __C.DSPComplex(real: -18.0, imag: 68.0)]

This provides the desired result but Xcode gives deprecation warnings since I'm not using the new headers.

Right, and I read your intention to not have this warning...

This would work:

alpha.withUnsafeBufferPointer { alphap in
    a.withUnsafeBytes { ap in
        x.withUnsafeBufferPointer { xp in
            beta.withUnsafeBytes { betap in
                y.withUnsafeBytes { yp in // Haha, immutable!!!
                    cblas_cgemv(
                        CblasRowMajor,
                        CblasNoTrans,
                        m,
                        n,
                        .init(alphap.baseAddress!),
                        .init(ap.baseAddress!),
                        m,
                        .init(xp.baseAddress!),
                        1,
                        .init(betap.baseAddress!),
                        .init(yp.baseAddress!),
                        1
                    )
                }
            }
        }
    }
}

or, if you prefer:

func cblas_cgemv_wrapper(order: CBLAS_ORDER, transpose: CBLAS_TRANSPOSE, m: Int, n: Int, alpha: UnsafeRawPointer, a: UnsafeRawPointer, lda: Int, x: UnsafeRawPointer, incX: Int, beta: UnsafeRawPointer, y: UnsafeRawPointer, incY: Int) {
    cblas_cgemv(
        order,
        transpose,
        m,
        n,
        .init(alpha),
        .init(a),
        lda,
        .init(x),
        incX,
        .init(beta),
        .init(y),
        incY
    )
}

Usage:

cblas_cgemv_wrapper(
    order: CblasRowMajor,
    transpose: CblasNoTrans,
    m: m,
    n: n,
    alpha: alpha,
    a: a,
    lda: m,
    x: x,
    incX: 1,
    beta: beta,
    y: y,
    incY: 1
)

to have a more convenient wrapper.

Answering myself above:

  • withUnsafeBufferPointer & withUnsafeBytes give identical results in this code.
  • withUnsafeBufferPointer (without Mutable) or withUnsafeBytes (without Mutable) wrapped in a OpaquePointer could indeed be used for the output parameter that's supposed to be mutable! Looks like a small hole in the type safety system.

Why is all this unsafe pointer stuff needed for this function? I can use other BLAS functions like cblas_dgemm without having to directly work with pointers and buffers.

These are how the API's in question are getting presented in Swift:

// without `ACCELERATE_NEW_LAPACK=1` and `ACCELERATE_LAPACK_ILP64=1`
cblas_cgemv(CBLAS_ORDER, CBLAS_TRANSPOSE, Int32, Int32, 
    UnsafeRawPointer!, UnsafeRawPointer!, Int32, 
    UnsafeRawPointer!, Int32, UnsafeRawPointer!, 
    UnsafeMutableRawPointer!, Int32)

cblas_dgemm(CBLAS_ORDER, CBLAS_TRANSPOSE, CBLAS_TRANSPOSE, 
    Int32, Int32, Int32, Double, 
    UnsafePointer<Double>!, Int32, UnsafePointer<Double>!, Int32, Double, 
    UnsafeMutablePointer<Double>!, Int32)

// with `ACCELERATE_NEW_LAPACK=1` and `ACCELERATE_LAPACK_ILP64=1`
cblas_cgemv(CBLAS_ORDER, CBLAS_TRANSPOSE, __LAPACK_int, __LAPACK_int, 
    OpaquePointer, OpaquePointer?, __LAPACK_int, 
    OpaquePointer?, __LAPACK_int, OpaquePointer, 
    OpaquePointer?, __LAPACK_int)

cblas_dgemm(CBLAS_ORDER, CBLAS_TRANSPOSE, CBLAS_TRANSPOSE, 
    __LAPACK_int, __LAPACK_int, __LAPACK_int, Double, 
    UnsafePointer<Double>?, __LAPACK_int, UnsafePointer<Double>?,  __LAPACK_int, Double, 
    UnsafeMutablePointer<Double>?, __LAPACK_int)

As you can see there's always "unsafe" stuff involved when importing C's API's that work with pointers... Sometime you don't see this unsafely at the use sites (when Swift could help you implicitly converting from &x to unsafePointer under the hood), sometimes you see it when Swift can't help doing that magic conversion.

If you use the "func cblas_cgemv_wrapper" of my previous post and put it out of sight in some library of yours – you'd use it without explicit unsafe stuff at the call sites, similar to how you don't see it when using cblas_dgemm now.


Why the library uses Unsafe[Mutable][Raw]Pointer with one settings and OpaquePointer with another? I think this has to do with C types involved:

struct S; // no body
typedef struct S T;
// or is this #define T struct S

double * x // converted to unsafe pointer
T * x;     // converted to opaque pointer

I created the example shown below based on your wrapper suggestion. I like this approach since it gets rid of the nested closures. Can you comment on the following so I have a better understanding of what's going on with this code:

  • I defined my own type named Complex instead of using DSPComplex and it still works fine. Is there any reason why I should use DSPComplex instead of using my own type?
  • The wrapper function defines the types used for the inputs. Those types are compatible with the original cblas_cgemv function inputs. How does this work? Is Swift automatically converting the types based on the wrapper definition?
  • I used var y in the example but it also works with let y. The original cblas_cgemv function defines Y as mutable but why does it work if it's also immutable? Is this what you were referring to when you said "Looks like a small hole in the type safety system" in the previous message?
import Accelerate

struct Complex {
    let real: Float
    let imag: Float
}

func cblas_cgemv_wrapper(
    _ order: CBLAS_ORDER,
    _ transpose: CBLAS_TRANSPOSE,
    _ m: Int,
    _ n: Int,
    _ alpha: UnsafeRawPointer,
    _ a: UnsafeRawPointer,
    _ lda: Int,
    _ x: UnsafeRawPointer,
    _ incX: Int,
    _ beta: UnsafeRawPointer,
    _ y: UnsafeRawPointer,
    _ incY: Int
) {
    cblas_cgemv(order, transpose, m, n, .init(alpha), .init(a), lda, .init(x), incX, .init(beta), .init(y), incY)
}

let a = [Complex(real: -1.0, imag: 2.0), Complex(real: 3.0, imag: 4.0),
         Complex(real: -5.0, imag: 6.0), Complex(real: 7.0, imag: -8.0)]

let x = [Complex(real: -1.0, imag: 2.0), Complex(real: 3.0, imag: 4.0)]

let m = 2                                    // rows in matrix A
let n = 2                                    // columns in matrix A
let alpha = [Complex(real: 1.0, imag: 0.0)]  // scale factor for αAX
let beta = [Complex(real: 1.0, imag: 0.0)]   // scale factor for βY

var y = [Complex](repeating: Complex(real: 0.0, imag: 0.0), count: m)

cblas_cgemv_wrapper(CblasRowMajor, CblasNoTrans, m, n, alpha, a, m, x, 1, beta, y, 1)

print(y)

This prints:

[complex_basic.Complex(real: -10.0, imag: 20.0), complex_basic.Complex(real: 46.0, imag: -12.0)]

I would use DSPComplex if you need to interoperate with complex vDSP operations; otherwise what you have is fine (or use the Numerics Complex type if you want to have arithmetic and math functions available, it's layout compatible as well).

I plan to add other features to the Complex type I created in the example. I just wanted to see if using my struct would be compatible with the cblas_cgemv function. I haven't looked at the other complex functions in Accelerate but would they be compatible with a user defined complex type or do they strictly require DSPComplex, DSPSplitComplex, etc.?

If I didn't like the aesthetics of the name "DSPComplex" I'd use a typealias instead of my own type:

typealias Complex = DSPComplex

possibly along with an extra convenience initialiser:

extension Complex {
    init(_ real: Float, _ imaginary: Float) {
        self.init(real: real, imag: imaginary)
    }
}

to get to a more succinct usage:

let a = [Complex(1, 2), Complex(3, 4), Complex(5, 6), Complex(7, 8)]

IIRC unless you mark the struct frozen or smth to that account – future Swift might change the fields layout (add / remove padding, reorder fields, etc), that's why the safest way to work with C API (which cblas_cgemv is) is to define a struct in C. Another thing that's "layout stable" is a tuple, in this case a tuple of two floats: typealias T = (Float, Float)

Swift understands what you mean when you pass an array to a UnsafeRawPointer parameter – this solves you from having the pyramid of doom in this case. And inside you are converting UnsafeRawPointer back to OpaquePointer to satisfy the new version of the API.

Yes, that's exactly what I meant! It only works here because of passing it through the OpaquePointer... Ideally we'd need to have the notion of OpaqueMutablePointer and distinguish between the two, but it's probably too late at this point – the number of such API's is not great and getting smaller as time goes on.

You can't add stored variables to that struct (if you do the two layouts won't match), so it's only methods, instance and static you could add – this you could do equally well in an extension of the original type (like the ".init" I created above).

I would suggest using the Complex type from Numerics. It is quite full-featured, and is layout-compatible with C and C++ standard library complex types, BLAS and LAPACK's complex types, Fortran complex types, and DSPComplex (so you will need to explicitly cast a pointer at boundaries between languages, but that's it).

I'm aware of the Swift Numerics package and have been following its development since it was announced. I have many questions about it but I'll ask them in a separate post so this one doesn't get off topic. But yes, I will consider using the complex type from the Numerics package.

Some of them will required original types, e.g. this:

public func vDSP_ctoz(_ __C: UnsafePointer<DSPComplex>, _ __IC: vDSP_Stride, _ __Z: UnsafePointer<DSPSplitComplex>, _ __IZ: vDSP_Stride, _ __N: vDSP_Length)

If you attempt to pass your Complex instead of DSPComplex you'd get a compilation error of the form

which compilation error could be resolved by some further unsafe bit cast tricks if you are inclined so.

A simpler version of this safety hole without involving Accelerate or OpaquePointer:

let array: [UInt8] = [0, 1, 2, 3]

func foo(_ p: UnsafePointer<UInt8>) {
    let p = UnsafeMutablePointer(mutating: p)
    p[2] = 42
}

print(array[2]) // 2
foo(array)
print(array[2]) // 42

and if we didn't have "UnsafeMutablePointer(mutating:)" we could still do it via unsafeBitCast.

I thought let is for immutable (constant) variables and var is for mutable variables. But apparently you can still mutate a variable defined with let by using an unsafe pointer. Is that what you are demonstrating here?

Yes! This makes unsafe pointers even more unsafe than they already are.

Fortunately this trick could be done with "let array" variables only but not with others, e.g. not with "let int" variables.


Possible (albeit source breaking) mitigation – prohibit passing "foo(array)", always require "foo(&array)" – now the two work interchangeably. By requiring & Swift would restrict variables to be "var" as you can't take & of a let variable.

Not any more, for better or worse. e.g. with the new Atomic type in the standard library, you have to declare all its uses as let, even though they are always mutable.

In that example it's kind of like a C const pointer to non-const value, as opposed to a non-const pointer to a const value, but that's not always the case. Swift doesn't clearly separate those two orthogonal aspects of mutability, which complicates things.

So let vs var is now sort of more about a distinction of unique ownership or references, or somesuch. It's much more complicated conceptually; I haven't gotten my brain around it yet. :confused:

To be precise: it's a distinction of whether or not exclusive access has to be checked dynamically. The "Law of Exclusivity" disallows overlapping read/write and write/write accesses to the same variable; var bindings incur a dynamic check at runtime to prevent overlapping access when the compiler cannot prove statically that the check is not needed.

let bindings do not need this check, because statically they can never have overlapping read/write or write/write accesses (constant lets because they cannot have writes, atomic types because all accesses to them are semantically instantaneous, hence cannot overlap with each other).

2 Likes