commit a8cb40960b965934add5bece1c8cfdcfa5f10d82
parent 83586c74edd65b747a111448c93fe178151b164a
Author: Drew DeVault <sir@cmpwn.com>
Date: Tue, 19 Oct 2021 10:01:14 +0200
iobus: initial commit
Signed-off-by: Drew DeVault <sir@cmpwn.com>
Diffstat:
7 files changed, 216 insertions(+), 2 deletions(-)
diff --git a/iobus/README b/iobus/README
@@ -0,0 +1 @@
+The iobus module implements a multiplexer for I/O operations.
diff --git a/iobus/io_uring/bus.ha b/iobus/io_uring/bus.ha
@@ -0,0 +1,81 @@
+use errors;
+use io;
+use linux::io_uring;
+
+// TODO: Better error handling everywhere
+def DEFAULT_RING_SIZE: u32 = 512;
+
+// Creates a new io_uring I/O bus.
+export fn new() (*bus | errors::error) = {
+ let params = io_uring::params { ... };
+ match (io_uring::setup(DEFAULT_RING_SIZE, ¶ms)) {
+ case err: io_uring::error =>
+ return err: error;
+ case ring: io_uring::io_uring =>
+ return alloc(bus {
+ uring = ring,
+ });
+ };
+};
+
+// Destroys an io_uring I/O bus.
+export fn destroy(bus: *bus) void = {
+ io_uring::finish(&bus.uring);
+};
+
+// Submits all pending I/O to the host, without blocking.
+export fn submit(bus: *bus) void = {
+ io_uring::submit(&bus.uring)!;
+};
+
+// Waits until at least one I/O event completes, then returns the result. The
+// caller must call [[done]] with the result when done with it.
+export fn dispatch(bus: *bus) (result | error) = {
+ match (io_uring::peek(&bus.uring)?) {
+ case null => void;
+ case cqe: *io_uring::cqe =>
+ return cqe: result;
+ };
+ match (io_uring::wait(&bus.uring)) {
+ case err: io_uring::error =>
+ return err: error;
+ case cqe: *io_uring::cqe =>
+ return cqe: result;
+ };
+};
+
+// Returns the [[handle]] which this [[result]] corresponds to.
+export fn handleof(res: result) handle = {
+ return io_uring::get_user(res): handle;
+};
+
+// Discards states associated with an [[result]].
+export fn done(bus: *bus, res: result) void = {
+ io_uring::cqe_seen(&bus.uring, res);
+};
+
+// Registers a file with the iobus, returning a [[registered_file]] object to
+// use for I/O operations. It is not necessary to register files to use them for
+// I/O, but it improves performance if you do.
+export fn register_file(bus: *bus, file: io::file) registered_file = {
+ let registered: int = -1;
+ if (bus.lastfd >= len(bus.fdset)) {
+ static const init: [256]int = [-1...];
+ append(bus.fdset, init...);
+ bus.fdset[bus.lastfd] = file;
+ registered = bus.lastfd: int;
+ io_uring::register_files(&bus.uring, bus.fdset)!;
+ } else {
+ let updates = [
+ io_uring::files_update {
+ offs = bus.lastfd: u32,
+ fds = &bus.fdset[bus.lastfd],
+ },
+ ];
+ bus.fdset[bus.lastfd] = file;
+ registered = bus.lastfd: int;
+ io_uring::register_files_update(&bus.uring, updates)!;
+ };
+ for (bus.fdset[bus.lastfd] != -1; bus.lastfd += 1) void;
+ return registered;
+};
diff --git a/iobus/io_uring/ops.ha b/iobus/io_uring/ops.ha
@@ -0,0 +1,62 @@
+use io;
+use linux::io_uring;
+
+// Prepares an asynchronous read operation.
+export fn read(bus: *bus, file: file, buf: []u8) (handle | queuefull) = {
+ let sqe = match (io_uring::get_sqe(&bus.uring)) {
+ case null =>
+ return queuefull;
+ case sqe: *io_uring::sqe =>
+ yield sqe;
+ };
+ let flags = io_uring::flags::NONE;
+ let fd = match (file) {
+ case file: io::file =>
+ yield file: i32;
+ case file: registered_file =>
+ flags |= io_uring::flags::FIXED_FILE;
+ yield file: i32;
+ };
+ io_uring::read(sqe, fd, buf: *[*]u8, len(buf), -1, flags);
+ io_uring::set_user(sqe, sqe);
+ return sqe: handle;
+};
+
+// Returns the result of a completed read operation.
+export fn endread(res: result) (size | io::EOF | error) = {
+ assert(handleof(res).opcode == io_uring::op::READ,
+ "endread called for non-read iobus::result");
+ let z = io_uring::result(res)?: size;
+ if (z == 0) {
+ return io::EOF;
+ };
+ return z;
+};
+
+// Prepares an asynchronous write operation.
+export fn write(bus: *bus, file: file, buf: []u8) (handle | queuefull) = {
+ let sqe = match (io_uring::get_sqe(&bus.uring)) {
+ case null =>
+ return queuefull;
+ case sqe: *io_uring::sqe =>
+ yield sqe;
+ };
+ let flags = io_uring::flags::NONE;
+ let fd = match (file) {
+ case file: io::file =>
+ yield file: i32;
+ case file: registered_file =>
+ flags |= io_uring::flags::FIXED_FILE;
+ yield file: i32;
+ };
+ io_uring::write(sqe, fd, buf: *[*]u8, len(buf), 0, flags);
+ io_uring::set_user(sqe, sqe);
+ return sqe: handle;
+};
+
+// Returns the result of a completed write operation.
+export fn endwrite(res: result) (size | error) = {
+ assert(handleof(res).opcode == io_uring::op::WRITE,
+ "endwrite called for non-write iobus::result");
+ return io_uring::result(res)?: size;
+};
diff --git a/iobus/io_uring/types.ha b/iobus/io_uring/types.ha
@@ -0,0 +1,30 @@
+use errors;
+use io;
+use linux::io_uring;
+
+export type error = io_uring::error;
+
+export fn strerror(err: error) const str = {
+ return io_uring::strerror(err);
+};
+
+export type bus = struct {
+ uring: io_uring::io_uring,
+ fdset: []io::file,
+ lastfd: size,
+};
+
+// The result of a completed I/O operation.
+export type result = *io_uring::cqe;
+
+// An I/O operation handle.
+export type handle = *io_uring::sqe;
+
+export type registered_file = int;
+
+export type file = (registered_file | io::file);
+
+// Returned if the submission queue is full and new I/O submissions are not
+// available. The user should call [[submit]] before attempting to submit new
+// I/O.
+export type queuefull = !void;
diff --git a/linux/io_uring/uring.ha b/linux/io_uring/uring.ha
@@ -267,8 +267,7 @@ export type regop = enum uint {
export type files_update = struct {
offs: u32,
resv: u32,
- // XXX: i32 but aligned to u64
- fds: u64,
+ fds: *int,
};
// Flags for a probe operation.
diff --git a/scripts/gen-stdlib b/scripts/gen-stdlib
@@ -572,6 +572,14 @@ io() {
gen_ssa io strings errors
}
+iobus_io_uring() {
+ gen_srcs iobus::io_uring \
+ bus.ha \
+ types.ha \
+ ops.ha
+ gen_ssa iobus::io_uring errors io linux::io_uring
+}
+
linux() {
gen_srcs linux \
start.ha \
@@ -936,6 +944,7 @@ hash::crc32
hash::crc64
hash::fnv
io
+iobus::io_uring
linux
linux::signalfd
linux::io_uring
diff --git a/stdlib.mk b/stdlib.mk
@@ -240,6 +240,10 @@ hare_stdlib_deps+=$(stdlib_hash_fnv)
stdlib_io=$(HARECACHE)/io/io.o
hare_stdlib_deps+=$(stdlib_io)
+# gen_lib iobus::io_uring
+stdlib_iobus_io_uring=$(HARECACHE)/iobus/io_uring/iobus_io_uring.o
+hare_stdlib_deps+=$(stdlib_iobus_io_uring)
+
# gen_lib linux
stdlib_linux=$(HARECACHE)/linux/linux.o
hare_stdlib_deps+=$(stdlib_linux)
@@ -837,6 +841,18 @@ $(HARECACHE)/io/io.ssa: $(stdlib_io_srcs) $(stdlib_rt) $(stdlib_strings) $(stdli
@HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Nio \
-t$(HARECACHE)/io/io.td $(stdlib_io_srcs)
+# iobus::io_uring
+stdlib_iobus_io_uring_srcs= \
+ $(STDLIB)/iobus/io_uring/bus.ha \
+ $(STDLIB)/iobus/io_uring/types.ha \
+ $(STDLIB)/iobus/io_uring/ops.ha
+
+$(HARECACHE)/iobus/io_uring/iobus_io_uring.ssa: $(stdlib_iobus_io_uring_srcs) $(stdlib_rt) $(stdlib_errors) $(stdlib_io) $(stdlib_linux_io_uring)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(HARECACHE)/iobus/io_uring
+ @HARECACHE=$(HARECACHE) $(HAREC) $(HAREFLAGS) -o $@ -Niobus::io_uring \
+ -t$(HARECACHE)/iobus/io_uring/iobus_io_uring.td $(stdlib_iobus_io_uring_srcs)
+
# linux
stdlib_linux_srcs= \
$(STDLIB)/linux/start.ha \
@@ -1474,6 +1490,10 @@ hare_testlib_deps+=$(testlib_hash_fnv)
testlib_io=$(TESTCACHE)/io/io.o
hare_testlib_deps+=$(testlib_io)
+# gen_lib iobus::io_uring
+testlib_iobus_io_uring=$(TESTCACHE)/iobus/io_uring/iobus_io_uring.o
+hare_testlib_deps+=$(testlib_iobus_io_uring)
+
# gen_lib linux
testlib_linux=$(TESTCACHE)/linux/linux.o
hare_testlib_deps+=$(testlib_linux)
@@ -2094,6 +2114,18 @@ $(TESTCACHE)/io/io.ssa: $(testlib_io_srcs) $(testlib_rt) $(testlib_strings) $(te
@HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Nio \
-t$(TESTCACHE)/io/io.td $(testlib_io_srcs)
+# iobus::io_uring
+testlib_iobus_io_uring_srcs= \
+ $(STDLIB)/iobus/io_uring/bus.ha \
+ $(STDLIB)/iobus/io_uring/types.ha \
+ $(STDLIB)/iobus/io_uring/ops.ha
+
+$(TESTCACHE)/iobus/io_uring/iobus_io_uring.ssa: $(testlib_iobus_io_uring_srcs) $(testlib_rt) $(testlib_errors) $(testlib_io) $(testlib_linux_io_uring)
+ @printf 'HAREC \t$@\n'
+ @mkdir -p $(TESTCACHE)/iobus/io_uring
+ @HARECACHE=$(TESTCACHE) $(HAREC) $(TESTHAREFLAGS) -o $@ -Niobus::io_uring \
+ -t$(TESTCACHE)/iobus/io_uring/iobus_io_uring.td $(testlib_iobus_io_uring_srcs)
+
# linux
testlib_linux_srcs= \
$(STDLIB)/linux/start.ha \