Skip to content
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

Draft changes to the io_uring prototype #208

Draft
wants to merge 40 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
15794f5
ioring pitch: first steps
oxy Jul 19, 2023
6338029
stage 2: IORequest enum
oxy Aug 1, 2023
996e940
initial AsyncFileDescriptor work
oxy Aug 1, 2023
1f821a8
AsyncSequence draft implementation
oxy Aug 8, 2023
0762f57
migrate CSystem to systemLibrary
oxy Aug 10, 2023
d099546
fix access control
oxy Aug 11, 2023
b584504
fix off-by-one in IORequest.openat
oxy Aug 11, 2023
b596783
implement closing
oxy Aug 11, 2023
6b4084c
introduce IORing unit tests
oxy Aug 11, 2023
baab9b2
Starting to move to noncopyable structs, and away from swift-atomics
Catfish-Man Oct 24, 2024
6029936
One more noncopyable struct
Catfish-Man Oct 24, 2024
10c070a
WIP, more ~Copyable adoption
Catfish-Man Oct 28, 2024
32966f9
It builds again! With some horrible hacks
Catfish-Man Oct 28, 2024
822e481
Adopt isolation parameters
Catfish-Man Oct 28, 2024
fcb0b69
Merge branch 'main' into david/ioring
Catfish-Man Oct 29, 2024
6f793cf
Merge branch 'main' into david/ioring
Catfish-Man Oct 29, 2024
a9f92a6
Delete stray Package.resolved changes
Catfish-Man Oct 29, 2024
1a3e37d
Fix mismerge
Catfish-Man Oct 29, 2024
f369347
Fix mismerge
Catfish-Man Oct 29, 2024
ef94a37
Refactoring, and give up on resources being noncopyable structs
Catfish-Man Dec 5, 2024
7ea32ae
More refactoring, and working timeout support on ManagedIORing
Catfish-Man Dec 11, 2024
55fd6e7
Add support for timeout-on-wait to IORing, don't have tests yet
Catfish-Man Dec 12, 2024
e5fdf9e
Remove managed abstractions for now
Catfish-Man Feb 6, 2025
fdbceca
Eliminate internal locking and add multiple consume support
Catfish-Man Feb 6, 2025
7107a57
Fix import visibility
Catfish-Man Feb 11, 2025
02481e0
More import fixes
Catfish-Man Feb 11, 2025
bb03f0f
Redesign registered resources API
Catfish-Man Feb 11, 2025
a396967
More registration tweaks
Catfish-Man Feb 11, 2025
d4ca412
Some renaming, and implement linked requests
Catfish-Man Feb 11, 2025
5bfed03
Switch to static methods for constructing requests
Catfish-Man Feb 11, 2025
74366c3
Improve submit API
Catfish-Man Feb 12, 2025
7f6e673
Adjust registered resources API
Catfish-Man Feb 12, 2025
0c6ef16
Fix type
Catfish-Man Feb 12, 2025
a22e5f6
Add a version of registerBuffers that isn't varargs
Catfish-Man Feb 13, 2025
6983196
Add unlinkAt support
Catfish-Man Feb 13, 2025
5ba1377
Dubious approach to this, but I want to try it out a bit
Catfish-Man Feb 13, 2025
0064649
Turn on single issuer as an experiment
Catfish-Man Feb 14, 2025
901f4c8
Revert "Turn on single issuer as an experiment"
Catfish-Man Feb 14, 2025
283e8d6
Reapply "Turn on single issuer as an experiment"
Catfish-Man Feb 14, 2025
d895f2a
Revert "Reapply "Turn on single issuer as an experiment""
Catfish-Man Feb 14, 2025
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
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,4 @@ let package = Package(
dependencies: ["SystemPackage"],
cSettings: cSettings,
swiftSettings: swiftSettings),
])
])
1 change: 1 addition & 0 deletions Sources/CSystem/include/CSystemLinux.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,6 @@
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
#include "io_uring.h"
#endif

55 changes: 55 additions & 0 deletions Sources/CSystem/include/io_uring.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/uio.h>

#include <signal.h>
#include <linux/io_uring.h>

#ifndef SWIFT_IORING_C_WRAPPER
#define SWIFT_IORING_C_WRAPPER

