-
Notifications
You must be signed in to change notification settings - Fork 109
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
base: main
Are you sure you want to change the base?
Changes from 19 commits
15794f5
6338029
996e940
1f821a8
0762f57
d099546
b584504
b596783
6b4084c
baab9b2
6029936
10c070a
32966f9
822e481
fcb0b69
6f793cf
a9f92a6
1a3e37d
f369347
ef94a37
7ea32ae
55fd6e7
e5fdf9e
fdbceca
7107a57
02481e0
bb03f0f
a396967
d4ca412
5bfed03
74366c3
7f6e673
0c6ef16
a22e5f6
6983196
5ba1377
0064649
901f4c8
283e8d6
d895f2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,5 +21,6 @@ | |
#include <pthread.h> | ||
#include <sched.h> | ||
#include <unistd.h> | ||
#include "io_uring.h" | ||
#endif | ||
|
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 |
This file was deleted.
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 | ||
@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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
} | ||
} |
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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