diff --git a/.github/workflows/compatibility_tests.yml b/.github/workflows/compatibility_tests.yml index 5045db9c..c18b3656 100644 --- a/.github/workflows/compatibility_tests.yml +++ b/.github/workflows/compatibility_tests.yml @@ -32,12 +32,14 @@ jobs: - name: Run compatibility tests on OpenSwiftUI + macOS run: | swift test \ + --filter OpenSwiftUICompatibilityTests \ --build-path .build-compatibility-test-debug env: OPENSWIFTUI_COMPATIBILITY_TEST: 0 - name: Run compatibility tests on SwiftUI + macOS run: | swift test \ + --filter OpenSwiftUICompatibilityTests \ --build-path .build-compatibility-test-debug env: OPENSWIFTUI_COMPATIBILITY_TEST: 1 @@ -69,6 +71,7 @@ jobs: run: swift --version - name: Run compatibility tests on OpenSwiftUI + iOS run: | + # FIXME: xcodebuild will run all test targets. xcodebuild test \ -scheme OpenSwiftUI \ -configuration Debug \ diff --git a/Package.resolved b/Package.resolved index 20bd5a6e..d079f1a1 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "5ab08033fa312aa04f0cf9d4751edd41156f13745ec663a3da48d38ca11c507b", + "originHash" : "093c1851811a8423bb946386b7901adf094aa28258f12ae335ef9ee3a59018c7", "pins" : [ { "identity" : "opengraph", @@ -7,7 +7,7 @@ "location" : "https://github.com/OpenSwiftUIProject/OpenGraph", "state" : { "branch" : "main", - "revision" : "3e9b6463ef2abec08269c86dea3b97f3cb0225ee" + "revision" : "7f22fb5948bb6bc77603f5496a9881622041d3bb" } } ], diff --git a/Sources/COpenSwiftUICore/include/kdebug_Private.h b/Sources/COpenSwiftUICore/include/kdebug_Private.h new file mode 100644 index 00000000..032502ca --- /dev/null +++ b/Sources/COpenSwiftUICore/include/kdebug_Private.h @@ -0,0 +1,168 @@ +// +// kdebug_Private.h +// COpenSwiftUICore +// +// Audited for RELEASE_2024 +// Status: Complete + +#ifndef kdebug_Private_h +#define kdebug_Private_h + +#include "OpenSwiftUIBase.h" + +#if OPENSWIFTUI_TARGET_OS_DARWIN + +/* + * Copyright (c) 2000-2018 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ + +#include +#include +#include + +#if OPENSWIFTUI_TARGET_OS_OSX +#include +#endif + +#include + +#pragma mark - user space SPI + +/* +* OS components can use the full precision of the "code" field +* (Class, SubClass, Code) to inject events using kdebug_trace() by +* using: +* +* kdebug_trace(KDBG_CODE(DBG_XPC, 15, 1) | DBG_FUNC_NONE, 1, 2, 3, 4); +* +* These trace points can be included in production code, since they +* use reserved, non-overlapping ranges. The performance impact when +* kernel tracing is not enabled is minimal. However, when tracing is enabled, +* each tracepoint becomes a syscall. For this reason, os_signpost(3) is +* recommended instead of kdebug_trace(2). +* +* Classes can be reserved by filing a Radar in xnu | ktrace. +* +* 64-bit arguments may be truncated if the system is using a 32-bit +* kernel. +* +* On error, -1 will be returned and errno will indicate the error. +*/ +OPENSWIFTUI_EXPORT int kdebug_trace(uint32_t code, uint64_t arg1, uint64_t arg2, uint64_t arg3, + uint64_t arg4) +__OSX_AVAILABLE(10.10) __IOS_AVAILABLE(8.2); + +/*! +* @function kdebug_trace_string +* +* @discussion +* This function emits strings to kdebug trace along with an ID and allows +* for previously-traced strings to be overwritten and invalidated. +* +* To start tracing a string and generate an ID to use to refer to it: +* +* string_id = kdebug_trace_string(debugid, 0, "string"); +* +* To replace a string previously traced: +* +* string_id = kdebug_trace_string(debugid, string_id, "new string"); +* +* To invalidate a string ID: +* +* string_id = kdebug_trace_string(debugid, string_id, NULL); +* +* To check for errors: +* +* if ((int64_t)string_id == -1) { perror("string error") } +* +* @param debugid +* The `debugid` to check if its enabled before tracing and include as +* an argument in the event containing the string. +* +* Some classes or subclasses are reserved for specific uses and are not +* allowed to be used with this function. No function qualifiers are +* allowed on `debugid`. +* +* @param str_id +* When 0, a new ID will be generated and returned if tracing is +* enabled. +* +* Otherwise `str_id` must contain an ID that was previously generated +* with this function. Clents should pass NULL in `str` if `str_id` +* is no longer in use. Otherwise, the string previously mapped to +* `str_id` will be overwritten with the contents of `str`. +* +* @param str +* A NUL-terminated 'C' string containing the characters that should be +* traced alongside `str_id`. +* +* If necessary, the string will be truncated at an +* implementation-defined length. The string must not be the empty +* string, but can be NULL if a valid `str_id` is provided. +* +* @return +* 0 if tracing is disabled or `debugid` is being filtered out of trace. +* It can also return (int64_t)-1 if an error occured. Otherwise, +* it returns the ID to use to refer to the string in future +* kdebug_trace(2) calls. +* +* The errors that can occur are: +* +* EINVAL +* There are function qualifiers on `debugid`, `str` is empty, or +* `str_id` was not generated by this function. +* EPERM +* The `debugid`'s class or subclass is reserved for internal use. +* EFAULT +* `str` is an invalid address or NULL when `str_id` is 0. +*/ +OPENSWIFTUI_EXPORT uint64_t kdebug_trace_string(uint32_t debugid, uint64_t str_id, + const char *str) +__OSX_AVAILABLE(10.11) __IOS_AVAILABLE(9.0); + +/* +* Although the performance impact of kdebug_trace() when kernel +* tracing is not enabled is minimal, it may require the caller to +* perform an expensive calculation/summarization. This cost can be +* skipped by checking the kdebug_is_enabled() predicate: +* +* if (kdebug_is_enabled(KDBG_CODE(DBG_XPC, 15, 1))) { +* uint64_t arg1 = ...; +* uint64_t arg2 = ...; +* kdebug_trace(KDBG_CODE(DBG_XPC, 15, 1) | DBG_FUNC_NONE, arg1, arg2, 0, 0); +* } +* +* If tracing is enabled for the code at the time of the check, 1 +* will be returned. Otherwise, 0 will be returned. +*/ +OPENSWIFTUI_EXPORT bool kdebug_is_enabled(uint32_t code) +__OSX_AVAILABLE(10.12) __IOS_AVAILABLE(10.0) +__WATCHOS_AVAILABLE(3.0) __TVOS_AVAILABLE(10.0); + +#endif + +#endif /* kdebug_Private_h */ diff --git a/Sources/OpenSwiftUI/Core/Log/Log.swift b/Sources/OpenSwiftUI/Core/Log/Log.swift deleted file mode 100644 index fa09be0c..00000000 --- a/Sources/OpenSwiftUI/Core/Log/Log.swift +++ /dev/null @@ -1,79 +0,0 @@ -// -// Log.swift -// OpenSwiftUI -// -// Audited for RELEASE_2021 -// Status: Complete - -#if OPENSWIFTUI_SWIFT_LOG -import Logging -extension Logger { - init(subsystem: String, category: String) { - var logger = Logger(label: subsystem) - logger[metadataKey: "category"] = MetadataValue.string(category) - self = logger - } -} -#else -import os -#if DEBUG -public let dso = { () -> UnsafeMutableRawPointer in - let count = _dyld_image_count() - for i in 0 ..< count { - if let name = _dyld_get_image_name(i) { - let swiftString = String(cString: name) - if swiftString.hasSuffix("/SwiftUI") { - if let header = _dyld_get_image_header(i) { - return UnsafeMutableRawPointer(mutating: UnsafeRawPointer(header)) - } - } - } - } - return UnsafeMutableRawPointer(mutating: #dsohandle) -}() -#endif -#endif - -@usableFromInline -enum Log { - static func internalWarning( - _ message: @autoclosure () -> String, - file: StaticString = #file, - line: UInt = #line - ) { - print("\(message()) at \(file):\(line)") - } - - #if OPENSWIFTUI_SWIFT_LOG - @usableFromInline - static let runtimeIssuesLog = Logger(label: "OpenSwiftUI") - - @_transparent - static func runtimeIssues( - _ message: @autoclosure () -> StaticString, - _ args: @autoclosure () -> [CVarArg] = [] - ) { - runtimeIssuesLog.log(level: .critical, "\(message())") - } - #else - - // Audited for RELEASE_2023 - @usableFromInline - static var runtimeIssuesLog = OSLog(subsystem: "com.apple.runtime-issues", category: "OpenSwiftUI") - - @_transparent - static func runtimeIssues( - _ message: @autoclosure () -> StaticString, - _ args: @autoclosure () -> [CVarArg] = [] - ) { - #if DEBUG - unsafeBitCast( - os_log as (OSLogType, UnsafeRawPointer, OSLog, StaticString, CVarArg...) -> Void, - to: ((OSLogType, UnsafeRawPointer, OSLog, StaticString, [CVarArg]) -> Void).self - )(.fault, dso, runtimeIssuesLog, message(), args()) - #else - os_log(.fault, log: runtimeIssuesLog, message(), args()) - #endif - } - #endif -} diff --git a/Sources/OpenSwiftUI/Core/Log/Signpost.swift b/Sources/OpenSwiftUI/Core/Log/Signpost.swift deleted file mode 100644 index a657fdbc..00000000 --- a/Sources/OpenSwiftUI/Core/Log/Signpost.swift +++ /dev/null @@ -1,87 +0,0 @@ -// -// Signpost.swift -// OpenSwiftUI -// -// Audited for RELEASE_2021 -// Status: WIP -// ID: 34756F646CF7AC3DBE2A8E0B344C962F - -internal import OpenGraphShims -#if canImport(os) -import os.signpost -#endif - -struct Signpost { - private let style: Style - private let stability: Stability - - // TODO - var isEnabled: Bool { - switch stability { - case .disabled, .verbose, .debug: - return false - case .published: - return true - } - } - - static let render = Signpost(style: .kdebug(0), stability: .published) - static let renderUpdate = Signpost(style: .kdebug(0), stability: .published) - static let viewHost = Signpost(style: .kdebug(9), stability: .published) - static let bodyInvoke = Signpost(style: .kdebug(5), stability: .published) - - @_transparent - @inline(__always) - func traceInterval( - object: AnyObject? = nil, - _ message: StaticString?, - closure: () -> R - ) -> R { - guard isEnabled else { - return closure() - } - // TODO - return closure() - } - - @_transparent - @inline(__always) - func traceInterval( - object: AnyObject? = nil, - _ message: StaticString?, - _ arguments: @autoclosure () -> [CVarArg], - closure: () -> R - ) -> R { - guard isEnabled else { - return closure() - } - // TODO - return closure() - } -} - -extension Signpost { - private enum Style { - case kdebug(UInt8) - case os_log(StaticString) - } - - private enum Stability: Hashable { - case disabled - case verbose - case debug - case published - } -} - -@_transparent -@inline(__always) -// FIXME -func traceRuleBody(_ type: Any.Type, body: () -> R) -> R { - Signpost.bodyInvoke.traceInterval( - "%{public}@.body [in %{public}@]", - [OGTypeID(type).description, Tracing.libraryName(defining: type)] - ) { - body() - } -} diff --git a/Sources/OpenSwiftUI/Core/Update/Update.swift b/Sources/OpenSwiftUI/Core/Update/Update.swift index 8df72ee7..8f469377 100644 --- a/Sources/OpenSwiftUI/Core/Update/Update.swift +++ b/Sources/OpenSwiftUI/Core/Update/Update.swift @@ -77,7 +77,7 @@ enum Update { let actions = Update.actions Update.actions = [] - performOnMainThread { + onMainThread { // TODO: Signpost.postUpdateActions begin() for action in actions { diff --git a/Sources/OpenSwiftUI/Core/Util/Tracing.swift b/Sources/OpenSwiftUI/Core/Util/DescriptiveDynamicProperty.swift similarity index 77% rename from Sources/OpenSwiftUI/Core/Util/Tracing.swift rename to Sources/OpenSwiftUI/Core/Util/DescriptiveDynamicProperty.swift index 67c6f2f0..51a296ef 100644 --- a/Sources/OpenSwiftUI/Core/Util/Tracing.swift +++ b/Sources/OpenSwiftUI/Core/Util/DescriptiveDynamicProperty.swift @@ -1,20 +1,9 @@ // -// Tracing.swift +// DescriptiveDynamicProperty.swift // OpenSwiftUI // -// Audited for RELEASE_2021 -// Status: WIP -// ID: D59B7A281FFF29619A43A3D8F551CCE1 - -// MARK: - Tracing - -enum Tracing { - static func libraryName(defining _: Any.Type) -> String { - // TODO: - "" - } -// private static moduleLookupCache: ThreadSpecific<[UnsafeRawPointer : String]> -} +// Created by Kyle on 2024/9/22. +// // MARK: - DescriptiveDynamicProperty diff --git a/Sources/OpenSwiftUI/Core/Util/ThreadUtils.swift b/Sources/OpenSwiftUI/Core/Util/ThreadUtils.swift deleted file mode 100644 index 9c224b6c..00000000 --- a/Sources/OpenSwiftUI/Core/Util/ThreadUtils.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// ThreadUtils.swift -// -// -// Created by Kyle on 2024/4/21. -// - -import Foundation - -@inline(__always) -func performOnMainThread(_ block: @escaping () -> Void) { - #if os(WASI) - // See #76: Thread and RunLoopMode.common is not available on WASI currently - block() - #else - if Thread.isMainThread { - block() - } else { - RunLoop.main.perform(inModes: [.common], block: block) - } - #endif -} diff --git a/Sources/OpenSwiftUI/Data/Model/DynamicProperty/DynamicPropertyBuffer.swift b/Sources/OpenSwiftUI/Data/Model/DynamicProperty/DynamicPropertyBuffer.swift index 7b7433df..d3f99412 100644 --- a/Sources/OpenSwiftUI/Data/Model/DynamicProperty/DynamicPropertyBuffer.swift +++ b/Sources/OpenSwiftUI/Data/Model/DynamicProperty/DynamicPropertyBuffer.swift @@ -95,7 +95,7 @@ public struct _DynamicPropertyBuffer { } func destroy() { - precondition(_count >= 0) + Swift.precondition(_count >= 0) var count = _count var pointer = buf while count > 0 { @@ -112,7 +112,7 @@ public struct _DynamicPropertyBuffer { } func reset() { - precondition(_count >= 0) + Swift.precondition(_count >= 0) var count = _count var pointer = buf while count > 0 { @@ -125,7 +125,7 @@ public struct _DynamicPropertyBuffer { } func getState(type: Value.Type) -> Binding? { - precondition(_count >= 0) + Swift.precondition(_count >= 0) var count = _count var pointer = buf while count > 0 { @@ -141,7 +141,7 @@ public struct _DynamicPropertyBuffer { } func update(container: UnsafeMutableRawPointer, phase: _GraphInputs.Phase) -> Bool { - precondition(_count >= 0) + Swift.precondition(_count >= 0) var changed = false var count = _count var pointer = buf @@ -163,7 +163,7 @@ public struct _DynamicPropertyBuffer { } private mutating func allocate(bytes: Int) -> UnsafeMutableRawPointer { - precondition(_count >= 0) + Swift.precondition(_count >= 0) var count = _count var pointer = buf while count > 0 { diff --git a/Sources/OpenSwiftUI/Data/Model/State/StoredLocation.swift b/Sources/OpenSwiftUI/Data/Model/State/StoredLocation.swift index cff04332..0a1cef5a 100644 --- a/Sources/OpenSwiftUI/Data/Model/State/StoredLocation.swift +++ b/Sources/OpenSwiftUI/Data/Model/State/StoredLocation.swift @@ -107,7 +107,7 @@ class StoredLocationBase: AnyLocation, Location { } var newTransaction = transaction newTransaction.override(.current) - performOnMainThread { [weak self] in + onMainThread { [weak self] in guard let self else { return } diff --git a/Sources/OpenSwiftUICore/Log/Logging.swift b/Sources/OpenSwiftUICore/Log/Logging.swift new file mode 100644 index 00000000..e1e58610 --- /dev/null +++ b/Sources/OpenSwiftUICore/Log/Logging.swift @@ -0,0 +1,187 @@ +// +// Logging.swift +// OpenSwiftUI +// +// Audited for RELEASE_2024 +// Status: Complete + +import Foundation +#if OPENSWIFTUI_SWIFT_LOG +import Logging +extension Logger { + package init(subsystem: String, category: String) { + var logger = Logger(label: subsystem) + logger[metadataKey: "category"] = MetadataValue.string(category) + self = logger + } +} +#else +import os.log + +#if DEBUG +package let dso = { () -> UnsafeMutableRawPointer in + let count = _dyld_image_count() + for i in 0 ..< count { + if let name = _dyld_get_image_name(i) { + let swiftString = String(cString: name) + if swiftString.hasSuffix("/SwiftUI") { + if let header = _dyld_get_image_header(i) { + return UnsafeMutableRawPointer(mutating: UnsafeRawPointer(header)) + } + } + } + } + return UnsafeMutableRawPointer(mutating: #dsohandle) +}() +#endif + +#endif + +@usableFromInline +package enum Log { + package static let subsystem: String = "org.OpenSwiftUIProject.OpenSwiftUI" + + @inline(__always) + package static func log(_ message: @autoclosure () -> String, unless condition: @autoclosure () -> Bool, file: StaticString, line: UInt) { + guard !condition() else { return } + #if OPENSWIFTUI_SWIFT_LOG + internalErrorsLog.debug("\(message()) \(file) \(line)") + #else + #if DEBUG && OPENSWIFTUI_SUPPORT_2022_API + os_log(.default, log: internalErrorsLog, "%s %s: %s", message(), file.description, line.description) + #endif + #endif + } + + @inline(__always) + package static func log(_ message: @autoclosure () -> String, unless condition: @autoclosure () -> Bool, file: StaticString = #fileID) { + log(message(), unless: condition(), file: file, line: #line) + } + + @inline(__always) + package static func log(_ message: @autoclosure () -> String, unless condition: @autoclosure () -> Bool) { + log(message(), unless: condition(), file: #fileID) + } + + @inline(__always) + package static func log(_ message: @autoclosure () -> String) { + log(message(), unless: false) + } + + package static func internalWarning(_ message: @autoclosure () -> String, file: StaticString, line: UInt) { + print("\(message()) - \(file): - please file a bug report") + } + + package static func internalWarning(_ message: @autoclosure () -> String) { + internalWarning(message(), file: #fileID, line: #line) + } + + package static func internalError(_ message: @autoclosure () -> String, file: StaticString = #fileID, line: UInt = #line) { + #if OPENSWIFTUI_SWIFT_LOG + internalErrorsLog.log(level: .error, "\(message()) - \(file): - please file a bug report") + #else + #if OPENSWIFTUI_SUPPORT_2022_API + os_log(.fault, log: internalErrorsLog, "%s %s: %s", message(), file.description, line.description) + #endif + print("\(message()) - \(file): - please file a bug report") + #endif + } + + package static func internalError(_ message: @autoclosure () -> String) { + internalError(message(), file: #fileID, line: #line) + } + + package static func externalWarning(_ message: String) { + #if OPENSWIFTUI_SWIFT_LOG + unlocatedIssuesLog.log(level: .critical, "\(message)") + #else + unlocatedIssuesLog.fault("\(message)") + #endif + } + + package static func eventDebug(_ message: String) { + #if !OPENSWIFTUI_SWIFT_LOG + os_log(log: eventDebuggingLog, "\(message)") + #endif + } + + #if OPENSWIFTUI_SWIFT_LOG + @usableFromInline + package static var runtimeIssuesLog = Logger(subsystem: "com.apple.runtime-issues", category: "OpenSwiftUI") + + @_transparent + package static func runtimeIssues( + _ message: @autoclosure () -> StaticString, + _ args: @autoclosure () -> [CVarArg] = [] + ) { + runtimeIssuesLog.log(level: .critical, "\(message())") + } + #else + @usableFromInline + package static var runtimeIssuesLog: OSLog = OSLog(subsystem: "com.apple.runtime-issues", category: "OpenSwiftUI") + + @_transparent + package static func runtimeIssues( + _ message: @autoclosure () -> StaticString, + _ args: @autoclosure () -> [CVarArg] = [] + ) { + #if DEBUG + unsafeBitCast( + os_log as (OSLogType, UnsafeRawPointer, OSLog, StaticString, CVarArg...) -> Void, + to: ((OSLogType, UnsafeRawPointer, OSLog, StaticString, [CVarArg]) -> Void).self + )(.fault, dso, runtimeIssuesLog, message(), args()) + #else + os_log(.fault, log: runtimeIssuesLog, message(), args()) + #endif + } + + #endif + package static let propertyChangeLog: Logger = Logger(subsystem: subsystem, category: "Changed Body Properties") + package static var unlocatedIssuesLog: Logger = Logger(subsystem: subsystem, category: "Invalid Configuration") + + #if OPENSWIFTUI_SWIFT_LOG + @usableFromInline + package static var internalErrorsLog: Logger = Logger(subsystem: subsystem, category: "OpenSwiftUI") + #else + #if OPENSWIFTUI_SUPPORT_2022_API + @usableFromInline + package static var internalErrorsLog: OSLog = OSLog(subsystem: subsystem, category: "OpenSwiftUI") + #endif + + @usableFromInline + package static var eventDebuggingLog: OSLog = OSLog(subsystem: "com.apple.diagnostics.events", category: "OpenSwiftUI") + #endif + + package static let archiving: Logger = Logger(subsystem: subsystem, category: "Archiving") + package static let archivedToggle: Logger = Logger(subsystem: subsystem, category: "ArchivedToggle") + package static let archivedButton: Logger = Logger(subsystem: subsystem, category: "ArchivedButton") + package static let archivedPlaybackButton: Logger = Logger(subsystem: subsystem, category: "ArchivedPlaybackButton") + package static let metadataExtraction: Logger = Logger(subsystem: subsystem, category: "MetadataExtraction") +} + +@available(*, unavailable) +extension Log: Sendable {} + +@_transparent +package func precondition(_ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String, file: StaticString = #fileID, line: UInt = #line) { + guard condition() else { + Swift.preconditionFailure(message(), file: file, line: line) + } +} + +@_transparent +package func preconditionFailure(_ message: @autoclosure () -> String, file: StaticString, line: UInt) -> Never { + Swift.preconditionFailure(message(), file: file, line: line) +} + +@_transparent +package func preconditionFailure(_ message: @autoclosure () -> String) -> Never { + preconditionFailure(message(), file: #fileID, line: #line) +} + +#if !OPENSWIFTUI_SWIFT_LOG +extension os.OSLog { + @usableFromInline + static var runtimeIssuesLog: os.OSLog = OSLog(subsystem: "com.apple.runtime-issues", category: "OpenSwiftUI") +} +#endif diff --git a/Sources/OpenSwiftUICore/Log/Signpost.swift b/Sources/OpenSwiftUICore/Log/Signpost.swift new file mode 100644 index 00000000..69128623 --- /dev/null +++ b/Sources/OpenSwiftUICore/Log/Signpost.swift @@ -0,0 +1,229 @@ +// +// Signpost.swift +// OpenSwiftUI +// +// Audited for RELEASE_2021 +// Status: WIP +// ID: 34756F646CF7AC3DBE2A8E0B344C962F (RELEASE_2021) +// ID: 59349949219F590F26B6A55CEC9D59A2 (RELEASE_2024) + +internal import COpenSwiftUICore +internal import OpenGraphShims +#if canImport(Darwin) +import os.signpost +#endif + +extension Signpost { + package static let render = Signpost.kdebug(0, "Render") + package static let postUpdateActions = Signpost.kdebug(2, "PostUpdateActions") + package static let renderUpdate = Signpost.kdebug(3, "RenderUpdate") + package static let renderFlattened = Signpost.kdebug(4, "RenderFlattened") + package static let bodyInvoke = Signpost.kdebug(5, "BodyInvoke") + package static let linkCreate = Signpost.os_log(6, "LinkCreate") + package static let linkUpdate = Signpost.os_log(7, "LinkUpdate") + package static let linkDestroy = Signpost.os_log(8, "LinkDestroy") + package static let viewHost = Signpost.kdebug(9, "ViewHost") + package static let platformView = Signpost.os_log(10, "ViewMapping") + package static let platformUpdate = Signpost.os_log(11, "PlatformViewUpdate") + package static let animationState = Signpost.os_log(12, "AnimationState") + package static let eventHandling = Signpost.os_log(13, "EventHandling") +} + +#if canImport(Darwin) +private let _signpostLog = OSLog(subsystem: Log.subsystem, category: "OpenSwiftUI") +#endif + +package struct Signpost { + #if canImport(Darwin) + package static let archiving = OSSignposter(logger: Log.archiving) + package static let metaExtraction = OSSignposter(logger: Log.metadataExtraction) + #endif + + package static let moduleName: String = Tracing.libraryName(defining: Signpost.self) + + @inlinable + package static func os_log(_: UInt8, _ name: StaticString) -> Signpost { + Signpost(style: .os_log(name), stability: .debug) + } + + @inlinable + package static func kdebug(_ code: UInt8, _: StaticString?) -> Signpost { + Signpost(style: .kdebug(code), stability: .debug) + } + + package static func kdebug(_ code: UInt8) -> Signpost { + Signpost(style: .kdebug(code), stability: .debug) + } + + private enum Style { + case kdebug(UInt8) + case os_log(StaticString) + } + + private enum Stability: Hashable { + case disabled + case verbose + case debug + case published + + @inline(__always) + static var valid: [Stability] { + #if DEBUG + [.debug, .published] + #else + [.published] + #endif + } + } + + private let style: Style + private let stability: Stability + + @inlinable + package var disabled: Signpost { + Signpost(style: style, stability: .disabled) + } + + @inlinable + package var verbose: Signpost { + Signpost(style: style, stability: .verbose) + } + + @inlinable + package var published: Signpost { + Signpost(style: style, stability: .published) + } + + package var isEnabled: Bool { + guard Stability.valid.contains(where: { $0 == stability }) else { + return false + } + #if canImport(Darwin) + switch style { + case let .kdebug(code): + return kdebug_is_enabled(UInt32(OSSignpostType.event.rawValue & 0xfc) | (UInt32(code) << 2) & 0x3fc | 0x14110000) + case .os_log: + guard kdebug_is_enabled(UInt32(OSSignpostType.event.rawValue & 0xfc) | 0x14110000) else { + return false + } + return _signpostLog.signpostsEnabled + } + #else + return true + #endif + } + + // TODO + @_transparent + package func traceInterval( + object: AnyObject?, + _ message: StaticString?, + closure: () -> T + ) -> T { + guard isEnabled else { + return closure() + } + #if canImport(Darwin) + let id = OSSignpostID.makeExclusiveID(object) + switch style { + case let .kdebug(code): + kdebug_trace(UInt32(code) << 2 | (UInt32(OSSignpostType.begin.rawValue & 0xfc) | 0x14110000), id.rawValue, 0, 0, 0) + defer { kdebug_trace(UInt32(code) << 2 | (UInt32(OSSignpostType.end.rawValue & 0xfc) | 0x14110000), id.rawValue, 0, 0, 0) } + return closure() + case let .os_log(name): + if let message { + os_signpost(.begin, log: _signpostLog, name: name, signpostID: id, message, []) + } else { + os_signpost(.begin, log: _signpostLog, name: name, signpostID: id) + } + defer { os_signpost(.end, log: _signpostLog, name: name, signpostID: id) } + return closure() + } + #else + return closure() + #endif + } + + @_transparent + package func traceInterval( + object: AnyObject? = nil, + _ message: StaticString, + _ args: @autoclosure () -> [any CVarArg], + closure: () -> T + ) -> T { + guard isEnabled else { + return closure() + } + #if canImport(Darwin) + let id = OSSignpostID.makeExclusiveID(object) + switch style { + case let .kdebug(code): + // FIXME: _primitive + print(code) + return closure() + case let .os_log(name): + os_signpost(.begin, log: _signpostLog, name: name, signpostID: id, message, args()) + defer { os_signpost(.end, log: _signpostLog, name: name, signpostID: id) } + return closure() + } + #else + return closure() + #endif + } + + #if canImport(Darwin) + @_transparent + package func traceEvent( + type: OSSignpostType, + object: AnyObject?, + _ message: StaticString, + _ args: @autoclosure () -> [any CVarArg] + ) { + guard isEnabled else { + return + } + let id = OSSignpostID.makeExclusiveID(object) + let args = args() + + switch style { + case let .kdebug(code): + // FIXME: _primitive + print(code) + return + case let .os_log(name): + os_signpost(type, log: _signpostLog, name: name, signpostID: id, message, args) + } + } + #endif + + #if canImport(Darwin) + private func _primitive( + _ type: OSSignpostType, + log: OSLog, + signpostID: OSSignpostID, + _ message: StaticString?, + _ arguments: [any CVarArg]? + ) { + // TODO + } + #endif +} + +#if canImport(Darwin) +extension OSSignpostID { + private static let continuation = OSSignpostID(0x0ea89ce2) + + @inline(__always) + static func makeExclusiveID(_ object: AnyObject?) -> OSSignpostID { + if let object { + OSSignpostID(log: _signpostLog, object: object) + } else { + .exclusive + } + } +} +#endif + +private func withKDebugValues(_ code: UInt32, _ args: [(any CVarArg)?], closure: (([UInt64]) -> Void)) { + // TODO +} diff --git a/Sources/OpenSwiftUICore/Util/ThreadUtils.swift b/Sources/OpenSwiftUICore/Util/ThreadUtils.swift new file mode 100644 index 00000000..e08416f4 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/ThreadUtils.swift @@ -0,0 +1,76 @@ +// +// ThreadUtils.swift +// OpenSwiftUICore +// +// Audited for RELEASE_2024 +// Status: Complete + +import Foundation + +final package class ThreadSpecific { + var key: pthread_key_t + let defaultValue: T + + package init(_ defaultValue: T) { + key = 0 + self.defaultValue = defaultValue + pthread_key_create(&key) { pointer in + #if !canImport(Darwin) + guard let pointer else { return } + #endif + pointer.withMemoryRebound(to: Any.self, capacity: 1) { ptr in + ptr.deinitialize(count: 1) + ptr.deallocate() + } + } + } + + deinit { + preconditionFailure("\(Self.self).deinit is unsafe and would leak", file: #file, line: #line) + } + + private final var box: UnsafeMutablePointer { + let pointer = pthread_getspecific(key) + if let pointer { + return pointer.assumingMemoryBound(to: Any.self) + } else { + let box = UnsafeMutablePointer.allocate(capacity: 1) + pthread_setspecific(key, box) + box.initialize(to: defaultValue) + return box + } + } + + final package var value: T { + get { + box.pointee as! T + } + set { + box.withMemoryRebound(to: T.self, capacity: 1) { $0.pointee = newValue } + } + } +} + +package func onMainThread(do body: @escaping () -> Void) { + #if os(WASI) + // See #76: Thread and RunLoopMode.common is not available on WASI currently + body() + #else + if Thread.isMainThread { + body() + } else { + RunLoop.main.perform(inModes: [.common]) { + // Workaround the @Senable warning + body() + } + } + #endif +} + +package func mainThreadPrecondition() { + #if !os(WASI) + guard Thread.isMainThread else { + fatalError("calling into OpenSwiftUI on a non-main thread is not supported") + } + #endif +} diff --git a/Sources/OpenSwiftUICore/Util/Tracing.swift b/Sources/OpenSwiftUICore/Util/Tracing.swift new file mode 100644 index 00000000..7e71fd52 --- /dev/null +++ b/Sources/OpenSwiftUICore/Util/Tracing.swift @@ -0,0 +1,80 @@ +// +// Tracing.swift +// OpenSwiftUICore +// +// Audited for RELEASE_2024 +// Status: WIP +// ID: D59B7A281FFF29619A43A3D8F551CCE1 (RELEASE_2021) +// ID: 56D4CED87D5B226E2B40FB60C47D6F49 (RELEASE_2024) + +#if canImport(Darwin) +import Darwin +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(WASI) +import WASILibc +#endif +internal import OpenGraphShims +import Foundation +internal import COpenSwiftUICore +#if canImport(Darwin) +import os.log +#endif + +enum Tracing { + private static var moduleLookupCache = ThreadSpecific<[UnsafeRawPointer: String]>([:]) + + static func libraryName(defining type: Any.Type) -> String { + let unknown = "ModuleUnknown" + guard let nominalDescriptor = OGTypeID(type).nominalDescriptor else { + return unknown + } + if let cachedName = moduleLookupCache.value[nominalDescriptor] { + return cachedName + } else { + #if canImport(Darwin) + var info = Dl_info() + guard dladdr(nominalDescriptor, &info) != 0 else { + return unknown + } + let name = (String(cString: info.dli_fname) as NSString).lastPathComponent + moduleLookupCache.value[nominalDescriptor] = name + return name + #else + // TODO: [Easy] Add a C layer to import dladdr on non-Darwin Swift platform + // See https://forums.swift.org/t/dladdr-and-the-clang-importer/26379/11 + return unknown + #endif + } + } + + static func nominalTypeName(_ type: Any.Type) -> String { + OGTypeID(type).description + } +} + +@_transparent +package func traceBody(_ v: any Any.Type, body: () -> Body) -> Body { + #if canImport(Darwin) + // Signpost.bodyInvoke.traceInterval + guard kdebug_is_enabled(UInt32(OSSignpostType.event.rawValue) & 0xF8 | 0x1411_0014) else { + return body() + } + // TODO: OGTypeID(type).description, Tracing.libraryName(defining: v) + return body() + #else + body() + #endif +} + +@_transparent +package func traceRuleBody(_ v: any Any.Type, body: () -> Body) -> Body { + #if canImport(Darwin) + // TODO: + return body() + #else + body() + #endif +} diff --git a/Tests/OpenSwiftUICoreTests/Util/ThreadUtilsTests.swift b/Tests/OpenSwiftUICoreTests/Util/ThreadUtilsTests.swift new file mode 100644 index 00000000..cb5788db --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/ThreadUtilsTests.swift @@ -0,0 +1,38 @@ +// +// ThreadUtilsTests.swift +// OpenSwiftUICoreTests + +import OpenSwiftUICore +import Testing + +struct ThreadUtilsTests { + static let defaultValue: Int = 1 + static let box = ThreadSpecific(defaultValue) + + @Test + func value() async throws { + let box = ThreadUtilsTests.box + #expect(box.value == ThreadUtilsTests.defaultValue) + try await withThrowingTaskGroup(of: Int.self) { group in + group.addTask { + await Task.detached { + box.value = 3 + #expect(box.value == 3) + return box.value + }.value + } + group.addTask { + await Task.detached { + box.value = 4 + #expect(box.value == 4) + return box.value + }.value + } + let result = try await group.reduce(0, +) + #expect(result == 7) + await MainActor.run { + #expect(box.value == ThreadUtilsTests.defaultValue) + } + } + } +} diff --git a/Tests/OpenSwiftUICoreTests/Util/TracingTests.swift b/Tests/OpenSwiftUICoreTests/Util/TracingTests.swift new file mode 100644 index 00000000..f4655af2 --- /dev/null +++ b/Tests/OpenSwiftUICoreTests/Util/TracingTests.swift @@ -0,0 +1,36 @@ +// +// TracingTests.swift +// OpenSwiftUICoreTests + +@testable import OpenSwiftUICore +import OpenGraphShims +import Testing + +@Suite(.disabled(if: !attributeGraphEnabled, "Not implemented in OG yet")) +struct TracingTests { + struct Demo {} + + @Test( + .disabled(if: !attributeGraphEnabled, "OGTypeDescription is not implemented yet"), + arguments: [ + (type: Int.self as Any.Type, nominalName: "Int"), + (type: String.self as Any.Type, nominalName: "String"), + (type: Demo.self as Any.Type, nominalName: "TracingTests.Demo"), + ] + ) + func name(type: Any.Type, nominalName: String) { + #expect(Tracing.nominalTypeName(type) == nominalName) + } + + @Test( + .disabled(if: !attributeGraphEnabled, "OGTypeNominalDescriptor is not implemented yet"), + arguments: [ + (type: Int.self as Any.Type, libraryName: "libswiftCore.dylib"), + (type: String.self as Any.Type, libraryName: "libswiftCore.dylib"), + (type: Demo.self as Any.Type, libraryName: "OpenSwiftUICoreTests"), + ] + ) + func library(type: Any.Type, libraryName: String) { + #expect(Tracing.libraryName(defining: type) == libraryName) + } +}