#ifdef __alpha__
/*
* alpha is the only exception, all other architectures
* have common numbers for new system calls.
*/
# ifndef __NR_io_uring_setup
# define __NR_io_uring_setup 535
# endif
# ifndef __NR_io_uring_enter
# define __NR_io_uring_enter 536
# endif
# ifndef __NR_io_uring_register
# define __NR_io_uring_register 537
# endif
#else /* !__alpha__ */
# ifndef __NR_io_uring_setup
# define __NR_io_uring_setup 425
# endif
# ifndef __NR_io_uring_enter
# define __NR_io_uring_enter 426
# endif
# ifndef __NR_io_uring_register
# define __NR_io_uring_register 427
# endif
#endif

int io_uring_register(int fd, unsigned int opcode, void *arg,
unsigned int nr_args)
{
return syscall(__NR_io_uring_register, fd, opcode, arg, nr_args);
}

int io_uring_setup(unsigned int entries, struct io_uring_params *p)
{
return syscall(__NR_io_uring_setup, entries, p);
}

int io_uring_enter(int fd, unsigned int to_submit, unsigned int min_complete,
unsigned int flags, sigset_t *sig)
{
return syscall(__NR_io_uring_enter, fd, to_submit, min_complete,
flags, sig, _NSIG / 8);
}

#endif
18 changes: 0 additions & 18 deletions Sources/CSystem/shims.c

This file was deleted.

166 changes: 166 additions & 0 deletions Sources/System/AsyncFileDescriptor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
@_implementationOnly import CSystem

public struct AsyncFileDescriptor: ~Copyable {
@usableFromInline var open: Bool = true
@usableFromInline let fileSlot: IORingFileSlot
Copy link
Member Author

Choose a reason for hiding this comment

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

Ideally this should be a borrow of the file slot, but that's hard to express right now

@usableFromInline let ring: ManagedIORing

public static func openat(
atDirectory: FileDescriptor = FileDescriptor(rawValue: -100),
path: FilePath,
_ mode: FileDescriptor.AccessMode,
options: FileDescriptor.OpenOptions = FileDescriptor.OpenOptions(),
permissions: FilePermissions? = nil,
onRing ring: ManagedIORing
) async throws -> AsyncFileDescriptor {
// todo; real error type
guard let fileSlot = ring.getFileSlot() else {
throw IORingError.missingRequiredFeatures
}
let cstr = path.withCString {
return $0 // bad
}
let res = await ring.submitAndWait(
.openat(
atDirectory: atDirectory,
path: cstr,
mode,
options: options,
permissions: permissions,
intoSlot: fileSlot.borrow()
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a hack, this should also be an actual borrow

))
if res.result < 0 {
throw Errno(rawValue: -res.result)
}

return AsyncFileDescriptor(
fileSlot, ring: ring
)
}

internal init(_ fileSlot: consuming IORingFileSlot, ring: ManagedIORing) {
Copy link
Member Author

Choose a reason for hiding this comment

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

more "should be a borrow"

self.fileSlot = consume fileSlot
self.ring = ring
}

@inlinable @inline(__always)
public consuming func close(isolation actor: isolated (any Actor)? = #isolation) async throws {
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 want to review the heavy use of isolation parameters at some point, as well as inlinability

let res = await ring.submitAndWait(
.close(
.registered(self.fileSlot)
))
if res.result < 0 {
throw Errno(rawValue: -res.result)
}
self.open = false
}

@inlinable @inline(__always)
public func read(
into buffer: inout UnsafeMutableRawBufferPointer,
atAbsoluteOffset offset: UInt64 = UInt64.max,
isolation actor: isolated (any Actor)? = #isolation
) async throws -> UInt32 {
let file = fileSlot.borrow()
Copy link
Member Author

Choose a reason for hiding this comment

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

more hacks

let res = await ring.submitAndWait(
.readUnregistered(
file: .registered(file),
buffer: buffer,
offset: offset
))
if res.result < 0 {
throw Errno(rawValue: -res.result)
} else {
return UInt32(bitPattern: res.result)
}
}

@inlinable @inline(__always)
public func read(
into buffer: borrowing IORingBuffer, //TODO: should be inout?
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 think inout is right here. We want to loan the buffer to the kernel to read into basically… right?

atAbsoluteOffset offset: UInt64 = UInt64.max,
isolation actor: isolated (any Actor)? = #isolation
) async throws -> UInt32 {
let res = await ring.submitAndWait(
.read(
file: .registered(self.fileSlot.borrow()),
Copy link
Member Author

Choose a reason for hiding this comment

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

borrow hacks

buffer: buffer.borrow(),
offset: offset
))
if res.result < 0 {
throw Errno(rawValue: -res.result)
} else {
return UInt32(bitPattern: res.result)
}
}

//TODO: temporary workaround until AsyncSequence supports ~Copyable
public consuming func toBytes() -> AsyncFileDescriptorSequence {
AsyncFileDescriptorSequence(self)
}

//TODO: can we do the linear types thing and error if they don't consume it manually?
Copy link
Member Author

Choose a reason for hiding this comment

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

Can we express this? Need to go look over the relevant S-E proposals again

// deinit {
// if self.open {
// TODO: close or error? TBD
// }
// }
}

