diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7d657fc6..82866d95 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -56,8 +56,6 @@ jobs: steps: - uses: actions/checkout@v3 - with: - submodules: recursive - id: setup-swiftwasm uses: swiftwasm/setup-swiftwasm@v1 with: @@ -75,14 +73,22 @@ jobs: - run: ./CI/check-spectest.sh - run: ./CI/check-wasi-testsuite.sh + build-windows: + runs-on: windows-latest + steps: + - uses: compnerd/gha-setup-swift@main + with: + branch: swift-5.10.1-release + tag: 5.10.1-RELEASE + - uses: actions/checkout@v4 + - run: swift test + build-cmake: runs-on: ubuntu-20.04 container: image: swift:5.8-focal steps: - uses: actions/checkout@v4 - with: - submodules: recursive - name: Install Ninja run: apt-get update && apt-get install -y ninja-build - name: Install CMake diff --git a/Package.swift b/Package.swift index 56e78074..3907e35e 100644 --- a/Package.swift +++ b/Package.swift @@ -15,14 +15,6 @@ let package = Package( name: "WasmParser", targets: ["WasmParser"] ), - .library( - name: "WasmKitWASI", - targets: ["WasmKitWASI"] - ), - .library( - name: "WASI", - targets: ["WASI"] - ), .library( name: "WIT", targets: ["WIT"] ), @@ -31,15 +23,12 @@ let package = Package( targets: ["CLI"] ), .library(name: "_CabiShims", targets: ["_CabiShims"]), - .plugin(name: "WITOverlayPlugin", targets: ["WITOverlayPlugin"]), - .plugin(name: "WITExtractorPlugin", targets: ["WITExtractorPlugin"]), ], targets: [ .executableTarget( name: "CLI", dependencies: [ "WasmKit", - "WasmKitWASI", .product(name: "ArgumentParser", package: "swift-argument-parser"), .product(name: "SystemPackage", package: "swift-system"), ], @@ -49,16 +38,10 @@ let package = Package( name: "WasmTypes", exclude: ["CMakeLists.txt"] ), - .target( - name: "WASI", - dependencies: ["WasmTypes", "SystemExtras"], - exclude: ["CMakeLists.txt"] - ), .target( name: "WasmKit", dependencies: [ "WasmParser", - "SystemExtras", "WasmTypes", .product(name: "SystemPackage", package: "swift-system"), ], @@ -72,18 +55,6 @@ let package = Package( ], exclude: ["CMakeLists.txt"] ), - .target( - name: "WasmKitWASI", - dependencies: ["WasmKit", "WASI"], - exclude: ["CMakeLists.txt"] - ), - .target( - name: "SystemExtras", - dependencies: [ - .product(name: "SystemPackage", package: "swift-system") - ], - exclude: ["CMakeLists.txt"] - ), .executableTarget( name: "Spectest", dependencies: [ @@ -96,31 +67,11 @@ let package = Package( .testTarget(name: "WITTests", dependencies: ["WIT"]), .target(name: "WITOverlayGenerator", dependencies: ["WIT"]), .target(name: "_CabiShims"), - .plugin(name: "WITOverlayPlugin", capability: .buildTool(), dependencies: ["WITTool"]), - .plugin(name: "GenerateOverlayForTesting", capability: .buildTool(), dependencies: ["WITTool"]), - .testTarget( - name: "WITOverlayGeneratorTests", - dependencies: ["WITOverlayGenerator", "WasmKit", "WasmKitWASI"], - exclude: ["Fixtures", "Compiled", "Generated"], - plugins: [.plugin(name: "GenerateOverlayForTesting")] - ), .target(name: "WITExtractor"), .testTarget( name: "WITExtractorTests", dependencies: ["WITExtractor", "WIT"] ), - .plugin( - name: "WITExtractorPlugin", - capability: .command( - intent: .custom(verb: "extract-wit", description: "Extract WIT definition from Swift module"), - permissions: [] - ), - dependencies: ["WITTool"] - ), - .testTarget( - name: "WITExtractorPluginTests", - exclude: ["Fixtures"] - ), .executableTarget( name: "WITTool", dependencies: [ @@ -138,10 +89,6 @@ let package = Package( name: "WasmParserTests", dependencies: ["WasmParser"] ), - .testTarget( - name: "WASITests", - dependencies: ["WASI"] - ), ], swiftLanguageVersions: [.v5] ) @@ -159,3 +106,80 @@ if ProcessInfo.processInfo.environment["SWIFTCI_USE_LOCAL_DEPS"] == nil { .package(path: "../swift-system"), ] } + +#if !os(Windows) + // Add WASI-related products and targets + package.products.append(contentsOf: [ + .library( + name: "WasmKitWASI", + targets: ["WasmKitWASI"] + ), + .library( + name: "WASI", + targets: ["WASI"] + ), + ]) + package.targets.append(contentsOf: [ + .target( + name: "WASI", + dependencies: ["WasmTypes", "SystemExtras"], + exclude: ["CMakeLists.txt"] + ), + .target( + name: "WasmKitWASI", + dependencies: ["WasmKit", "WASI"], + exclude: ["CMakeLists.txt"] + ), + .target( + name: "SystemExtras", + dependencies: [ + .product(name: "SystemPackage", package: "swift-system") + ], + exclude: ["CMakeLists.txt"] + ), + .testTarget( + name: "WASITests", + dependencies: ["WASI"] + ), + ]) + let targetDependenciesToAdd = [ + "CLI": ["WasmKitWASI"], + "WasmKit": ["SystemExtras"], + ] + for (targetName, dependencies) in targetDependenciesToAdd { + if let target = package.targets.first(where: { $0.name == targetName }) { + target.dependencies += dependencies.map { .target(name: $0) } + } else { + fatalError("Target \(targetName) not found!?") + } + } + + // Add build tool plugins only for non-Windows platforms + package.products.append(contentsOf: [ + .plugin(name: "WITOverlayPlugin", targets: ["WITOverlayPlugin"]), + .plugin(name: "WITExtractorPlugin", targets: ["WITExtractorPlugin"]), + ]) + + package.targets.append(contentsOf: [ + .plugin(name: "WITOverlayPlugin", capability: .buildTool(), dependencies: ["WITTool"]), + .plugin(name: "GenerateOverlayForTesting", capability: .buildTool(), dependencies: ["WITTool"]), + .testTarget( + name: "WITOverlayGeneratorTests", + dependencies: ["WITOverlayGenerator", "WasmKit", "WasmKitWASI"], + exclude: ["Fixtures", "Compiled", "Generated"], + plugins: [.plugin(name: "GenerateOverlayForTesting")] + ), + .plugin( + name: "WITExtractorPlugin", + capability: .command( + intent: .custom(verb: "extract-wit", description: "Extract WIT definition from Swift module"), + permissions: [] + ), + dependencies: ["WITTool"] + ), + .testTarget( + name: "WITExtractorPluginTests", + exclude: ["Fixtures"] + ), + ]) +#endif diff --git a/Sources/CLI/Run/Run.swift b/Sources/CLI/Run/Run.swift index e2be5457..54bbeddb 100644 --- a/Sources/CLI/Run/Run.swift +++ b/Sources/CLI/Run/Run.swift @@ -1,6 +1,8 @@ import ArgumentParser import SystemPackage +#if canImport(WasmKitWASI) import WasmKitWASI +#endif import WasmKit struct Run: ParsableCommand { @@ -78,6 +80,7 @@ struct Run: ParsableCommand { } } + #if canImport(SystemExtras) func deriveInterceptor() throws -> (interceptor: GuestTimeProfiler, finalize: () -> Void)? { guard let outputPath = self.profileOutput else { return nil } let fileHandle = try FileDescriptor.open( @@ -97,8 +100,15 @@ struct Run: ParsableCommand { } ) } + #else + // GuestTimeProfiler is not available without SystemExtras + func deriveInterceptor() throws -> (interceptor: RuntimeInterceptor, finalize: () -> Void)? { + nil + } + #endif func instantiateWASI(module: Module, interceptor: RuntimeInterceptor?) throws -> () throws -> Void { + #if canImport(WasmKitWASI) // Flatten environment variables into a dictionary (Respect the last value if a key is duplicated) let environment = environment.reduce(into: [String: String]()) { $0[$1.key] = $1.value @@ -113,6 +123,9 @@ struct Run: ParsableCommand { let exitCode = try wasi.start(moduleInstance, runtime: runtime) throw ExitCode(Int32(exitCode)) } + #else + fatalError("WASI is not supported on this platform") + #endif } func instantiateNonWASI(module: Module, interceptor: RuntimeInterceptor?) throws -> (() throws -> Void)? { diff --git a/Sources/Spectest/Spectest.swift b/Sources/Spectest/Spectest.swift index 2fed3c8f..888155dd 100644 --- a/Sources/Spectest/Spectest.swift +++ b/Sources/Spectest/Spectest.swift @@ -41,7 +41,7 @@ struct Spectest: AsyncParsableCommand { let rootPath: String let filePath = FilePath(path) - if (try? FileDescriptor.open(filePath, FileDescriptor.AccessMode.readOnly, options: .directory)) != nil { + if isDirectory(filePath) { rootPath = path } else { rootPath = URL(fileURLWithPath: path).deletingLastPathComponent().path diff --git a/Sources/Spectest/TestCase.swift b/Sources/Spectest/TestCase.swift index 5e80e617..37439de6 100644 --- a/Sources/Spectest/TestCase.swift +++ b/Sources/Spectest/TestCase.swift @@ -79,13 +79,6 @@ struct TestCase { let content: Content let path: String - private static func isDirectory(_ path: FilePath) -> Bool { - let fd = try? FileDescriptor.open(path, FileDescriptor.AccessMode.readOnly, options: .directory) - let isDirectory = fd != nil - try? fd?.close() - return isDirectory - } - static func load(include: [String], exclude: [String], in path: String, log: ((String) -> Void)? = nil) throws -> [TestCase] { let fileManager = FileManager.default let filePath = FilePath(path) @@ -419,9 +412,9 @@ extension TestCase.Command { } } - private func deriveFeatureSet(rootPath: String) -> WasmFeatureSet { + private func deriveFeatureSet(rootPath: FilePath) -> WasmFeatureSet { var features = WasmFeatureSet.default - if rootPath.hasSuffix("/proposals/memory64") { + if rootPath.ends(with: "proposals/memory64") { features.insert(.memory64) // memory64 doesn't expect reference-types proposal // and it depends on the fact reference-types is disabled @@ -431,9 +424,10 @@ extension TestCase.Command { } private func parseModule(rootPath: String, filename: String) throws -> Module { - let url = URL(fileURLWithPath: rootPath).appendingPathComponent(filename) + let rootPath = FilePath(rootPath) + let path = rootPath.appending(filename) - let module = try parseWasm(filePath: FilePath(url.path), features: deriveFeatureSet(rootPath: rootPath)) + let module = try parseWasm(filePath: path, features: deriveFeatureSet(rootPath: rootPath)) return module } @@ -563,3 +557,20 @@ extension Swift.Error { return "unknown error: \(self)" } } + +#if os(Windows) +import WinSDK +#endif +internal func isDirectory(_ path: FilePath) -> Bool { + #if os(Windows) + return path.withPlatformString { + let result = GetFileAttributesW($0) + return result != INVALID_FILE_ATTRIBUTES && result & DWORD(FILE_ATTRIBUTE_DIRECTORY) != 0 + } + #else + let fd = try? FileDescriptor.open(path, FileDescriptor.AccessMode.readOnly, options: .directory) + let isDirectory = fd != nil + try? fd?.close() + return isDirectory + #endif +} diff --git a/Sources/SystemExtras/Clock.swift b/Sources/SystemExtras/Clock.swift index 2ec17edf..3727df7a 100644 --- a/Sources/SystemExtras/Clock.swift +++ b/Sources/SystemExtras/Clock.swift @@ -12,6 +12,8 @@ import ucrt import SystemPackage +#if !os(Windows) + @frozen public struct Clock: RawRepresentable { @@ -110,3 +112,4 @@ extension Clock { } } } +#endif diff --git a/Sources/SystemExtras/Constants.swift b/Sources/SystemExtras/Constants.swift index c44c5d9b..6d5f2bfd 100644 --- a/Sources/SystemExtras/Constants.swift +++ b/Sources/SystemExtras/Constants.swift @@ -12,6 +12,7 @@ import ucrt import SystemPackage +#if !os(Windows) @_alwaysEmitIntoClient internal var _AT_EACCESS: CInt { AT_EACCESS } @_alwaysEmitIntoClient @@ -20,6 +21,7 @@ internal var _AT_SYMLINK_NOFOLLOW: CInt { AT_SYMLINK_NOFOLLOW } internal var _AT_SYMLINK_FOLLOW: CInt { AT_SYMLINK_FOLLOW } @_alwaysEmitIntoClient internal var _AT_REMOVEDIR: CInt { AT_REMOVEDIR } +#endif #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) @_alwaysEmitIntoClient internal var _AT_REALDEV: CInt { AT_REALDEV } @@ -35,17 +37,20 @@ internal var _AT_NO_AUTOMOUNT: CInt { AT_NO_AUTOMOUNT } #endif */ +#if !os(Windows) @_alwaysEmitIntoClient internal var _F_GETFL: CInt { F_GETFL } @_alwaysEmitIntoClient internal var _O_DSYNC: CInt { O_DSYNC } @_alwaysEmitIntoClient internal var _O_SYNC: CInt { O_SYNC } +#endif #if os(Linux) @_alwaysEmitIntoClient internal var _O_RSYNC: CInt { O_RSYNC } #endif +#if !os(Windows) @_alwaysEmitIntoClient internal var _UTIME_NOW: CInt { #if os(Linux) @@ -87,23 +92,26 @@ internal var _DT_LNK: CInt { CInt(DT_LNK) } internal var _DT_SOCK: CInt { CInt(DT_SOCK) } @_alwaysEmitIntoClient internal var _DT_WHT: CInt { CInt(DT_WHT) } +#endif @_alwaysEmitIntoClient internal var _S_IFMT: CInterop.Mode { S_IFMT } @_alwaysEmitIntoClient -internal var _S_IFIFO: CInterop.Mode { S_IFIFO } -@_alwaysEmitIntoClient internal var _S_IFCHR: CInterop.Mode { S_IFCHR } @_alwaysEmitIntoClient internal var _S_IFDIR: CInterop.Mode { S_IFDIR } @_alwaysEmitIntoClient -internal var _S_IFBLK: CInterop.Mode { S_IFBLK } -@_alwaysEmitIntoClient internal var _S_IFREG: CInterop.Mode { S_IFREG } +#if !os(Windows) +@_alwaysEmitIntoClient +internal var _S_IFIFO: CInterop.Mode { S_IFIFO } +@_alwaysEmitIntoClient +internal var _S_IFBLK: CInterop.Mode { S_IFBLK } @_alwaysEmitIntoClient internal var _S_IFLNK: CInterop.Mode { S_IFLNK } @_alwaysEmitIntoClient internal var _S_IFSOCK: CInterop.Mode { S_IFSOCK } +#endif #if os(Linux) @_alwaysEmitIntoClient diff --git a/Sources/SystemExtras/FileAtOperations.swift b/Sources/SystemExtras/FileAtOperations.swift index d7dbfa3f..ace751be 100644 --- a/Sources/SystemExtras/FileAtOperations.swift +++ b/Sources/SystemExtras/FileAtOperations.swift @@ -26,6 +26,7 @@ extension FileDescriptor { self.rawValue = rawValue } + #if !os(Windows) /// Indicates the operation does't follow symlinks /// /// If you specify this option and the file you pass to @@ -35,6 +36,7 @@ extension FileDescriptor { /// The corresponding C constant is `AT_SYMLINK_NOFOLLOW`. @_alwaysEmitIntoClient public static var noFollow: AtOptions { AtOptions(rawValue: _AT_SYMLINK_NOFOLLOW) } + #endif /* FIXME: Disabled until CSystem will include "linux/fcntl.h" #if os(Linux) diff --git a/Sources/SystemExtras/Syscalls.swift b/Sources/SystemExtras/Syscalls.swift index 87c850a6..e902651f 100644 --- a/Sources/SystemExtras/Syscalls.swift +++ b/Sources/SystemExtras/Syscalls.swift @@ -71,11 +71,6 @@ internal func system_unlinkat( return unlinkat(fd, path, flags) } -// futimens -internal func system_futimens(_ fd: Int32, _ times: UnsafePointer) -> CInt { - return futimens(fd, times) -} - // ftruncate internal func system_ftruncate(_ fd: Int32, _ size: off_t) -> CInt { return ftruncate(fd, size) @@ -95,6 +90,9 @@ internal func system_symlinkat( return symlinkat(oldPath, newDirFd, newPath) } +// ucrt does not provide `opendir` API +#if !os(Windows) + extension CInterop { #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) public typealias DirP = UnsafeMutablePointer @@ -114,12 +112,20 @@ internal func system_fdopendir(_ fd: Int32) -> CInterop.DirP? { internal func system_readdir(_ dirp: CInterop.DirP) -> UnsafeMutablePointer? { return readdir(dirp) } +#endif + +#if !os(Windows) extension CInterop { public typealias ClockId = clockid_t public typealias TimeSpec = timespec } +// futimens +internal func system_futimens(_ fd: Int32, _ times: UnsafePointer) -> CInt { + return futimens(fd, times) +} + // clock_gettime internal func system_clock_gettime(_ id: CInterop.ClockId, _ tp: UnsafeMutablePointer) -> CInt { return clock_gettime(id, tp) @@ -129,3 +135,4 @@ internal func system_clock_gettime(_ id: CInterop.ClockId, _ tp: UnsafeMutablePo internal func system_clock_getres(_ id: CInterop.ClockId, _ tp: UnsafeMutablePointer) -> CInt { return clock_getres(id, tp) } +#endif diff --git a/Sources/SystemExtras/Vendor/Utils.swift b/Sources/SystemExtras/Vendor/Utils.swift index f22d551d..f86e5415 100644 --- a/Sources/SystemExtras/Vendor/Utils.swift +++ b/Sources/SystemExtras/Vendor/Utils.swift @@ -1,7 +1,7 @@ /* This source file is part of the Swift System open source project - Copyright (c) 2020 Apple Inc. and the Swift System project authors + Copyright (c) 2020 - 2021 Apple Inc. and the Swift System project authors Licensed under Apache License v2.0 with Runtime Library Exception See https://swift.org/LICENSE.txt for license information @@ -12,38 +12,22 @@ import SystemPackage -#if canImport(Darwin) -import Darwin -#endif - -#if canImport(Glibc) -import Glibc -#endif - -#if canImport(ucrt) -import ucrt -#endif - -#if canImport(WASILibc) -import WASILibc -#endif - // Results in errno if i == -1 -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) private func valueOrErrno( _ i: I ) -> Result { - i == -1 ? .failure(Errno(rawValue: errno)) : .success(i) + i == -1 ? .failure(Errno(rawValue: system_errno)) : .success(i) } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) private func nothingOrErrno( _ i: I ) -> Result<(), Errno> { valueOrErrno(i).map { _ in () } } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) internal func valueOrErrno( retryOnInterrupt: Bool, _ f: () -> I ) -> Result { @@ -57,7 +41,7 @@ internal func valueOrErrno( } while true } -// @available(macOS 10.16, iOS 14.0, watchOS 7.0, tvOS 14.0, *) +@available(/*System 0.0.1: macOS 11.0, iOS 14.0, watchOS 7.0, tvOS 14.0*/iOS 8, *) internal func nothingOrErrno( retryOnInterrupt: Bool, _ f: () -> I ) -> Result<(), Errno> { @@ -148,3 +132,13 @@ extension MutableCollection where Element: Equatable { } } } + +internal func _withOptionalUnsafePointerOrNull( + to value: T?, + _ body: (UnsafePointer?) throws -> R +) rethrows -> R { + guard let value = value else { + return try body(nil) + } + return try withUnsafePointer(to: value, body) +} diff --git a/Sources/WasmKit/Execution/Runtime/Profiler.swift b/Sources/WasmKit/Execution/Runtime/Profiler.swift index ae4553d7..b0dc2af4 100644 --- a/Sources/WasmKit/Execution/Runtime/Profiler.swift +++ b/Sources/WasmKit/Execution/Runtime/Profiler.swift @@ -1,3 +1,4 @@ +#if canImport(SystemExtras) import SystemExtras import SystemPackage @@ -116,3 +117,5 @@ private enum JSON { return output } } + +#endif diff --git a/Sources/WasmKit/ModuleParser.swift b/Sources/WasmKit/ModuleParser.swift index 92a819d4..d6119883 100644 --- a/Sources/WasmKit/ModuleParser.swift +++ b/Sources/WasmKit/ModuleParser.swift @@ -1,10 +1,21 @@ import SystemPackage import WasmParser +#if os(Windows) +import ucrt +#endif /// Parse a given file as a WebAssembly binary format file /// > Note: public func parseWasm(filePath: FilePath, features: WasmFeatureSet = .default) throws -> Module { - let fileHandle = try FileDescriptor.open(filePath, .readOnly) + #if os(Windows) + // TODO: Upstream `O_BINARY` to `SystemPackage + let accessMode = FileDescriptor.AccessMode( + rawValue: FileDescriptor.AccessMode.readOnly.rawValue | O_BINARY + ) + #else + let accessMode: FileDescriptor.AccessMode = .readOnly + #endif + let fileHandle = try FileDescriptor.open(filePath, accessMode) defer { try? fileHandle.close() } let stream = try FileHandleStream(fileHandle: fileHandle) let module = try parseModule(stream: stream, features: features) diff --git a/Tests/WITExtractorTests/TestSupport.swift b/Tests/WITExtractorTests/TestSupport.swift index e4766929..a4ebd66d 100644 --- a/Tests/WITExtractorTests/TestSupport.swift +++ b/Tests/WITExtractorTests/TestSupport.swift @@ -47,9 +47,18 @@ struct TestSupport { let templatePath = tempdir.appendingPathComponent("WasmKit.XXXXXX") var template = [UInt8](templatePath.path.utf8).map({ Int8($0) }) + [Int8(0)] + #if os(Windows) + if _mktemp_s(&template, template.count) != 0 { + throw Error(errno: errno) + } + if _mkdir(template) != 0 { + throw Error(errno: errno) + } + #else if mkdtemp(&template) == nil { throw Error(errno: errno) } + #endif let path = String(cString: template) defer { _ = try? FileManager.default.removeItem(atPath: path) }