Skip to content
Merged
3 changes: 3 additions & 0 deletions Sources/CSystemExtras/include/clock.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
inline static clockid_t csystemextras_monotonic_clockid() {
return CLOCK_MONOTONIC;
}
inline static clockid_t csystemextras_realtime_clockid() {
return CLOCK_REALTIME;
}
10 changes: 10 additions & 0 deletions Sources/SystemExtras/Clock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ extension Clock {
public static var monotonic: Clock { Clock(rawValue: csystemextras_monotonic_clockid()) }
#endif

#if SYSTEM_PACKAGE_DARWIN || os(Linux) || os(Android) || os(OpenBSD) || os(FreeBSD)
@_alwaysEmitIntoClient
public static var realtime: Clock { Clock(rawValue: _CLOCK_REALTIME) }
#endif

#if os(WASI)
@_alwaysEmitIntoClient
public static var realtime: Clock { Clock(rawValue: csystemextras_realtime_clockid()) }
#endif

#if os(OpenBSD) || os(FreeBSD)
@_alwaysEmitIntoClient
public static var uptime: Clock { Clock(rawValue: _CLOCK_UPTIME) }
Expand Down
4 changes: 4 additions & 0 deletions Sources/SystemExtras/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ internal var _CLOCK_MONOTONIC_RAW: CInterop.ClockId { CLOCK_MONOTONIC_RAW }
@_alwaysEmitIntoClient
internal var _CLOCK_MONOTONIC: CInterop.ClockId { CLOCK_MONOTONIC }
#endif
#if SYSTEM_PACKAGE_DARWIN || os(Linux) || os(Android) || os(OpenBSD) || os(FreeBSD)
@_alwaysEmitIntoClient
internal var _CLOCK_REALTIME: CInterop.ClockId { CLOCK_REALTIME }
#endif
#if SYSTEM_PACKAGE_DARWIN
@_alwaysEmitIntoClient
internal var _CLOCK_UPTIME_RAW: CInterop.ClockId { CLOCK_UPTIME_RAW }
Expand Down
5 changes: 4 additions & 1 deletion Sources/SystemExtras/FileOperations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,10 @@ extension FileDescriptor {

@_alwaysEmitIntoClient
public var device: UInt64 {
UInt64(rawValue.st_dev)
if (rawValue.st_dev < 0) {
return UInt64(bitPattern: Int64(rawValue.st_dev))
}
return UInt64(rawValue.st_dev)
}

@_alwaysEmitIntoClient
Expand Down
6 changes: 6 additions & 0 deletions Sources/WASI/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@ add_wasmkit_library(WASI
Platform/File.swift
Platform/PlatformTypes.swift
Platform/SandboxPrimitives.swift
Platform/HostFileSystem.swift
MemoryFileSystem/MemoryFileSystem.swift
MemoryFileSystem/MemoryFSNodes.swift
MemoryFileSystem/MemoryDirEntry.swift
MemoryFileSystem/MemoryFileEntry.swift
FileSystem.swift
GuestMemorySupport.swift
Clock.swift
RandomBufferGenerator.swift
WASI.swift
WASIBridgeToHost.swift
)

target_link_wasmkit_libraries(WASI PUBLIC
Expand Down
56 changes: 41 additions & 15 deletions Sources/WASI/Clock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,23 @@ public protocol MonotonicClock {

public func now() throws -> WallClock.Duration {
var fileTime = FILETIME()
GetSystemTimeAsFileTime(&fileTime)
// > the number of 100-nanosecond intervals since January 1, 1601 (UTC).
// Use GetSystemTimePreciseAsFileTime for better precision
// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime
GetSystemTimePreciseAsFileTime(&fileTime)
// FILETIME is 100-nanosecond intervals since 1601-01-01
// https://learn.microsoft.com/en-us/windows/win32/api/minwinbase/ns-minwinbase-filetime
let time = (UInt64(fileTime.dwLowDateTime) | UInt64(fileTime.dwHighDateTime) << 32) / 10
return (seconds: time / 1_000_000_000, nanoseconds: UInt32(time % 1_000_000_000))
let intervals = (UInt64(fileTime.dwHighDateTime) << 32) | UInt64(fileTime.dwLowDateTime)
// Convert from Windows epoch (1601) to Unix epoch (1970)
// Epoch offset: 11_644_473_600 seconds * 10_000_000 (100ns intervals per second) = 116_444_736_000_000_000
let unixEpochOffset: UInt64 = 116_444_736_000_000_000 // 100ns intervals between epochs
guard intervals >= unixEpochOffset else {
// Handle pre-1970 dates (return 0)
return (seconds: 0, nanoseconds: 0)
}
let unixIntervals = intervals - unixEpochOffset
// Convert 100ns intervals to nanoseconds, then to seconds/nanoseconds
let totalNanoseconds = unixIntervals * 100
return (seconds: totalNanoseconds / 1_000_000_000, nanoseconds: UInt32((totalNanoseconds % 1_000_000_000)))
}

public func resolution() throws -> WallClock.Duration {
Expand Down Expand Up @@ -123,15 +135,7 @@ public protocol MonotonicClock {
/// A wall clock that uses the system's wall clock.
public struct SystemWallClock: WallClock {
private var underlying: SystemExtras.Clock {
#if os(Linux) || os(Android)
return .boottime
#elseif os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS)
return .rawMonotonic
#elseif os(OpenBSD) || os(FreeBSD) || os(WASI)
return .monotonic
#else
#error("Unsupported platform")
#endif
return .realtime
}

public init() {}
Expand All @@ -140,15 +144,37 @@ public protocol MonotonicClock {
let timeSpec = try WASIAbi.Errno.translatingPlatformErrno {
try underlying.currentTime()
}
return (seconds: UInt64(timeSpec.seconds), nanoseconds: UInt32(timeSpec.nanoseconds))
// Handle potential negative tv_sec (pre-1970 dates)
let seconds = timeSpec.seconds >= 0 ? UInt64(timeSpec.seconds) : 0
let nanoseconds = timeSpec.nanoseconds >= 0 ? UInt32(timeSpec.nanoseconds) : 0
return (seconds: seconds, nanoseconds: nanoseconds)
}

public func resolution() throws -> WallClock.Duration {
let timeSpec = try WASIAbi.Errno.translatingPlatformErrno {
try underlying.resolution()
}
return (seconds: UInt64(timeSpec.seconds), nanoseconds: UInt32(timeSpec.nanoseconds))
let seconds = timeSpec.seconds >= 0 ? UInt64(timeSpec.seconds) : 0
let nanoseconds = timeSpec.nanoseconds >= 0 ? UInt32(timeSpec.nanoseconds) : 0
return (seconds: seconds, nanoseconds: nanoseconds)
}
}

#endif

// MARK: - Internal Helper

extension WASIAbi.Timestamp {
/// Get the current wall clock time in nanoseconds since Unix epoch.
/// This is an internal helper for use within the WASI module.
internal static func currentWallClock() -> WASIAbi.Timestamp {
let clock = SystemWallClock()
do {
let duration = try clock.now()
return WASIAbi.Timestamp(wallClockDuration: duration)
} catch {
// Fallback: return 0 on error
return 0
}
}
}
32 changes: 32 additions & 0 deletions Sources/WASI/FileSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,13 @@ enum FdEntry {
return directory
}
}

func asFile() -> (any WASIFile)? {
if case .file(let entry) = self {
return entry
}
return nil
}
}

/// A table that maps file descriptor to actual resource in host environment
Expand Down Expand Up @@ -120,3 +127,28 @@ struct FdTable {
}
}
}

/// Content of a file that can be retrieved from the file system.
public enum FileContent {
case bytes([UInt8])
case handle(FileDescriptor)
}

/// Protocol for file system implementations used by WASI.
///
/// This protocol contains WASI-specific implementation details.
protocol FileSystemImplementation: ~Copyable {
/// Preopens a directory and returns a WASIDir implementation.
func preopenDirectory(guestPath: String, hostPath: String) throws -> any WASIDir

/// Opens a file or directory from a directory file descriptor.
func openAt(
dirFd: any WASIDir,
path: String,
oflags: WASIAbi.Oflags,
fsRightsBase: WASIAbi.Rights,
fsRightsInheriting: WASIAbi.Rights,
fdflags: WASIAbi.Fdflags,
symlinkFollow: Bool
) throws -> FdEntry
}
Loading
Loading