hare

The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

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:
Aiobus/README | 1+
Aiobus/io_uring/bus.ha | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aiobus/io_uring/ops.ha | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aiobus/io_uring/types.ha | 30++++++++++++++++++++++++++++++
Mlinux/io_uring/uring.ha | 3+--
Mscripts/gen-stdlib | 9+++++++++
Mstdlib.mk | 32++++++++++++++++++++++++++++++++
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, &params)) { + 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 \