Skip to content

Commit 898c58a

Browse files
committed
Add FFI tutorial and examples
1 parent 4fea565 commit 898c58a

File tree

6 files changed

+525
-10
lines changed

6 files changed

+525
-10
lines changed

README.md

Lines changed: 323 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -403,11 +403,330 @@ Running the app,
403403

404404
![5 squared](tutorial/squared.png)
405405

406-
## Strings and ByteString FFI
406+
## Passing Complex Data Types
407407

408-
TODO
408+
### Bytes
409409

410-
## Function and Closure FFI
410+
#### `[UInt8]` to `ByteString`
411411

412-
TODO
412+
Call `withUnsafeBufferPointer` on a Swift `Array` to get
413+
an `UnsafeBufferPointer`, and then read its `.baseAddress`
414+
property to get an `UnsafePointer` pass into the exported
415+
Haskell function. The corresponding mutable variants are
416+
`withUnsafeMutableBufferPointer`, `UnsafeMutableBufferPointer`,
417+
and `UnsafeMutablePointer`.
413418

419+
The generated Haskell headers use a single pointer type for all
420+
pointers, `HsPtr` (`void *`), which is mutable (not `const`). If
421+
you know that a function does not mutate through a pointer, you
422+
can use the `HsPtr(mutating:)` constructor to cast a non-mutable
423+
pointer to a mutable pointer.
424+
425+
```swift
426+
bytes.withUnsafeBufferPointer { bytesBufPtr in
427+
someHaskellFunction(HsPtr(mutating: bytesBufPtr.baseAddress), bytesBufPtr.count)
428+
}
429+
```
430+
431+
If the function mutates the pointer's data, you must use
432+
`withUnsafeMutableBytes`:
433+
434+
```swift
435+
bytes.withUnsafeMutableBufferPointer { bytesBufPtr in
436+
someHaskellFunction(bytesBufPtr.baseAddress, bytesBufPtr.count)
437+
}
438+
```
439+
440+
To bring an array of bytes into a Haskell `ByteString`, use
441+
`Data.ByteString.packCStringLen`:
442+
443+
```haskell
444+
type CString = Ptr CChar
445+
packCStringLen :: (CString, Int) -> IO ByteString
446+
```
447+
448+
For example,
449+
450+
```haskell
451+
import Foreign.C
452+
import Foreign.Ptr
453+
454+
import qualified Data.ByteString as B
455+
import Data.Word
456+
457+
foreign export ccall countBytes :: Word8 -> Ptr CChar -> CSize -> IO CSize
458+
459+
countBytes :: Word8 -> Ptr CChar -> CSize -> IO CSize
460+
countBytes needle haystack haystackLen = do
461+
s <- B.packCStringLen (haystack, fromIntegral haystackLen)
462+
pure (B.foldl (\count b -> count + if b == needle then 1 else 0) 0 s)
463+
```
464+
465+
With a Swift wrapping function of
466+
467+
```swift
468+
func count(byte: UInt8, in bytes: [UInt8]) -> Int {
469+
var r = 0
470+
bytes.withUnsafeBytes { bytesPtr in
471+
r = Int(SwiftHaskell.countBytes(byte, HsPtr(mutating: bytesPtr.baseAddress)))
472+
}
473+
return r
474+
}
475+
```
476+
477+
#### `ByteString` to `[UInt8]`
478+
479+
To pass a `ByteString` to an exported Swift function that
480+
accepts a pointer and a length, use `useAsCStringLen`:
481+
482+
```haskell
483+
import Data.ByteString (ByteString)
484+
import qualified Data.ByteString as B
485+
486+
foreign import ccall "someSwiftFunction" someSwiftFunction :: Ptr CChar -> CSize -> IO ()
487+
488+
passByteString :: ByteString -> IO ()
489+
passByteString s =
490+
B.useAsCStringLen s $ \(p, n) ->
491+
someSwiftFunction p (fromIntegral n)
492+
```
493+
494+
To return the contents of a `ByteString`, call `mallocArray` to
495+
allocate a new array with C's `malloc` allocator and copy the
496+
`ByteString` data into it. The Swift caller is then responsible
497+
for calling `free` on the pointer. Use `Foreign.Storable.poke`
498+
to also return the size by writing into a passed pointer.
499+
500+
```haskell
501+
import Data.ByteString (ByteString)
502+
import qualified Data.ByteString as B
503+
import qualified Data.ByteString.Unsafe as BU
504+
import Foreign.Storable (poke)
505+
506+
mallocCopyByteString :: ByteString -> IO (Ptr CChar, Int)
507+
mallocCopyByteString s =
508+
BU.unsafeUseAsCStringLen s $ \(p, n) -> do
509+
a <- mallocArray n
510+
copyArray a p n
511+
pure (a, n)
512+
513+
foreign export ccall getSequence :: Ptr CSize -> IO (Ptr CChar)
514+
515+
getSequence :: Ptr CSize -> IO (Ptr CChar)
516+
getSequence sizePtr = do
517+
(p, n) <- mallocCopyByteString (B.pack [1..10])
518+
poke sizePtr (fromIntegral n)
519+
pure p
520+
```
521+
522+
The imported `getSequence` function returns a
523+
`UnsafeMutableRawPointer` in Swift. To copy the elements into
524+
a Swift array, first assign a type to the memory using the
525+
`.assumingMemoryBound(to:)` method. Then wrap the pointer
526+
and length in an `UnsafeBufferPointer` and pass it to the
527+
array constructor, which copies the elements into a new array
528+
using the `Collection` protocol that `UnsafeBufferPointer`
529+
implements.
530+
531+
```swift
532+
func getSequence() -> [UInt8] {
533+
var n = 0
534+
let p = SwiftHaskell.getSequence(&n).assumingMemoryBound(to: UInt8.self)
535+
let a = [UInt8](UnsafeBufferPointer(start: p, count: n))
536+
free(p)
537+
return a
538+
}
539+
```
540+
541+
### Functions and Closures
542+
543+
#### Passing Swift Functions to Haskell
544+
545+
C function pointers have a type constructor of `FunPtr` in
546+
Haskell. For example, `FunPtr (CInt -> CSize -> IO ())`
547+
corresponds to `void (*)(int, size_t)`.
548+
549+
To convert `FunPtr`s into callable Haskell functions, use a
550+
`foreign import ccall "dynamic"` declaration to ask the compiler
551+
to generate a conversion function for that function type:
552+
553+
```haskell
554+
foreign export ccall callbackExample :: FunPtr (CInt -> IO ()) -> IO ()
555+
foreign import ccall "dynamic" unwrapCallback :: FunPtr (CInt -> IO ()) -> (CInt -> IO ())
556+
557+
callbackExample :: FunPtr (CInt -> IO ()) -> IO ()
558+
callbackExample f = (unwrapCallback f) 3
559+
```
560+
561+
If there is no context that needs to be captured, Swift
562+
functions can be passed in almost directly. However, like
563+
`HsPtr`, the generated headers only use a single function
564+
pointer type, `HsFunPtr` (`void (*)(void)`), so a little casting
565+
is usually necessary:
566+
567+
```swift
568+
func callbackExample(f: (@convention(c) (CInt) -> Void)) {
569+
let hsf: HsFunPtr = unsafeBitCast(f, to: HsFunPtr.self)
570+
SwiftHaskell.callbackExample(hsf)
571+
}
572+
```
573+
574+
To pass Swift closures with context, we can use the traditional
575+
`void *` context pointer solution. Passing context however
576+
means that we need to keep it alive while the callback is held,
577+
and release it when we're done with it. For that, we can use
578+
`Foreign.ForeignPtr`.
579+
580+
We'll wrap the context with
581+
582+
```haskell
583+
type FinalizerPtr a = FunPtr (Ptr a -> IO ())
584+
newForeignPtr :: FinalizerPtr a -> Ptr a -> IO (ForeignPtr a)
585+
```
586+
587+
and then apply it to the function with
588+
589+
```haskell
590+
withForeignPtr :: ForeignPtr a -> (Ptr a -> IO b) -> IO b
591+
```
592+
593+
Together we have:
594+
595+
```haskell
596+
import Control.Concurrent
597+
import Foreign.C
598+
import Foreign.ForeignPtr
599+
import Foreign.Ptr
600+
601+
foreign export ccall contextCallbackExample
602+
:: Ptr ()
603+
-> FunPtr (Ptr () -> IO ())
604+
-> FunPtr (Ptr () -> CInt -> IO ())
605+
-> IO ()
606+
foreign import ccall "dynamic" unwrapContextCallback
607+
:: FunPtr (Ptr () -> CInt -> IO ())
608+
-> (Ptr () -> CInt -> IO ())
609+
610+
contextCallbackExample
611+
:: Ptr () -- ^ Context pointer
612+
-> FunPtr (Ptr () -> IO ()) -- ^ Context release function
613+
-> FunPtr (Ptr () -> CInt -> IO ()) -- ^ Callback function
614+
-> IO ()
615+
contextCallbackExample ctxp releaseCtx callbackPtr = do
616+
ctxfp <- newForeignPtr releaseCtx ctxp
617+
let callback :: CInt -> IO ()
618+
callback result = withForeignPtr ctxfp $ \ctxp' ->
619+
(unwrapContextCallback callbackPtr) ctxp' result
620+
_ <- forkIO $ do
621+
let result = 3 -- perform your complex computation here
622+
callback result
623+
pure ()
624+
```
625+
626+
The context pointer that we pass from the Swift side will be an
627+
object containing the closure itself. The function passed as
628+
the function pointer will merely cast the object to the known
629+
closure type and call it.
630+
631+
To convert our Swift closure into a raw pointer, we'll use
632+
Swift's `Unmanaged` wrapper type. These are the methods we'll
633+
use from it:
634+
635+
```swift
636+
public struct Unmanaged<Instance : AnyObject> {
637+
public static func passRetained(_ value: Instance) -> Unmanaged<Instance>
638+
public func toOpaque() -> UnsafeMutableRawPointer
639+
public static func fromOpaque(_ value: UnsafeRawPointer) -> Unmanaged<Instance>
640+
public func takeUnretainedValue() -> Instance
641+
public func takeRetainedValue() -> Instance
642+
}
643+
```
644+
645+
Since Swift functions do not implement the `AnyObject` protocol
646+
(they are not class types), we'll need to wrap them in a object
647+
first.
648+
649+
Additionally, referring directly to a Swift function name will
650+
give a Swift function type, which is not bit-compatible with a C
651+
function type. Before casting to `HsFunPtr`, we'll need to use a
652+
safe `as` cast to a `@convention(c)` type.
653+
654+
```swift
655+
func contextCallbackExample(f: ((CInt) -> Void)) {
656+
class Wrap<T> {
657+
var inner: T
658+
659+
init(_ inner: T) {
660+
self.inner = inner
661+
}
662+
}
663+
func release(context: HsPtr) {
664+
let _: Wrap<(CInt) -> Void> = Unmanaged.fromOpaque(context).takeRetainedValue()
665+
}
666+
func call(context: HsPtr, value: CInt) {
667+
let wf: Wrap<(CInt) -> Void> = Unmanaged.fromOpaque(context).takeUnretainedValue()
668+
let f = wf.inner
669+
f(value)
670+
}
671+
let release_hs = unsafeBitCast(
672+
release as @convention(c) (HsPtr) -> Void, to: HsFunPtr.self)
673+
let call_hs = unsafeBitCast(
674+
call as @convention(c) (HsPtr, CInt) -> Void, to: HsFunPtr.self)
675+
let ctx = Unmanaged.passRetained(Wrap(f)).toOpaque()
676+
SwiftHaskell.contextCallbackExample(ctx, release_hs, call_hs)
677+
}
678+
```
679+
680+
#### Passing Haskell Functions to Swift
681+
682+
In addition to the static `foreign export`, we can export
683+
dynamically created Haskell functions with `foreign export
684+
"wrapper"`. Unlike when passing Swift closures, a separate
685+
context pointer is not needed as the Haskell runtime supplies a
686+
distinct function pointer address for each wrapped function.
687+
688+
```haskell
689+
import Foreign.C
690+
import Foreign.Ptr
691+
692+
foreign export ccall makeMultiplier :: CInt -> IO (FunPtr (CInt -> CInt))
693+
foreign import ccall "wrapper" wrapMultiplier
694+
:: (CInt -> CInt)
695+
-> IO (FunPtr (CInt -> CInt))
696+
697+
makeMultiplier :: CInt -> IO (FunPtr (CInt -> CInt))
698+
makeMultiplier x = wrapMultiplier (x *)
699+
```
700+
701+
To free the `FunPtr`, export `Foreign.Ptr.freeHaskellFunPtr` and
702+
call it from Swift when you're done with the function.
703+
704+
```haskell
705+
foreign export ccall freeMultiplier :: FunPtr (CInt -> CInt) -> IO ()
706+
707+
freeMultiplier :: FunPtr (CInt -> CInt) -> IO ()
708+
freeMultiplier = freeHaskellFunPtr
709+
```
710+
711+
Wrap the Haskell function in a Swift class to manage its
712+
lifetime:
713+
714+
```swift
715+
class Multiplier {
716+
let funPtr: HsFunPtr
717+
718+
init(_ x: CInt) {
719+
self.funPtr = SwiftHaskell.makeMultiplier(x)
720+
}
721+
722+
func multiply(_ y: CInt) -> CInt {
723+
typealias F = @convention(c) (CInt) -> CInt
724+
let f = unsafeBitCast(self.funPtr, to: F.self)
725+
return f(y)
726+
}
727+
728+
deinit {
729+
SwiftHaskell.freeMultiplier(self.funPtr)
730+
}
731+
}
732+
```

0 commit comments

Comments
 (0)