#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_sqe_mixed"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — mixed-size SQEs and 128-byte opcodes
* (`IORING_SETUP_SQE_MIXED` + `IORING_OP_NOP128`, Linux 6.19).
*
* Historically a ring's submission queue entries (SQEs) were a fixed stride:
* either all 64 bytes, or — with `IORING_SETUP_SQE128` (6.16) — all 128 bytes.
* `IORING_SETUP_SQE_MIXED` (6.19) lets a single ring carry *both*: ordinary
* 64-byte SQEs sit alongside 128-byte SQEs, with the kernel and the userspace
* library tracking the per-slot stride. `IORING_OP_NOP128` is the simplest op
* that *requires* the wide layout — a `NOP` that occupies a 128-byte slot —
* making it the canonical smoke test for the feature.
*
* This example sets up a mixed ring, emits one plain 64-byte `NOP` and one
* 128-byte `NOP128` (reserved via `next128()`, which claims the two contiguous
* SQ slots a 128-byte entry needs), submits them together, and reaps both CQEs.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md
* § "6.19 — Mixed-size SQE and 128-byte opcodes".
*
* Run with: `dub run --single sqe-mixed.d`
*
* Portability: `SQE_MIXED`/`NOP128` land in Linux 6.19. On an older kernel
* `io_uring_setup` rejects the flag with `-EINVAL` (or the op completes with
* `-EINVAL`/`-EOPNOTSUPP`); the program then prints a `SKIP:` line and exits 0
* so it stays green on hosts predating the feature. It is also green when the
* host has no `io_uring` at all.
*/
module (module) io_uring_sqe_mixedio_uring — mixed-size SQEs and 128-byte opcodes
(IORING_SETUP_SQE_MIXED + IORING_OP_NOP128, Linux 6.19).
Historically a ring's submission queue entries (SQEs) were a fixed stride:
either all 64 bytes, or — with IORING_SETUP_SQE128 (6.16) — all 128 bytes.
IORING_SETUP_SQE_MIXED (6.19) lets a single ring carry both: ordinary
64-byte SQEs sit alongside 128-byte SQEs, with the kernel and the userspace
library tracking the per-slot stride. IORING_OP_NOP128 is the simplest op
that requires the wide layout — a NOP that occupies a 128-byte slot —
making it the canonical smoke test for the feature.
This example sets up a mixed ring, emits one plain 64-byte NOP and one
128-byte NOP128 (reserved via next128(), which claims the two contiguous
SQ slots a 128-byte entry needs), submits them together, and reaps both CQEs.
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md
§ "6.19 — Mixed-size SQE and 128-byte opcodes".
Run with: dub run --single sqe-mixed.d
Portability
SQE_MIXED/NOP128 land in Linux 6.19. On an older kernel
io_uring_setup rejects the flag with -EINVAL (or the op completes with
-EINVAL/-EOPNOTSUPP); the program then prints a SKIP: line and exits 0
so it stays green on hosts predating the feature. It is also green when the
host has no io_uring at all.
io_uring_sqe_mixed;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
import (package) stdstd.(module) std.stdioCategory Symbols File handles _popen File isFileHandle openNetwork stderr stdin stdout Reading chunks lines readf readfln readln Writing toFile write writef writefln writeln Misc KeepTerminator LockType StdioException
Standard I/O functions that extend core.stdc.stdio. core.stdc.stdio
is publically imported when importing std.stdio.
There are three layers of I/O:
The lowest layer is the operating system layer. The two main schemes are Windows and Posix.
C's stdio.h which unifies the two operating system schemes.
std.stdio, this module, unifies the various stdio.h implementations into
a high level package for D programs.
Source
std/stdio.d
stdio : (alias template) io_uring_sqe_mixed.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
import (package) corecore.(package) core.syssys.(package) core.sys.linuxlinux.(module) core.sys.linux.errnoD header file for GNU/Linux
errno : (alias constant) io_uring_sqe_mixed.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL, (alias constant) io_uring_sqe_mixed.EOPNOTSUPP = int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP;
int int D main()main()
{
// Cookies that ride through the kernel and come back on the CQEs, letting us
// confirm the 64-byte and 128-byte completions are correlated correctly.
enum ulong (constant) ulong io_uring_sqe_mixed.main.cookie64 = 100LUcookie64 = 0x64;
enum ulong (constant) ulong io_uring_sqe_mixed.main.cookie128 = 296LUcookie128 = 0x128;
// `SQE_MIXED` is requested as a setup flag. On a pre-6.19 kernel this is the
// gate that fails first (-EINVAL), giving us a clean SKIP without ever
// touching the wide-SQE code path.
(struct) during.UringMain entry point to work with io_uring.
It hides SubmissionQueue and CompletionQueue behind standard range interface.
We put in SubmissionEntry entries and take out CompletionEntry entries.
Use predefined prepXX methods to fill required fields of SubmissionEntry before put or during putWith.
Note
prepXX functions doesn't touch previous entry state, just fills in operation properties. This is because for
less error prone interface it is cleared automatically when prepared using putWith. So when using on own SubmissionEntry
(outside submission queue), that would be added to the submission queue using put, be sure its cleared if it's
reused for multiple operations.
Uring (local variable) during.Uring ioio;
const (local variable) const(int) setupRetsetupRet = (local variable) during.Uring ioio.int during.setup(ref during.Uring uring, uint entries = 128u, during.io_uring.SetupFlags flags = SetupFlags.NONE) nothrow @nogc @safeSetup new instance of io_uring into provided Uring structure.
setup(8, (enum) during.io_uring.SetupFlagsio_uring_setup() flags
SetupFlags.(enum value) during.io_uring.SetupFlags.SQE_MIXED = 524288uIORING_SETUP_SQE_MIXED (from Linux 6.18)
Allow a mix of 64- and 128-byte SQEs in the same ring.
SQE_MIXED);
if ((local variable) const(int) setupRetsetupRet == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (local variable) const(int) setupRetsetupRet == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: IORING_SETUP_SQE_MIXED unsupported (errno %d) — needs Linux 6.19+",
-(local variable) const(int) setupRetsetupRet);
return 0;
}
if ((local variable) const(int) setupRetsetupRet < 0)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: io_uring_setup failed (errno %d) — io_uring unavailable on this host",
-(local variable) const(int) setupRetsetupRet);
return 0;
}
// A normal 64-byte SQE: `putWith` reserves one slot and runs the prep callback.
(local variable) during.Uring ioio.putWith!((ref SubmissionEntry e) {
e.prepNop();
e.user_data = cookie64;
})();
// A 128-byte SQE: `next128()` reserves the two contiguous SQ slots a wide
// entry needs (on an SQE_MIXED ring) and returns a reference to fill in.
// `prepNop128()` is the 128-byte NOP opcode (IORING_OP_NOP128).
auto (local variable) during.io_uring.SubmissionEntry* sqe128sqe128 = &(local variable) during.Uring ioio.during.io_uring.SubmissionEntry during.Uring.next128!()() pure nothrow @nogc ref @safeAdvances the userspace submission queue and returns a slot suitable for a 128-byte SQE
(Operation.NOP128 / Operation.URING_CMD128). On SetupFlags.SQE128 rings this is
equivalent to next; on SetupFlags.SQE_MIXED rings it reserves two contiguous 64-byte
slots, inserting a skipped NOP if needed to avoid wrapping a 128-byte SQE over the ring end.
next128();
(*(local variable) during.io_uring.SubmissionEntry* sqe128sqe128).during.io_uring.SubmissionEntry during.prepNop128(return ref during.io_uring.SubmissionEntry entry) nothrow @nogc ref @safePrepares a 128-byte nop (IORING_OP_NOP128). Only meaningful on rings created with
SetupFlags.SQE128 — exposes the second-half SQE payload for kernels/code that need to
exercise the SQE128 layout.
Note
Available from Linux 6.16
prepNop128();
(local variable) during.io_uring.SubmissionEntry* sqe128sqe128.(field) ulong during.io_uring.SubmissionEntry.user_datadata to be passed back at completion time
user_data = (constant) ulong io_uring_sqe_mixed.main.cookie128 = 296LUcookie128;
// We asked for 2 logical ops; the wide one occupies an extra SQ slot, so the
// kernel sees 3 submission slots. We only assert success, not the slot count.
const (local variable) const(int) submittedsubmitted = (local variable) during.Uring ioio.int during.Uring.submit(uint want) nothrow @nogc @safeSubmits qued SubmissionEntry to be processed by kernel.
submit(2);
if ((local variable) const(int) submittedsubmitted < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("submit failed: errno %d", -(local variable) const(int) submittedsubmitted);
return 1;
}
// Reap both completions. Bound the loop so a misbehaving kernel can't hang us.
bool (local variable) bool sawNopsawNop;
bool (local variable) bool sawNop128sawNop128;
foreach ((local variable) int ii; 0 .. 2)
{
(local variable) during.Uring ioio.int during.Uring.wait(uint want = 1u) nothrow @nogcSimmilar to submit but with this method we just wait for required number
of CompletionEntries.
wait(1);
const (local variable) const(int) resres = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) int during.io_uring.CompletionEntry.resresult code for this event
res;
const (local variable) const(ulong) echoedechoed = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front.(field) ulong during.io_uring.CompletionEntry.user_datasqe->data submission passed back
user_data;
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
// A kernel that knows SQE_MIXED setup but not the NOP128 op (or rejects
// the wide layout) reports it per-op here — still an expected SKIP.
if ((local variable) const(int) resres == -(constant) int core.stdc.errno.EINVAL = 22EINVAL || (local variable) const(int) resres == -(constant) int core.stdc.errno.EOPNOTSUPP = 95EOPNOTSUPP)
{
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: NOP128/SQE_MIXED op unsupported (errno %d) — needs Linux 6.19+",
-(local variable) const(int) resres);
return 0;
}
if ((local variable) const(int) resres < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("op completed with error: errno %d", -(local variable) const(int) resres);
return 1;
}
if ((local variable) const(ulong) echoedechoed == (constant) ulong io_uring_sqe_mixed.main.cookie64 = 100LUcookie64)
(local variable) bool sawNopsawNop = true;
else if ((local variable) const(ulong) echoedechoed == (constant) ulong io_uring_sqe_mixed.main.cookie128 = 296LUcookie128)
(local variable) bool sawNop128sawNop128 = true;
else
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("unexpected user_data: 0x%X", (local variable) const(ulong) echoedechoed);
return 1;
}
}
if (!(local variable) bool sawNopsawNop || !(local variable) bool sawNop128sawNop128)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("missing completion (nop=%s nop128=%s)", (local variable) bool sawNopsawNop, (local variable) bool sawNop128sawNop128);
return 1;
}
void std.stdio.writefln!(char, ulong, ulong)(in char[] fmt, ulong __param_1, ulong __param_2) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: SQE_MIXED ring completed a 64-byte NOP (0x%X) and a 128-byte NOP128 (0x%X)",
(constant) ulong io_uring_sqe_mixed.main.cookie64 = 100LUcookie64, (constant) ulong io_uring_sqe_mixed.main.cookie128 = 296LUcookie128);
return 0;
}