From e0df2aa9fd5a716d5294f30f74de4fc7ab3f4a00 Mon Sep 17 00:00:00 2001 From: Mathias Quintero Date: Wed, 15 Jul 2020 16:30:31 +0200 Subject: [PATCH 1/2] Replace symbol hacks with AssociatedTypeRequirementsVisitor hacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I built a generic version of the hacks I debugged with @a2 for decomposition. This one does not use any of the symbol hacks from before, but instead is betting on the implementations on changing the default associated type of their parent protocol. This means we can get rid of all the linux workarounds 🎉 --- Package.resolved | 9 ++ Package.swift | 6 +- Sources/CSymbols/CSymbols.c | 11 -- Sources/CSymbols/include/CSymbols.h | 7 - Sources/SwiftShortcuts/Internal/FromAny.swift | 146 ++++-------------- 5 files changed, 41 insertions(+), 138 deletions(-) delete mode 100644 Sources/CSymbols/CSymbols.c delete mode 100644 Sources/CSymbols/include/CSymbols.h diff --git a/Package.resolved b/Package.resolved index 1a46105..4ea5273 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,6 +1,15 @@ { "object": { "pins": [ + { + "package": "AssociatedTypeRequirementsKit", + "repositoryURL": "https://github.com/nerdsupremacist/AssociatedTypeRequirementsKit.git", + "state": { + "branch": null, + "revision": "5c0b95758d2d65fdb98977ee1af5648d9d192a32", + "version": "0.2.0" + } + }, { "package": "SnapshotTesting", "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git", diff --git a/Package.swift b/Package.swift index 6852aa2..2f64906 100644 --- a/Package.swift +++ b/Package.swift @@ -14,14 +14,12 @@ let package = Package( ], dependencies: [ .package(name: "SnapshotTesting", url: "https://github.com/pointfreeco/swift-snapshot-testing.git", from: "1.8.1"), + .package(url: "https://github.com/nerdsupremacist/AssociatedTypeRequirementsKit.git", from: "0.2.0"), ], targets: [ - .target( - name: "CSymbols", - dependencies: []), .target( name: "SwiftShortcuts", - dependencies: ["CSymbols"]), + dependencies: ["AssociatedTypeRequirementsKit"]), .testTarget( name: "SwiftShortcutsTests", dependencies: ["SwiftShortcuts", "SnapshotTesting"], diff --git a/Sources/CSymbols/CSymbols.c b/Sources/CSymbols/CSymbols.c deleted file mode 100644 index b0bdee1..0000000 --- a/Sources/CSymbols/CSymbols.c +++ /dev/null @@ -1,11 +0,0 @@ -#include - -void *makeAnyShortcutSymbol(void) { - extern void _swift_shortcuts_makeAnyShortcut(void); - return &_swift_shortcuts_makeAnyShortcut; -} - -void *decomposeIntoActionsSymbol(void) { - extern void _swift_shortcuts_decomposeIntoActions(void); - return &_swift_shortcuts_decomposeIntoActions; -} diff --git a/Sources/CSymbols/include/CSymbols.h b/Sources/CSymbols/include/CSymbols.h deleted file mode 100644 index d7c2511..0000000 --- a/Sources/CSymbols/include/CSymbols.h +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef CSymbols_h -#define CSymbols_h - -void *_Nonnull makeAnyShortcutSymbol(void); -void *_Nonnull decomposeIntoActionsSymbol(void); - -#endif /* CSymbols_h */ diff --git a/Sources/SwiftShortcuts/Internal/FromAny.swift b/Sources/SwiftShortcuts/Internal/FromAny.swift index ae35f17..18021e7 100644 --- a/Sources/SwiftShortcuts/Internal/FromAny.swift +++ b/Sources/SwiftShortcuts/Internal/FromAny.swift @@ -1,142 +1,56 @@ -#if os(Linux) -import CSymbols -#else -import Foundation -#endif + +import AssociatedTypeRequirementsVisitor + +private let typeEraser = ShortcutTypeEraser() +private let decomposer = ShortcutDecomposer() // MARK: - AnyShortcut from Any extension AnyShortcut { init?(_fromValue value: Any) { - // Synthesize a fake protocol conformance record to AnyShortcutConvertible - let conformance = ProtocolConformanceRecord(type: type(of: value), witnessTable: 0) - let type = unsafeBitCast(conformance, to: AnyShortcutConvertible.Type.self) - - guard let action = type.anyShortcut(from: value) else { - return nil - } + guard let action = typeEraser(value) else { return nil } self = action } } -// MARK: - AnyViewConvertible - -private let actionMetadata: ProtocolMetadata = { - let module = "SwiftShortcuts" - let name = "Shortcut" - let postfix = "_p" - let mangled = "\(module.count)\(module)\(name.count)\(name)\(postfix)" - return ProtocolMetadata(type: _typeByName(mangled)!) -}() - -private protocol AnyShortcutConvertible {} - -extension AnyShortcutConvertible { - static func anyShortcut(from action: Any) -> AnyShortcut? { - guard let witnessTable = _conformsToProtocol(Self.self, actionMetadata.protocolDescriptorVector) else { - return nil - } +// MARK: - Actions from Any - let conformanceRecord = ProtocolConformanceRecord(type: Self.self, witnessTable: Int(bitPattern: witnessTable)) - return withUnsafePointer(to: action as! Self) { pointer in makeAnyShortcut(pointer, conformanceRecord) } - } -} - -@_silgen_name("_swift_shortcuts_makeAnyShortcut") -@available(*, unavailable) -public func makeAnyShortcut(from shortcut: S) -> AnyShortcut { - return AnyShortcut(shortcut) +func actionComponents(from value: Any) -> [Action]? { + return decomposer(value) } -private typealias ShortcutToAnyShortcutFunction = @convention(thin) (UnsafeRawPointer, ProtocolConformanceRecord) -> AnyShortcut - -#if os(Linux) -private let makeAnyShortcut = unsafeBitCast(makeAnyShortcutSymbol(), to: ShortcutToAnyShortcutFunction.self) -#else -private let makeAnyShortcut: ShortcutToAnyShortcutFunction = { - let symbolName = "_swift_shortcuts_makeAnyShortcut" - let handle = dlopen(nil, RTLD_GLOBAL) - let pointer = dlsym(handle, symbolName) - return unsafeBitCast(pointer, to: ShortcutToAnyShortcutFunction.self) -}() -#endif - -// MARK: - [ActionComponent] from Any - -private protocol ActionsConvertible {} +// MARK: - ShortcutVisitor -extension ActionsConvertible { - static func actions(from shortcut: Any) -> [Action]? { - guard let witnessTable = _conformsToProtocol(Self.self, actionMetadata.protocolDescriptorVector) else { - return nil - } - - let conformanceRecord = ProtocolConformanceRecord(type: Self.self, witnessTable: Int(bitPattern: witnessTable)) - return withUnsafePointer(to: shortcut as! Self) { pointer in decomposeIntoActions(pointer, conformanceRecord) } - } -} +private protocol ShortcutVisitor: AssociatedTypeRequirementsVisitor { + associatedtype Visitor = ShortcutVisitor + associatedtype Input = Shortcut + associatedtype Output -@_silgen_name("_swift_shortcuts_decomposeIntoActions") -@available(*, unavailable) -public func decomposeIntoActions(shortcut: S) -> [Action] { - _decomposeIntoActions(shortcut: shortcut) + func callAsFunction(_ shortcut: S) -> Output } -private func _decomposeIntoActions(shortcut: S) -> [Action] { - if S.Body.self == Never.self { - return shortcut.decompose() - } +private struct ShortcutTypeEraser : ShortcutVisitor { - let body = shortcut.body - if let component = body as? Action { - return [component] - } else { - return _decomposeIntoActions(shortcut: body) + func callAsFunction(_ shortcut: S) -> AnyShortcut { + return AnyShortcut(shortcut) } -} -private typealias ShortcutToActionsFunction = @convention(thin) (UnsafeRawPointer, ProtocolConformanceRecord) -> [Action] -#if os(Linux) -private let decomposeIntoActions = unsafeBitCast(decomposeIntoActionsSymbol(), to: ShortcutToActionsFunction.self) -#else -private let decomposeIntoActions: ShortcutToActionsFunction = { - let symbolName = "_swift_shortcuts_decomposeIntoActions" - let handle = dlopen(nil, RTLD_GLOBAL) - let pointer = dlsym(handle, symbolName) - return unsafeBitCast(pointer, to: ShortcutToActionsFunction.self) -}() -#endif - -func actionComponents(from value: Any) -> [Action]? { - // Synthesize a fake protocol conformance record to ActionStepsConvertible - let conformance = ProtocolConformanceRecord(type: type(of: value), witnessTable: 0) - let type = unsafeBitCast(conformance, to: ActionsConvertible.Type.self) - return type.actions(from: value) } -// MARK: - Protocol Runtime Information +private struct ShortcutDecomposer : ShortcutVisitor { -private struct ProtocolConformanceRecord { - let type: Any.Type - let witnessTable: Int -} - -private struct ProtocolDescriptor {} - -private struct ProtocolMetadata { - let kind: Int - let layoutFlags: UInt32 - let numberOfProtocols: UInt32 - let protocolDescriptorVector: UnsafeMutablePointer + func callAsFunction(_ shortcut: S) -> [Action] { + if S.Body.self == Never.self { + return shortcut.decompose() + } - init(type: Any.Type) { - self = unsafeBitCast(type, to: UnsafeMutablePointer.self).pointee + let body = shortcut.body + if let component = body as? Action { + return [component] + } else { + return self(body) + } } -} -@_silgen_name("swift_conformsToProtocol") -private func _conformsToProtocol( - _ type: Any.Type, - _ protocolDescriptor: UnsafeMutablePointer -) -> UnsafeRawPointer? +} From b4ff340e3dff7c49ee99684cd807959f133d4b65 Mon Sep 17 00:00:00 2001 From: Mathias Quintero Date: Wed, 15 Jul 2020 16:33:38 +0200 Subject: [PATCH 2/2] Simplifying decompose function --- .../SwiftShortcuts/Internal/ActionDecomposable.swift | 2 +- Sources/SwiftShortcuts/Internal/FromAny.swift | 11 +---------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/Sources/SwiftShortcuts/Internal/ActionDecomposable.swift b/Sources/SwiftShortcuts/Internal/ActionDecomposable.swift index 36d2c50..7cbc464 100644 --- a/Sources/SwiftShortcuts/Internal/ActionDecomposable.swift +++ b/Sources/SwiftShortcuts/Internal/ActionDecomposable.swift @@ -83,6 +83,6 @@ extension Optional: ActionDecomposable where Wrapped: Shortcut { extension TupleShortcut: ActionDecomposable { func decompose() -> [Action] { let mirror = Mirror(reflecting: value) - return mirror.children.flatMap { _, value in AnyShortcut(_fromValue: value)!.decompose() } + return mirror.children.flatMap { _, value in actionComponents(from: value)! } } } diff --git a/Sources/SwiftShortcuts/Internal/FromAny.swift b/Sources/SwiftShortcuts/Internal/FromAny.swift index 18021e7..9cb4606 100644 --- a/Sources/SwiftShortcuts/Internal/FromAny.swift +++ b/Sources/SwiftShortcuts/Internal/FromAny.swift @@ -41,16 +41,7 @@ private struct ShortcutTypeEraser : ShortcutVisitor { private struct ShortcutDecomposer : ShortcutVisitor { func callAsFunction(_ shortcut: S) -> [Action] { - if S.Body.self == Never.self { - return shortcut.decompose() - } - - let body = shortcut.body - if let component = body as? Action { - return [component] - } else { - return self(body) - } + return shortcut.decompose() } }