Skip to content

Commit

Permalink
comments
Browse files Browse the repository at this point in the history
  • Loading branch information
li-feng-sc committed Jul 12, 2024
1 parent ea62a3b commit bcf8b1c
Show file tree
Hide file tree
Showing 9 changed files with 146 additions and 130 deletions.
7 changes: 6 additions & 1 deletion support-lib/swift/DJData.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import DjinniSupportCxx
import Foundation

// DataView and DataRef are bridged to `NSData` instead of `Data` because `Data`
// uses small buffer optimization and it does not always provide a stable
// pointer that can be safely accessed in C++ (the pointer we can get from a
// small Data object will only work within the scope of `withUnsafeBytes` block)
public enum DataViewMarshaller: Marshaller {
public typealias SwiftType = NSData
public static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType {
Expand All @@ -11,7 +15,8 @@ public enum DataViewMarshaller: Marshaller {
return djinni.swift.makeRangeValue(s.bytes, s.length)
}
}

// The C++ side implementation of DataRef uses CFData which is toll-free bridged
// to NSData.
public enum DataRefMarshaller: Marshaller {
public typealias SwiftType = NSData
public static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType {
Expand Down
16 changes: 14 additions & 2 deletions support-lib/swift/DJFuture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import DjinniSupportCxx
import Foundation
import Combine

// DJinni future<> maps to Combine.Future<> in Swift
public typealias DJFuture<T> = Future<T, DjinniError>

// Type erased interface for PromiseHolder because in futureCb() we don't have
// the type parameter.
protocol AbstractPromiseHolder: AnyObject {
func fulfillPromise(value: UnsafePointer<djinni.swift.AnyValue>)
}

// The Swift Future wrapper object that can be fulfilled by a C++ call
class PromiseHolder<T: Marshaller>: AbstractPromiseHolder {
var promise: DJFuture<T.SwiftType>.Promise
init(marshaller: T.Type, promise: @escaping DJFuture<T.SwiftType>.Promise) {
Expand All @@ -21,7 +24,9 @@ class PromiseHolder<T: Marshaller>: AbstractPromiseHolder {
}
}
}

// A C++ friendly function. This is passed to C++ as the continuation routine of
// the C++ future. It calls the PromiseHolder and forwards the C++ future's
// result or error to the Swift future.
public func futureCb(
ptr: UnsafeMutableRawPointer?,
result: UnsafeMutablePointer<djinni.swift.AnyValue>?)
Expand All @@ -31,6 +36,8 @@ public func futureCb(
promiseHolder.fulfillPromise(value:result!)
}

// A C++ friendly function to release the subscription token stored with the C++
// future value.
public func cleanupCb(psubscription: UnsafeMutableRawPointer?) -> Void {
let _ = Unmanaged<AnyCancellable>.fromOpaque(psubscription!).takeRetainedValue()
}
Expand All @@ -39,15 +46,19 @@ public enum FutureMarshaller<T: Marshaller>: Marshaller {
public typealias SwiftType = DJFuture<T.SwiftType>
public static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType {
return Future() { promise in
// Allocate the Swift future wrapper
let promiseHolder = PromiseHolder(marshaller: T.self, promise: promise)
let promiseHolderPtr = Unmanaged.passRetained(promiseHolder).toOpaque()
// And connect it with the C++ future
withUnsafePointer(to: v) { p in
djinni.swift.setFutureCb(p, futureCb, promiseHolderPtr)
}
}
}
public static func toCpp(_ s: SwiftType) -> djinni.swift.AnyValue {
// Create a C++ future
var futureValue = djinni.swift.makeFutureValue(cleanupCb)
// Connect it with the Swift future
let subscription = s.sink { completion in
if case let .failure(e) = completion {
var errorValue = djinni.swift.makeVoidValue()
Expand All @@ -58,6 +69,7 @@ public enum FutureMarshaller<T: Marshaller>: Marshaller {
var cppValue = T.toCpp(v)
djinni.swift.setFutureResult(&futureValue, &cppValue)
}
// Store the subscription token so that the connection remains alive.
let pSubscription = Unmanaged.passRetained(subscription).toOpaque()
djinni.swift.storeSubscription(&futureValue, pSubscription)
return futureValue
Expand Down
12 changes: 9 additions & 3 deletions support-lib/swift/DJMarshal.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import DjinniSupportCxx
import Foundation

// Common interface for all Swift type marshallers
// The C++ type is always djinni.swift.AnyValue
public protocol Marshaller {
associatedtype SwiftType
static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType
static func toCpp(_ s: SwiftType) -> djinni.swift.AnyValue
}

// Djinni generated enums are always backed by Int32
public enum EnumMarshaller<T: RawRepresentable>: Marshaller where T.RawValue == Int32 {
public typealias SwiftType = T
public static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType {
Expand All @@ -16,7 +18,7 @@ public enum EnumMarshaller<T: RawRepresentable>: Marshaller where T.RawValue ==
return djinni.swift.I32.fromCpp(s.rawValue)
}
}

// All integer types smaller than 32-bit are handled by this
public enum SmallIntMarshaller<T: BinaryInteger>: Marshaller {
public typealias SwiftType = T
public static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType {
Expand All @@ -26,6 +28,7 @@ public enum SmallIntMarshaller<T: BinaryInteger>: Marshaller {
return djinni.swift.I32.fromCpp(Int32(s))
}
}
// 64-bit integer
public enum I64Marshaller: Marshaller {
public typealias SwiftType = Int64
public static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType {
Expand All @@ -35,6 +38,7 @@ public enum I64Marshaller: Marshaller {
return djinni.swift.I64.fromCpp(s)
}
}
// Both float and double are marshalled to double values
public enum FloatMarshaller<T: BinaryFloatingPoint>: Marshaller {
public typealias SwiftType = T
public static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType {
Expand All @@ -44,6 +48,7 @@ public enum FloatMarshaller<T: BinaryFloatingPoint>: Marshaller {
return djinni.swift.F64.fromCpp(Double(s))
}
}
// Bool is marshalled as Int32 (0 or 1)
public enum BoolMarshaller: Marshaller {
public typealias SwiftType = Bool
public static func fromCpp(_ v: djinni.swift.AnyValue) -> SwiftType {
Expand All @@ -53,7 +58,7 @@ public enum BoolMarshaller: Marshaller {
return djinni.swift.I32.fromCpp(s ? 1 : 0)
}
}

// Aliases for number types
public typealias I8Marshaller = SmallIntMarshaller<Int8>
public typealias I16Marshaller = SmallIntMarshaller<Int16>
public typealias I32Marshaller = SmallIntMarshaller<Int32>
Expand Down Expand Up @@ -158,6 +163,7 @@ public enum ListMarshaller<T: Marshaller>: Marshaller {
}
}

// Swift don't need to box primitive types in arrays so they are identical to list
public typealias ArrayMarshaller<T: Marshaller> = ListMarshaller<T>

public enum SetMarshaller<T: Marshaller>: Marshaller where T.SwiftType: Hashable {
Expand Down
5 changes: 5 additions & 0 deletions support-lib/swift/DJOutcome.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import DjinniSupportCxx
import Foundation

// `outcome<T>` is marshalled as a composite value
// 1. If the `outcome` object contains a successful value, then it's the [0]
// element in the composite value
// 2. If the `outcome` object contains an error value, then element [0] is void,
// and element [1] is the error value.
public enum OutcomeMarshaller<Res: Marshaller, Err: Marshaller>: Marshaller
where Err.SwiftType: Error {
public typealias SwiftType = Result<Res.SwiftType, Err.SwiftType>
Expand Down
117 changes: 71 additions & 46 deletions support-lib/swift/DjinniSupport.swift
Original file line number Diff line number Diff line change
@@ -1,25 +1,39 @@
import DjinniSupportCxx
import Foundation

// Define a C++ vtable like data structure for dispatching proxy methods to
// protocol methods. Each entry in the table is function pointer that takes 3
// parameters
// - The object instance (that conforms to the protocol)
// - The parameter list
// - The return value (output parameter)
// The return value has to be an output parameter instead of the function's
// return type, otherwise the Swift compiler will crash (if this crash is fixed
// in a later version of the compiler, we may change this).
public typealias Vtbl<T> = [(T, UnsafePointer<djinni.swift.ParameterList>?, UnsafeMutablePointer<djinni.swift.AnyValue>?) throws -> Void]

public class GenericProtocolWrapperContext {
public func dispatch(idx: Int32, params: UnsafePointer<djinni.swift.ParameterList>?, ret: UnsafeMutablePointer<djinni.swift.AnyValue>?) -> Void {
fatalError("pure virtual")
}
public func getInst() -> AnyObject {
fatalError("pure virtual")
}
// Type erased interface for ProtocolWrapperContext. We don't have the type
// parameter T for ProtocolWrapperContext inside dispatcherProtocalCall (called
// by C++ code)
protocol GenericProtocolWrapperContext: AnyObject {
func dispatch(idx: Int32, params: UnsafePointer<djinni.swift.ParameterList>?, ret: UnsafeMutablePointer<djinni.swift.AnyValue>?) -> Void
func getInst() -> AnyObject
}

public final class ProtocolWrapperContext<T>: GenericProtocolWrapperContext {
// The bridge between C++ caller and Swift protocol. We store
// - The object instance (that conforms to our protocol)
// - The dispatch table for each callable method in the protocol
final class ProtocolWrapperContext<T>: GenericProtocolWrapperContext {
var inst: T
var vtbl: Vtbl<T>
public init(inst: T, vtbl: Vtbl<T>) {
init(inst: T, vtbl: Vtbl<T>) {
self.inst = inst
self.vtbl = vtbl
}
public override func dispatch(idx: Int32, params: UnsafePointer<djinni.swift.ParameterList>?, ret: UnsafeMutablePointer<djinni.swift.AnyValue>?) -> Void {
func dispatch(idx: Int32, params: UnsafePointer<djinni.swift.ParameterList>?, ret: UnsafeMutablePointer<djinni.swift.AnyValue>?) -> Void {
// No Swift error will cross the language boundary. They are converted
// to `ErrorValue`s which will be translated into C++ exceptions on the
// other side.
do {
try vtbl[Int(idx)](inst, params, ret)
} catch let error as DjinniError {
Expand All @@ -28,71 +42,85 @@ public final class ProtocolWrapperContext<T>: GenericProtocolWrapperContext {
djinni.swift.setErrorMessage(ret, std.string(String(describing: error)))
}
}
public override func getInst() -> AnyObject {
func getInst() -> AnyObject {
return inst as AnyObject
}
}

// This function has a C++ friendly signature. It is passed to C++ and will be
// used by proxy objects to dispatch protocol calls.
public func dispatcherProtocalCall(
ptr: UnsafeMutableRawPointer?,
idx: Int32,
params: UnsafePointer<djinni.swift.ParameterList>?,
ret: UnsafeMutablePointer<djinni.swift.AnyValue>?)
ptr: UnsafeMutableRawPointer?, // The Swift protocol wrapper object as an opaque pointer
idx: Int32, // The method index (starting from 0)
params: UnsafePointer<djinni.swift.ParameterList>?, // Input parameters from C++ caller
ret: UnsafeMutablePointer<djinni.swift.AnyValue>?) // Return value that will be passed back to C++
-> Void {
guard let pctx = ptr else { return }
if (idx < 0) {
// release and destroy the context
let ctx = Unmanaged<GenericProtocolWrapperContext>.fromOpaque(pctx).takeRetainedValue()
print("swift proxy cache before destroy", SwiftProxyCache.shared.mapPtrToProxy)
let ctx = Unmanaged<AnyObject>.fromOpaque(pctx).takeUnretainedValue() as! GenericProtocolWrapperContext
if (idx >= 0) {
// Dynamic dispatch on the vtbl. We use `takeUnretainedValue` here
// because we want to keep the wrapper object alive inside the C++ side
// proxy (inherits ProtocolWrapper).
ctx.dispatch(idx: idx, params: params, ret: ret)
} else {
// If the index is negative, release and destroy the context. We do
// this when the C++ side proxy (ProtocolWrapper) is destroyed.
let key = Unmanaged.passUnretained(ctx.getInst()).toOpaque()
SwiftProxyCache.shared.mapPtrToProxy.removeValue(forKey: key)
print("swift proxy cache after destroy", SwiftProxyCache.shared.mapPtrToProxy)
} else {
// dynamic dispatch on the vtbl
let ctx = Unmanaged<GenericProtocolWrapperContext>.fromOpaque(pctx).takeUnretainedValue()
ctx.dispatch(idx: idx, params: params, ret: ret)
}
}

// Base class for a Swift callable proxy of a C++ object
open class CppProxy {
// Stores a C++ interface. A C++ interface value is a double pointer:
// 1. A shared_ptr<> that keeps the C++ implementation object alive
// 2. A shared_ptr<> to a ProtocolWrapper that facilitates dispatching
public var inst: djinni.swift.AnyValue
public init(inst: djinni.swift.AnyValue) {
self.inst = inst
}
deinit {
destroyCppProxy(inst)
// Remove the C++ proxy pointer from the proxy cache
withUnsafePointer(to: inst) { p in
let info = djinni.swift.getInterfaceInfo(p)
CppProxyCache.shared.mapPtrToProxy.removeValue(forKey: info.cppPointer)
}
}
}

class CppProxyCache {
static let shared = CppProxyCache()
// c++ objects in swift
// C++ objects in swift.
// Key: raw C++ implementation object pointer
// Value: Swift callable proxy converted to an *Unretained* (weak) opaque pointer
var mapPtrToProxy: [UnsafeRawPointer: UnsafeMutableRawPointer] = [:]
}

class SwiftProxyCache {
static let shared = SwiftProxyCache()
// swift objects in c++
// Swift objects in c++
// Key: Swift implementation object converted to an *Unretained* opaque pointer
// Value : Weak proxy, no ownership, but can be converted to a C++ callable strong proxy
var mapPtrToProxy: [UnsafeMutableRawPointer: djinni.swift.WeakSwiftProxy] = [:]
}

// 1. object is a an existing cpp proxy : return cpp proxy
// 2. object is a an existing swift proxy: unwrap the original swift object
// 3. need to create a new proxy : call newProxyFunc
// 1. Object is a an existing cpp proxy : return cpp proxy
// 2. Object is a an existing swift proxy: unwrap the original swift object
// 3. Need to create a new proxy : call newProxyFunc
public func cppInterfaceToSwift<I>(_ c: djinni.swift.AnyValue,
_ newProxyFunc: ()->I) -> I {
return withUnsafePointer(to: c) { p in
let info = djinni.swift.getInterfaceInfo(p)
// for 1. check the cpp proxy cache
// 1. Check the CppProxyCache
if let s = CppProxyCache.shared.mapPtrToProxy[info.cppPointer] {
return Unmanaged<AnyObject>.fromOpaque(s).takeUnretainedValue() as! I
}
// for 2. check if c++ ptr exists in SwiftProxyCache.mapCPtrToSwift
// 2. Check if c++ ptr exists in SwiftProxyCache
if let pctx = info.ctxPointer {
let ctx = Unmanaged<GenericProtocolWrapperContext>.fromOpaque(pctx).takeUnretainedValue()
let ctx = Unmanaged<AnyObject>.fromOpaque(pctx).takeUnretainedValue() as! GenericProtocolWrapperContext
return ctx.getInst() as! I
}
// 3.
// 3. Create new proxy and store unretained (weak) pointer in CppProxyCache
let newProxy = newProxyFunc()
CppProxyCache.shared.mapPtrToProxy[info.cppPointer] = Unmanaged.passUnretained(newProxy as AnyObject).toOpaque()
return newProxy
Expand All @@ -104,35 +132,31 @@ public func cppInterfaceToSwift<I>(_ c: djinni.swift.AnyValue,
// 3. need to create a new proxy : call newProxyFunc
public func swiftInterfaceToCpp<I>(_ s: I,
_ newProxyFunc: ()->djinni.swift.AnyValue) -> djinni.swift.AnyValue {
// 1. try cast to CppProxy and unwrap
// 1. Try cast to CppProxy and unwrap
if let cppproxy = s as? CppProxy {
return cppproxy.inst
}
// 2. check swift proxy cache
// 2. Check swift proxy cache
let key = Unmanaged.passUnretained(s as AnyObject).toOpaque()
if let weakProxy = SwiftProxyCache.shared.mapPtrToProxy[key] {
return djinni.swift.strongify(weakProxy)
}
// 3.
// 3. Create new proxy and store weak reference in SwiftProxyCache
let newProxy = newProxyFunc()
SwiftProxyCache.shared.mapPtrToProxy[key] = djinni.swift.weakify(newProxy)
return newProxy
}

// Shortcut function to create a Swift protocol wrapper context and return its
// *Retained* pointer. The returned pointer is owned by the C++ side
// ProtocolWrapper, and released in ProtocolWrapper::~ProtocolWrapper() by
// sending a dispatch request with index -1.
public func ctxPtr<I> (_ s: I, _ vtbl: Vtbl<I>) -> UnsafeMutableRawPointer {
let ctx = ProtocolWrapperContext(inst: s, vtbl: vtbl)
return Unmanaged.passRetained(ctx).toOpaque()
}

public func destroyCppProxy(_ inst: djinni.swift.AnyValue) {
withUnsafePointer(to: inst) { p in
let info = djinni.swift.getInterfaceInfo(p)
print("before destroy cppproxycache entry", CppProxyCache.shared.mapPtrToProxy)
CppProxyCache.shared.mapPtrToProxy.removeValue(forKey: info.cppPointer)
print("after destroy cppproxycache entry", CppProxyCache.shared.mapPtrToProxy)
}
}

// Wraps C++ ErrorValue as a Swift throwable Error
public class DjinniError: Error {
var wrapped: djinni.swift.ErrorValue
init(_ wrapped: djinni.swift.ErrorValue) {
Expand All @@ -144,6 +168,7 @@ public class DjinniError: Error {
public var errorMessage: String { return String(wrapped.msg) }
}

// Called by stubs to convert C++ exceptions stored as ErrorValue to Swift errors
public func handleCppErrors(_ ret: UnsafePointer<djinni.swift.AnyValue>) throws {
if (djinni.swift.isError(ret)) {
throw DjinniError(djinni.swift.getError(ret))
Expand Down
Loading

0 comments on commit bcf8b1c

Please sign in to comment.