Return signal handler values across the C ABI
This commit is contained in:
parent
a172d07ab1
commit
858544cdec
1 changed files with 83 additions and 5 deletions
|
|
@ -867,6 +867,75 @@ private func renderClass(_ plan: ClassPlan) -> String {
|
|||
|
||||
// ── Signal rendering ──
|
||||
|
||||
/// How a signal handler's Swift return value is handed back across the C ABI.
|
||||
///
|
||||
/// `nil` for a void signal. Switches on `marshalIn` - the mapping's
|
||||
/// Swift-into-C direction, which is exactly the direction a signal return
|
||||
/// travels - so the spellings match the parameter path in `renderCallable`.
|
||||
private struct SignalReturnBridge {
|
||||
/// The `@_cdecl` function's return type.
|
||||
let cType: String
|
||||
/// Returned when `data` is nil and the closure never runs.
|
||||
let zero: String
|
||||
/// Statements that evaluate `call` and `return` its C form. Emitted
|
||||
/// inside `MainActor.assumeIsolated`.
|
||||
let emit: (_ call: String) -> [String]
|
||||
}
|
||||
|
||||
private func signalReturnBridge(_ m: Mapping) -> SignalReturnBridge? {
|
||||
let isOptional = m.swiftType.hasSuffix("?")
|
||||
switch m.marshalIn {
|
||||
case .boolToGboolean:
|
||||
return .init(cType: "Int32", zero: "0", emit: { ["return (\($0)) ? 1 : 0"] })
|
||||
case .direct:
|
||||
return .init(cType: m.cSwiftType, zero: "0", emit: { ["return \($0)"] })
|
||||
case .numericCast:
|
||||
return .init(cType: m.cSwiftType, zero: "0", emit: { ["return numericCast(\($0))"] })
|
||||
case .enumRaw, .bitfieldRaw:
|
||||
return .init(
|
||||
cType: m.cSwiftType, zero: ".init(rawValue: 0)",
|
||||
emit: { ["return \(m.cSwiftType)(rawValue: numericCast((\($0)).rawValue))"] })
|
||||
case .stringToC:
|
||||
// The signal's return is an owned `char*`; duplicate so the Swift
|
||||
// String's buffer is not handed to C.
|
||||
return .init(cType: "UnsafeMutablePointer<CChar>?", zero: "nil",
|
||||
emit: { ["return g_strdup(\($0))"] })
|
||||
case .objectPointer, .interfacePointer:
|
||||
// transfer=full (`.objectWrap`/`.interfaceWrap(adopt:)`) means C takes
|
||||
// ownership of a reference, so add one; transfer=none
|
||||
// (`.objectRetain`) hands over a borrowed pointer unchanged.
|
||||
let transfersFull: Bool
|
||||
switch m.marshalOut {
|
||||
case .objectRetain: transfersFull = false
|
||||
default: transfersFull = true
|
||||
}
|
||||
let refLine = transfersFull ? ["_ = g_object_ref(r.pointer)"] : []
|
||||
// _instancePointer overloads return OpaquePointer or
|
||||
// UnsafeMutablePointer<T>; neither matches UnsafeMutableRawPointer.
|
||||
if m.cSwiftType.replacingOccurrences(of: "?", with: "") == "UnsafeMutableRawPointer" {
|
||||
if isOptional {
|
||||
return .init(cType: m.cSwiftType, zero: "nil", emit: { call in
|
||||
["guard let r = \(call) else { return nil }"] + refLine
|
||||
+ ["return r.pointer"]
|
||||
})
|
||||
}
|
||||
return .init(cType: m.cSwiftType, zero: "nil", emit: { call in
|
||||
["let r = \(call)"] + refLine + ["return r.pointer"]
|
||||
})
|
||||
}
|
||||
if isOptional {
|
||||
return .init(cType: m.cSwiftType, zero: "nil", emit: { call in
|
||||
["guard let r = \(call) else { return nil }"] + refLine
|
||||
+ ["return _instancePointer(r.pointer)"]
|
||||
})
|
||||
}
|
||||
return .init(cType: m.cSwiftType, zero: "nil", emit: { call in
|
||||
["let r = \(call)"] + refLine + ["return _instancePointer(r.pointer)"]
|
||||
})
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
/// Renders a `@_cdecl nonisolated` trampoline for a GObject signal.
|
||||
///
|
||||
/// The `@_cdecl` ABI is required because C calls this function via a raw
|
||||
|
|
@ -914,13 +983,21 @@ private func renderSignalTrampoline(_ plan: SignalPlan) -> [String] {
|
|||
}
|
||||
let wrapperRefs = (0..<allParams.count).map { "w\($0)" }.joined(separator: ", ")
|
||||
let shadowBody = shadowLines.map { " \($0)" }.joined(separator: "\n")
|
||||
let wrapperBody = (wrapperLines + ["box.closure(\(wrapperRefs))"]).map { " \($0)" }.joined(separator: "\n")
|
||||
let bridge = plan.returnMapping.flatMap(signalReturnBridge)
|
||||
let call = "box.closure(\(wrapperRefs))"
|
||||
let tail = bridge.map { $0.emit(call) } ?? [call]
|
||||
let wrapperBody = (wrapperLines + tail).map { " \($0)" }.joined(separator: "\n")
|
||||
|
||||
lines.append("@_cdecl(\"\(plan.trampolineCName)\")")
|
||||
lines.append("nonisolated func \(plan.trampolineCName)(\(cDecl)) {")
|
||||
lines.append(" guard let data else { return }")
|
||||
lines.append("nonisolated func \(plan.trampolineCName)(\(cDecl))\(bridge.map { " -> \($0.cType)" } ?? "") {")
|
||||
lines.append(" guard let data else { return \(bridge?.zero ?? "") }")
|
||||
lines.append(" let box = Unmanaged<_ClosureBox<\(closureType)>>.fromOpaque(data).takeUnretainedValue()")
|
||||
lines.append(shadowBody)
|
||||
lines.append(" MainActor.assumeIsolated {")
|
||||
if let bridge {
|
||||
lines.append(" return MainActor.assumeIsolated { () -> \(bridge.cType) in")
|
||||
} else {
|
||||
lines.append(" MainActor.assumeIsolated {")
|
||||
}
|
||||
lines.append(wrapperBody)
|
||||
lines.append(" }")
|
||||
lines.append("}")
|
||||
|
|
@ -987,6 +1064,7 @@ private func renderSignalConnect(_ plan: SignalPlan, className: String) -> [Stri
|
|||
let closureParams = closureParamTypes.isEmpty ? "" : closureParamTypes.joined(separator: ", ")
|
||||
let closureType = "(\(closureParams)) -> \(closureRet)"
|
||||
|
||||
let cRet = plan.returnMapping.flatMap(signalReturnBridge)?.cType ?? "Void"
|
||||
// Build @convention(c) type for the trampoline's unsafeBitCast
|
||||
let cTypes = ["UnsafeMutableRawPointer"] + realParams.map { p in
|
||||
p.mapping.cSwiftType.replacingOccurrences(of: "?", with: "")
|
||||
|
|
@ -1014,7 +1092,7 @@ private func renderSignalConnect(_ plan: SignalPlan, className: String) -> [Stri
|
|||
lines.append(" let ptr = self.pointer")
|
||||
lines.append(" \(detailBody)")
|
||||
lines.append(" return signalName.withCString { cName in")
|
||||
lines.append(" let id = _sgtkSignalConnectData(ptr, cName, unsafeBitCast(\(plan.trampolineCName) as (@convention(c) (\(cTypeStr)) -> Void), to: UnsafeRawPointer.self), dataPtr, unsafeBitCast(destroyFn as (@convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void), to: UnsafeRawPointer.self), 0)")
|
||||
lines.append(" let id = _sgtkSignalConnectData(ptr, cName, unsafeBitCast(\(plan.trampolineCName) as (@convention(c) (\(cTypeStr)) -> \(cRet)), to: UnsafeRawPointer.self), dataPtr, unsafeBitCast(destroyFn as (@convention(c) (UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void), to: UnsafeRawPointer.self), 0)")
|
||||
lines.append(" return SignalHandle(id: id, instance: ptr)")
|
||||
lines.append(" }")
|
||||
lines.append(" }")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue