Skip to content

Drop Foundation dependency #92

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ let package = Package(
),
.target(
name: "WasmParser",
dependencies: ["WasmTypes"]
dependencies: [
"WasmTypes",
.product(name: "SystemPackage", package: "swift-system"),
]
),
.target(
name: "WasmKitWASI",
Expand Down
39 changes: 20 additions & 19 deletions Sources/CLI/Run/Run.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import ArgumentParser
import Foundation
import SystemPackage
import WasmKitWASI
import WasmKit
Expand Down Expand Up @@ -43,7 +42,7 @@ struct Run: ParsableCommand {
log("Started parsing module", verbose: true)

let module: Module
if verbose {
if verbose, #available(macOS 13.0, *) {
let (parsedModule, parseTime) = try measure {
try parseWasm(filePath: FilePath(path))
}
Expand Down Expand Up @@ -71,23 +70,27 @@ struct Run: ParsableCommand {
invoke = entry
}

let (_, invokeTime) = try measure(execution: invoke)

log("Finished invoking function \"\(path)\": \(invokeTime)", verbose: true)
if #available(macOS 13.0, *) {
let (_, invokeTime) = try measure(execution: invoke)
log("Finished invoking function \"\(path)\": \(invokeTime)", verbose: true)
} else {
try invoke()
}
}

func deriveInterceptor() throws -> (interceptor: GuestTimeProfiler, finalize: () -> Void)? {
guard let outputPath = self.profileOutput else { return nil }
FileManager.default.createFile(atPath: outputPath, contents: nil)
let fileHandle = try FileHandle(forWritingTo: URL(fileURLWithPath: outputPath))
let fileHandle = try FileDescriptor.open(
FilePath(outputPath), .writeOnly, options: .create
)
let profiler = GuestTimeProfiler { data in
try? fileHandle.write(contentsOf: data)
var data = data
_ = data.withUTF8 { try! fileHandle.writeAll($0) }
}
return (
profiler,
{
profiler.finalize()
try! fileHandle.synchronize()
try! fileHandle.close()

print("\nProfile Completed: \(outputPath) can be viewed using https://ui.perfetto.dev/")
Expand Down Expand Up @@ -144,23 +147,21 @@ struct Run: ParsableCommand {
}
}

@available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
func measure<Result>(
execution: () throws -> Result
) rethrows -> (Result, String) {
let start = DispatchTime.now()
let result = try execution()
let end = DispatchTime.now()

let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
let nanoseconds = NSNumber(value: end.uptimeNanoseconds - start.uptimeNanoseconds)
let formattedTime = numberFormatter.string(from: nanoseconds)! + " ns"
return (result, formattedTime)
var result: Result!
let formattedTime = try ContinuousClock().measure {
result = try execution()
}

return (result, formattedTime.description)
}

@Sendable func log(_ message: String, verbose: Bool = false) {
if !verbose || self.verbose {
fputs(message + "\n", stderr)
try! FileDescriptor.standardError.writeAll((message + "\n").utf8)
}
}
}
1 change: 0 additions & 1 deletion Sources/WASI/FileSystem.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import SystemPackage

struct FileAccessMode: OptionSet {
Expand Down
50 changes: 18 additions & 32 deletions Sources/WASI/Platform/File.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import SystemPackage

protocol FdWASIEntry: WASIEntry {
Expand Down Expand Up @@ -31,48 +30,34 @@ extension FdWASIFile {
throw WASIAbi.Errno.EBADF
}
// TODO: Use `writev`
let handle = FileHandle(fileDescriptor: fd.rawValue)
var bytesWritten: UInt32 = 0
for iovec in buffer {
try iovec.withHostBufferPointer {
try handle.write(contentsOf: $0)
bytesWritten += try iovec.withHostBufferPointer {
UInt32(try fd.write(UnsafeRawBufferPointer($0)))
}
bytesWritten += iovec.length
}
return bytesWritten
}

@inlinable
func pwrite<Buffer: Sequence>(vectored buffer: Buffer, offset: WASIAbi.FileSize) throws -> WASIAbi.Size where Buffer.Element == WASIAbi.IOVec {
// TODO: Use `pwritev`
let handle = FileHandle(fileDescriptor: fd.rawValue)
let savedOffset = try handle.offset()
try handle.seek(toOffset: offset)
let nwritten = try write(vectored: buffer)
try handle.seek(toOffset: savedOffset)
return nwritten
var currentOffset: Int64 = Int64(offset)
for iovec in buffer {
currentOffset += try iovec.withHostBufferPointer {
Int64(try fd.writeAll(toAbsoluteOffset: currentOffset, $0))
}
}
let nwritten = WASIAbi.FileSize(currentOffset) - offset
return WASIAbi.Size(nwritten)
}

@inlinable
func read<Buffer: Sequence>(into buffer: Buffer) throws -> WASIAbi.Size where Buffer.Element == WASIAbi.IOVec {
// TODO: Use `readv`
let handle = FileHandle(fileDescriptor: fd.rawValue)
var nread: UInt32 = 0
for iovec in buffer {
try iovec.buffer.withHostPointer(count: Int(iovec.length)) { rawBufferStart in
var bufferStart = rawBufferStart.baseAddress!.bindMemory(
to: UInt8.self, capacity: Int(iovec.length)
)
let bufferEnd = bufferStart + Int(iovec.length)
while bufferStart < bufferEnd {
let remaining = bufferEnd - bufferStart
guard let bytes = try handle.read(upToCount: remaining) else {
break
}
bytes.copyBytes(to: bufferStart, count: bytes.count)
bufferStart += bytes.count
}
nread += iovec.length - UInt32(bufferEnd - bufferStart)
nread += try iovec.withHostBufferPointer {
try UInt32(fd.read(into: $0))
}
}
return nread
Expand All @@ -81,11 +66,12 @@ extension FdWASIFile {
@inlinable
func pread<Buffer: Sequence>(into buffer: Buffer, offset: WASIAbi.FileSize) throws -> WASIAbi.Size where Buffer.Element == WASIAbi.IOVec {
// TODO: Use `preadv`
let handle = FileHandle(fileDescriptor: fd.rawValue)
let savedOffset = try handle.offset()
try handle.seek(toOffset: offset)
let nread = try read(into: buffer)
try handle.seek(toOffset: savedOffset)
var nread: UInt32 = 0
for iovec in buffer {
nread += try iovec.withHostBufferPointer {
try UInt32(fd.read(fromAbsoluteOffset: Int64(offset + UInt64(nread)), into: $0))
}
}
return nread
}
}
Expand Down
6 changes: 4 additions & 2 deletions Sources/WASI/RandomBufferGenerator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ extension RandomBufferGenerator where Self: RandomNumberGenerator {
withUnsafeBytes(of: random) { randomBytes in
let startOffset = i * 8
let destination = UnsafeMutableBufferPointer(rebasing: buffer[startOffset..<(startOffset + 8)])
randomBytes.copyBytes(to: destination)
UnsafeMutableRawBufferPointer(destination).copyMemory(from: randomBytes)
}
}

Expand All @@ -33,7 +33,9 @@ extension RandomBufferGenerator where Self: RandomNumberGenerator {
withUnsafeBytes(of: random) { randomBytes in
let startOffset = count * 8
let destination = UnsafeMutableBufferPointer(rebasing: buffer[startOffset..<(startOffset + remaining)])
randomBytes.copyBytes(to: destination)
UnsafeMutableRawBufferPointer(destination).copyMemory(
from: UnsafeRawBufferPointer(start: randomBytes.baseAddress, count: remaining)
)
}
}
}
Expand Down
34 changes: 22 additions & 12 deletions Sources/WASI/WASI.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import Foundation
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
import Darwin
#elseif os(Linux) || os(FreeBSD) || os(Android)
import Glibc
#elseif os(Windows)
import ucrt
#else
#error("Unsupported Platform")
#endif
import SystemExtras
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come we need to use libc here? Seems like the only use site is open(..., O_DIRECTORY) — can we not use SystemPackage.FileDescriptor.OpenOptions.directory instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep using memchr because llvm recognizes the lib call and optimizes it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, what do you think about making a memchr shim in SystemExtras? Might be nice to isolate the C library stuff to that one spot. We can do the same for O_DIRECTORY, which seems like it might need to be re-declared by us on Windows (other platforms define .directory in SystemPackage).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, importing it through a C shim could be an option. Actually memchr is not a syscall and SystemExtras will be upstreamed to swift-system later, so SystemExtras might not be an appropriate place.
Once we upgrade the minimum supported Swift version to 6.0, we can import it via @_extern(c) without C module, but it's not feasible right now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah interesting, I would've assumed that UnsafeRawBufferPointer.firstIndex(of:) could be optimized to memchr under the hood but it looks like that isn't being done yet. It's being tracked via swiftlang/swift#63200.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's unfortunate situation now 🥲

import SystemPackage
import WasmTypes
Expand Down Expand Up @@ -376,9 +384,9 @@ enum WASIAbi {
let buffer: UnsafeGuestRawPointer
let length: WASIAbi.Size

func withHostBufferPointer<R>(_ body: (UnsafeRawBufferPointer) throws -> R) rethrows -> R {
func withHostBufferPointer<R>(_ body: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R {
try buffer.withHostPointer(count: Int(length)) { hostPointer in
try body(UnsafeRawBufferPointer(hostPointer))
try body(hostPointer)
}
}

Expand Down Expand Up @@ -1382,7 +1390,7 @@ public class WASIBridgeToHost: WASI {
let fd = open(cHostPath, O_DIRECTORY)
if fd < 0 {
let errno = errno
throw POSIXError(POSIXErrorCode(rawValue: errno)!)
throw WASIError(description: "Failed to open preopen path '\(hostPath)': \(String(cString: strerror(errno)))")
}
return FileDescriptor(rawValue: fd)
}
Expand All @@ -1409,8 +1417,8 @@ public class WASIBridgeToHost: WASI {
offsets += 1
let count = arg.utf8CString.withUnsafeBytes { bytes in
let count = UInt32(bytes.count)
_ = buffer.raw.withHostPointer(count: bytes.count) { hostDestBuffer in
bytes.copyBytes(to: hostDestBuffer)
buffer.raw.withHostPointer(count: bytes.count) { hostDestBuffer in
hostDestBuffer.copyMemory(from: bytes)
}
return count
}
Expand All @@ -1434,8 +1442,8 @@ public class WASIBridgeToHost: WASI {
offsets += 1
let count = "\(key)=\(value)".utf8CString.withUnsafeBytes { bytes in
let count = UInt32(bytes.count)
_ = buffer.raw.withHostPointer(count: bytes.count) { hostDestBuffer in
bytes.copyBytes(to: hostDestBuffer)
buffer.raw.withHostPointer(count: bytes.count) { hostDestBuffer in
hostDestBuffer.copyMemory(from: bytes)
}
return count
}
Expand Down Expand Up @@ -1589,8 +1597,8 @@ public class WASIBridgeToHost: WASI {
guard bytes.count <= maxPathLength else {
throw WASIAbi.Errno.ENAMETOOLONG
}
_ = path.withHostPointer(count: Int(maxPathLength)) { buffer in
bytes.copyBytes(to: buffer)
path.withHostPointer(count: Int(maxPathLength)) { buffer in
UnsafeMutableRawBufferPointer(buffer).copyBytes(from: bytes)
}
}
}
Expand Down Expand Up @@ -1650,8 +1658,10 @@ public class WASIBridgeToHost: WASI {
let copyingBytes = min(entry.dirNameLen, totalBufferSize - bufferUsed)
let rangeStart = buffer.baseAddress.raw.advanced(by: bufferUsed)
name.withUTF8 { bytes in
_ = rangeStart.withHostPointer(count: Int(copyingBytes)) { hostBuffer in
bytes.copyBytes(to: hostBuffer, count: Int(copyingBytes))
rangeStart.withHostPointer(count: Int(copyingBytes)) { hostBuffer in
hostBuffer.copyMemory(
from: UnsafeRawBufferPointer(start: bytes.baseAddress, count: Int(copyingBytes))
)
}
}
bufferUsed += copyingBytes
Expand Down
59 changes: 45 additions & 14 deletions Sources/WasmKit/Execution/Runtime/Profiler.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import Foundation
import SystemExtras
import SystemPackage

Expand All @@ -15,30 +14,29 @@ public class GuestTimeProfiler: RuntimeInterceptor {
let pid: Int
let name: String
let ts: Int

var jsonLine: String {
#"{"ph":"\#(ph.rawValue)","pid":\#(pid),"name":"\#(JSON.serialize(name))","ts":\#(ts)}"#
}
}

private var output: (Data) -> Void
private var output: (_ line: String) -> Void
private var hasFirstEvent: Bool = false
private let encoder = JSONEncoder()
private let startTime: UInt64

public init(output: @escaping (Data) -> Void) {
public init(output: @escaping (_ line: String) -> Void) {
self.output = output
self.startTime = Self.getTimestamp()
}

private func eventLine(_ event: Event) -> Data? {
return try? encoder.encode(event)
}

private func addEventLine(_ event: Event) {
guard let line = eventLine(event) else { return }
let line = event.jsonLine
if !hasFirstEvent {
self.output("[\n".data(using: .utf8)!)
self.output("[\n")
self.output(line)
hasFirstEvent = true
} else {
self.output(",\n".data(using: .utf8)!)
self.output(",\n")
self.output(line)
}
}
Expand All @@ -65,7 +63,7 @@ public class GuestTimeProfiler: RuntimeInterceptor {
let functionName = try? store.nameRegistry.lookup(address)
let event = Event(
ph: .begin, pid: 1,
name: functionName ?? "unknown function(\(String(format: "0x%x", address)))",
name: functionName ?? "unknown function(0x\(String(address, radix: 16)))",
ts: getDurationSinceStart()
)
addEventLine(event)
Expand All @@ -75,13 +73,46 @@ public class GuestTimeProfiler: RuntimeInterceptor {
let functionName = try? store.nameRegistry.lookup(address)
let event = Event(
ph: .end, pid: 1,
name: functionName ?? "unknown function(\(String(format: "0x%x", address)))",
name: functionName ?? "unknown function(0x\(String(address, radix: 16)))",
ts: getDurationSinceStart()
)
addEventLine(event)
}

public func finalize() {
output("\n]".data(using: .utf8)!)
output("\n]")
}
}

/// Foundation-less JSON serialization
private enum JSON {
static func serialize(_ value: String) -> String {
// https://www.ietf.org/rfc/rfc4627.txt
var output = "\""
for scalar in value.unicodeScalars {
switch scalar {
case "\"":
output += "\\\""
case "\\":
output += "\\\\"
case "\u{08}":
output += "\\b"
case "\u{0C}":
output += "\\f"
case "\n":
output += "\\n"
case "\r":
output += "\\r"
case "\t":
output += "\\t"
case "\u{20}"..."\u{21}", "\u{23}"..."\u{5B}", "\u{5D}"..."\u{10FFFF}":
output.unicodeScalars.append(scalar)
default:
var hex = String(scalar.value, radix: 16, uppercase: true)
hex = String(repeating: "0", count: 4 - hex.count) + hex
output += "\\u" + hex
}
}
return output
}
}
Loading
Loading