From 1b2e236d95ce3cc1d4e0c2f15d3a246996e7a952 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 18 Mar 2024 13:57:44 -0400 Subject: [PATCH 1/3] Add partial WASI/WASM support. This PR allows building using the SwiftWasm toolchain from https://swiftwasm.org. As of right now, macro plugins do not build correctly for WASM, so swift-testing is not usable, but it is buildable! ## Testing A workaround is provided on the [jgrynspan/wasi-workaround](https://github.com/apple/swift-testing/tree/jgrynspan/wasi-workaround) branch in order to facilitate testing. Check out that branch, then set the environment variable `SWT_COMPILER_PLUGIN_ENABLED` to `0` when building: ```sh SWT_COMPILER_PLUGIN_ENABLED=0 swift build --triple wasm32-unknown-wasi ``` The macros target should build except for its main function (which will emit a warning about being unsupported) as should the primary library target. It is not possible to build the testing library's tests yet. --- Sources/Testing/Events/Recorder/Event.Symbol.swift | 2 +- Sources/Testing/Events/TimeValue.swift | 5 +++++ Sources/Testing/SourceAttribution/Backtrace.swift | 4 ++++ Sources/Testing/Support/Environment.swift | 2 +- Sources/Testing/Support/FileHandle.swift | 2 +- Sources/Testing/Support/Locked.swift | 8 ++++---- Sources/Testing/Support/Versions.swift | 2 +- Sources/TestingInternals/Discovery.cpp | 2 +- Tests/TestingTests/Support/EnvironmentTests.swift | 2 +- 9 files changed, 19 insertions(+), 10 deletions(-) diff --git a/Sources/Testing/Events/Recorder/Event.Symbol.swift b/Sources/Testing/Events/Recorder/Event.Symbol.swift index 719c3e795..10f0fbf93 100644 --- a/Sources/Testing/Events/Recorder/Event.Symbol.swift +++ b/Sources/Testing/Events/Recorder/Event.Symbol.swift @@ -100,7 +100,7 @@ extension Event.Symbol { /// be used to represent it in text-based output. The value of this property /// is platform-dependent. public var unicodeCharacter: Character { -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) switch self { case .default: // Unicode: WHITE DIAMOND diff --git a/Sources/Testing/Events/TimeValue.swift b/Sources/Testing/Events/TimeValue.swift index f798a78ef..033aa9f86 100644 --- a/Sources/Testing/Events/TimeValue.swift +++ b/Sources/Testing/Events/TimeValue.swift @@ -75,6 +75,10 @@ extension TimeValue: Codable {} extension TimeValue: CustomStringConvertible { var description: String { +#if os(WASI) + // BUG: https://github.com/apple/swift/issues/72398 + return String(describing: Duration(self)) +#else let (secondsFromAttoseconds, attosecondsRemaining) = attoseconds.quotientAndRemainder(dividingBy: 1_000_000_000_000_000_000) let seconds = seconds + secondsFromAttoseconds var milliseconds = attosecondsRemaining / 1_000_000_000_000_000 @@ -88,6 +92,7 @@ extension TimeValue: CustomStringConvertible { } return String(cString: buffer.baseAddress!) } +#endif } } diff --git a/Sources/Testing/SourceAttribution/Backtrace.swift b/Sources/Testing/SourceAttribution/Backtrace.swift index 3d9977c76..df8834477 100644 --- a/Sources/Testing/SourceAttribution/Backtrace.swift +++ b/Sources/Testing/SourceAttribution/Backtrace.swift @@ -72,6 +72,10 @@ public struct Backtrace: Sendable { initializedCount = .init(backtrace(addresses.baseAddress!, .init(addresses.count))) #elseif os(Windows) initializedCount = Int(RtlCaptureStackBackTrace(0, ULONG(addresses.count), addresses.baseAddress!, nil)) +#elseif os(WASI) + // SEE: https://github.com/WebAssembly/WASI/issues/159 + // SEE: https://github.com/apple/swift/pull/31693 + initializedCount = 0 #else #warning("Platform-specific implementation missing: backtraces unavailable") initializedCount = 0 diff --git a/Sources/Testing/Support/Environment.swift b/Sources/Testing/Support/Environment.swift index fb01d95bc..d9fa050b3 100644 --- a/Sources/Testing/Support/Environment.swift +++ b/Sources/Testing/Support/Environment.swift @@ -36,7 +36,7 @@ enum Environment { static func variable(named name: String) -> String? { #if SWT_NO_ENVIRONMENT_VARIABLES simulatedEnvironment.rawValue[name] -#elseif SWT_TARGET_OS_APPLE || os(Linux) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) getenv(name).flatMap { String(validatingUTF8: $0) } #elseif os(Windows) name.withCString(encodedAs: UTF16.self) { name in diff --git a/Sources/Testing/Support/FileHandle.swift b/Sources/Testing/Support/FileHandle.swift index 247ada218..adab81981 100644 --- a/Sources/Testing/Support/FileHandle.swift +++ b/Sources/Testing/Support/FileHandle.swift @@ -140,7 +140,7 @@ struct FileHandle: ~Copyable, Sendable { let fd: CInt = -1 #endif - if fd >= 0 { + if Bool(fd >= 0) { return try body(fd) } return try body(nil) diff --git a/Sources/Testing/Support/Locked.swift b/Sources/Testing/Support/Locked.swift index 5c9fddde7..ab0fb69e6 100644 --- a/Sources/Testing/Support/Locked.swift +++ b/Sources/Testing/Support/Locked.swift @@ -36,7 +36,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { /// To keep the implementation of this type as simple as possible, /// `pthread_mutex_t` is used on Apple platforms instead of `os_unfair_lock` /// or `OSAllocatedUnfairLock`. -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) private typealias _Lock = pthread_mutex_t #elseif os(Windows) private typealias _Lock = SRWLOCK @@ -49,7 +49,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { private final class _Storage: ManagedBuffer { deinit { withUnsafeMutablePointerToElements { lock in -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) _ = pthread_mutex_destroy(lock) #elseif os(Windows) // No deinitialization needed. @@ -66,7 +66,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { init(rawValue: T) { let storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue }) storage.withUnsafeMutablePointerToElements { lock in -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) _ = pthread_mutex_init(lock, nil) #elseif os(Windows) InitializeSRWLock(lock) @@ -95,7 +95,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { /// concurrency tools. nonmutating func withLock(_ body: (inout T) throws -> R) rethrows -> R { try _storage.rawValue.withUnsafeMutablePointers { rawValue, lock in -#if SWT_TARGET_OS_APPLE || os(Linux) +#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) _ = pthread_mutex_lock(lock) defer { _ = pthread_mutex_unlock(lock) diff --git a/Sources/Testing/Support/Versions.swift b/Sources/Testing/Support/Versions.swift index 8c4251bc4..5fe79f02e 100644 --- a/Sources/Testing/Support/Versions.swift +++ b/Sources/Testing/Support/Versions.swift @@ -31,7 +31,7 @@ let operatingSystemVersion: String = { default: return "\(productVersion) (\(buildNumber))" } -#elseif !SWT_NO_UNAME && (SWT_TARGET_OS_APPLE || os(Linux)) +#elseif !SWT_NO_UNAME && (SWT_TARGET_OS_APPLE || os(Linux) || os(WASI)) var name = utsname() if 0 == uname(&name) { let release = withUnsafeBytes(of: name.release) { release in diff --git a/Sources/TestingInternals/Discovery.cpp b/Sources/TestingInternals/Discovery.cpp index bba05ffdc..60d482993 100644 --- a/Sources/TestingInternals/Discovery.cpp +++ b/Sources/TestingInternals/Discovery.cpp @@ -260,7 +260,7 @@ static void enumerateTypeMetadataSections(const SectionEnumerator& body) { } } -#elif defined(__linux__) || defined(_WIN32) +#elif defined(__linux__) || defined(_WIN32) || defined(__wasi__) #pragma mark - Linux/Windows implementation /// Specifies the address range corresponding to a section. diff --git a/Tests/TestingTests/Support/EnvironmentTests.swift b/Tests/TestingTests/Support/EnvironmentTests.swift index b25d4528c..66e1c838a 100644 --- a/Tests/TestingTests/Support/EnvironmentTests.swift +++ b/Tests/TestingTests/Support/EnvironmentTests.swift @@ -79,7 +79,7 @@ extension Environment { environment[name] = value } return true -#elseif SWT_TARGET_OS_APPLE || os(Linux) +#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) if let value { return 0 == setenv(name, value, 1) } From 7da13cde33a37ce499bbe567abba807d326decfd Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Mon, 18 Mar 2024 14:27:36 -0400 Subject: [PATCH 2/3] Disable file I/O API on WASI --- Package.swift | 2 ++ Package@swift-6.0.swift | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Package.swift b/Package.swift index 40e28c845..52aa87831 100644 --- a/Package.swift +++ b/Package.swift @@ -123,6 +123,8 @@ extension Array where Element == PackageDescription.SwiftSetting { .enableUpcomingFeature("InternalImportsByDefault"), .define("SWT_TARGET_OS_APPLE", .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])), + + .define("SWT_NO_FILE_IO", .when(platforms: [.wasi])), ] } diff --git a/Package@swift-6.0.swift b/Package@swift-6.0.swift index 71b1fd1e7..a2006b1e6 100644 --- a/Package@swift-6.0.swift +++ b/Package@swift-6.0.swift @@ -123,6 +123,8 @@ extension Array where Element == PackageDescription.SwiftSetting { .enableUpcomingFeature("InternalImportsByDefault"), .define("SWT_TARGET_OS_APPLE", .when(platforms: [.macOS, .iOS, .macCatalyst, .watchOS, .tvOS, .visionOS])), + + .define("SWT_NO_FILE_IO", .when(platforms: [.wasi])), ] } From 68f33ab6cc58a6b3a4845d94ec0d81207cd6b5d6 Mon Sep 17 00:00:00 2001 From: Jonathan Grynspan Date: Tue, 19 Mar 2024 11:33:53 -0400 Subject: [PATCH 3/3] Make Locked a no-op on WASI for now --- Sources/Testing/Support/Locked.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/Testing/Support/Locked.swift b/Sources/Testing/Support/Locked.swift index ab0fb69e6..05bef558a 100644 --- a/Sources/Testing/Support/Locked.swift +++ b/Sources/Testing/Support/Locked.swift @@ -36,10 +36,12 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { /// To keep the implementation of this type as simple as possible, /// `pthread_mutex_t` is used on Apple platforms instead of `os_unfair_lock` /// or `OSAllocatedUnfairLock`. -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) private typealias _Lock = pthread_mutex_t #elseif os(Windows) private typealias _Lock = SRWLOCK +#elseif os(WASI) + // No locks on WASI. #else #warning("Platform-specific implementation missing: locking unavailable") private typealias _Lock = Void @@ -49,10 +51,12 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { private final class _Storage: ManagedBuffer { deinit { withUnsafeMutablePointerToElements { lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) _ = pthread_mutex_destroy(lock) #elseif os(Windows) // No deinitialization needed. +#elseif os(WASI) + // No locks on WASI. #else #warning("Platform-specific implementation missing: locking unavailable") #endif @@ -66,10 +70,12 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { init(rawValue: T) { let storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue }) storage.withUnsafeMutablePointerToElements { lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) _ = pthread_mutex_init(lock, nil) #elseif os(Windows) InitializeSRWLock(lock) +#elseif os(WASI) + // No locks on WASI. #else #warning("Platform-specific implementation missing: locking unavailable") #endif @@ -95,7 +101,7 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { /// concurrency tools. nonmutating func withLock(_ body: (inout T) throws -> R) rethrows -> R { try _storage.rawValue.withUnsafeMutablePointers { rawValue, lock in -#if SWT_TARGET_OS_APPLE || os(Linux) || os(WASI) +#if SWT_TARGET_OS_APPLE || os(Linux) _ = pthread_mutex_lock(lock) defer { _ = pthread_mutex_unlock(lock) @@ -105,6 +111,8 @@ struct Locked: RawRepresentable, Sendable where T: Sendable { defer { ReleaseSRWLockExclusive(lock) } +#elseif os(WASI) + // No locks on WASI. #else #warning("Platform-specific implementation missing: locking unavailable") #endif