public class AsyncFileDescriptorSequence: AsyncSequence {
Copy link
Member Author

Choose a reason for hiding this comment

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

This is only here because ~Copyable structs can't conform to AsyncSequence rn

var descriptor: AsyncFileDescriptor?

public func makeAsyncIterator() -> FileIterator {
return .init(descriptor.take()!)
}

internal init(_ descriptor: consuming AsyncFileDescriptor) {
self.descriptor = consume descriptor
}

public typealias AsyncIterator = FileIterator
public typealias Element = UInt8
}

//TODO: only a class due to ~Copyable limitations
Copy link
Member Author

Choose a reason for hiding this comment

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

Same here

public class FileIterator: AsyncIteratorProtocol {
@usableFromInline let file: AsyncFileDescriptor
@usableFromInline var buffer: IORingBuffer
@usableFromInline var done: Bool

@usableFromInline internal var currentByte: UnsafeRawPointer?
@usableFromInline internal var lastByte: UnsafeRawPointer?

init(_ file: consuming AsyncFileDescriptor) {
self.buffer = file.ring.getBuffer()!
self.file = file
self.done = false
}

@inlinable @inline(__always)
public func nextBuffer() async throws {
let bytesRead = Int(try await file.read(into: buffer))
if _fastPath(bytesRead != 0) {
let unsafeBuffer = buffer.unsafeBuffer
let bufPointer = unsafeBuffer.baseAddress.unsafelyUnwrapped
self.currentByte = UnsafeRawPointer(bufPointer)
self.lastByte = UnsafeRawPointer(bufPointer.advanced(by: bytesRead))
} else {
done = true
}
}

@inlinable @inline(__always)
public func next() async throws -> UInt8? {
Copy link
Member Author

Choose a reason for hiding this comment

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

For some reason changing this to take an isolation parameter makes it not compile

if _fastPath(currentByte != lastByte) {
// SAFETY: both pointers should be non-nil if they're not equal
let byte = currentByte.unsafelyUnwrapped.load(as: UInt8.self)
currentByte = currentByte.unsafelyUnwrapped + 1
return byte
} else if done {
return nil
}
try await nextBuffer()
return try await next()
}
}
51 changes: 51 additions & 0 deletions Sources/System/IOCompletion.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
@_implementationOnly import CSystem

//TODO: should be ~Copyable, but requires UnsafeContinuation add ~Copyable support
Copy link
Member Author

Choose a reason for hiding this comment

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

Another workaround for missing features

public struct IOCompletion {
let rawValue: io_uring_cqe
}

extension IOCompletion {
public struct Flags: OptionSet, Hashable, Codable {
public let rawValue: UInt32

public init(rawValue: UInt32) {
self.rawValue = rawValue
}

public static let allocatedBuffer = Flags(rawValue: 1 << 0)
public static let moreCompletions = Flags(rawValue: 1 << 1)
public static let socketNotEmpty = Flags(rawValue: 1 << 2)
public static let isNotificationEvent = Flags(rawValue: 1 << 3)
}
}

extension IOCompletion {
public var userData: UInt64 {
get {
return rawValue.user_data
}
}

public var result: Int32 {
get {
return rawValue.res
}
}

public var flags: IOCompletion.Flags {
get {
return Flags(rawValue: rawValue.flags & 0x0000FFFF)
}
}

public var bufferIndex: UInt16? {
get {
if self.flags.contains(.allocatedBuffer) {
return UInt16(rawValue.flags >> 16)
} else {
return nil
}
}
}
}
Loading