#!/usr/bin/env dub
/+ dub.sdl:
name "io_uring_cqe_mixed"
dependency "during" version="~>0.5.0"
platforms "linux"
targetPath "build"
+/
/**
* `io_uring` — mixed-size completion queue (`IORING_SETUP_CQE_MIXED`, Linux 6.18).
*
* Before 6.18 a ring had to commit, at setup time, to one CQE size for its
* whole lifetime: either the default 16-byte CQE or — via `IORING_SETUP_CQE32`
* — a 32-byte CQE for *every* completion. The wide form carries 16 extra bytes
* of per-completion payload (used by ops like `URING_CMD`), but paying for it
* ring-wide doubles the CQ memory and the cache footprint of every reaped CQE,
* even the NOPs and reads that never need the extra room.
*
* `IORING_SETUP_CQE_MIXED` removes that all-or-nothing choice: a single ring
* carries a *mix* of 16- and 32-byte CQEs. Each submission decides its own CQE
* width, and the kernel tags the wide ones with `IORING_CQE_F_32`
* (`CQEFlags.F_32`) so the reader can tell them apart while walking the ring.
*
* This example sets up a `CQE_MIXED` ring and submits two NOPs:
* 1. a plain NOP, which completes into a normal 16-byte CQE; and
* 2. a NOP with `IORING_NOP_CQE32` set in its `nop_flags`, which asks the
* kernel for a wide 32-byte CQE — completing with `CQEFlags.F_32` set.
* Seeing both CQE widths reaped from one ring is the whole point: you pay for
* the 32 bytes only on the completion that opted in, not on every CQE.
*
* Companion to the io_uring chronology:
* see docs/research/async-io/io-uring/timeline.md
* § "6.18 — Mixed-size CQE (≈ November 2025, per tree)".
*
* Run with: `dub run --single cqe-mixed.d`
*
* Portability: if the running kernel has no `io_uring` at all, or is older than
* 6.18 and rejects `IORING_SETUP_CQE_MIXED` with `-EINVAL`, the program prints a
* `SKIP:` line and exits 0 so it stays green in CI regardless of host kernel.
*/
module (module) io_uring_cqe_mixedio_uring — mixed-size completion queue (IORING_SETUP_CQE_MIXED, Linux 6.18).
Before 6.18 a ring had to commit, at setup time, to one CQE size for its
whole lifetime: either the default 16-byte CQE or — via IORING_SETUP_CQE32
— a 32-byte CQE for every completion. The wide form carries 16 extra bytes
of per-completion payload (used by ops like URING_CMD), but paying for it
ring-wide doubles the CQ memory and the cache footprint of every reaped CQE,
even the NOPs and reads that never need the extra room.
IORING_SETUP_CQE_MIXED removes that all-or-nothing choice: a single ring
carries a mix of 16- and 32-byte CQEs. Each submission decides its own CQE
width, and the kernel tags the wide ones with IORING_CQE_F_32
(CQEFlags.F_32) so the reader can tell them apart while walking the ring.
This example sets up a CQE_MIXED ring and submits two NOPs:
a plain NOP, which completes into a normal 16-byte CQE; and
a NOP with IORING_NOP_CQE32 set in its nop_flags, which asks the
kernel for a wide 32-byte CQE — completing with CQEFlags.F_32 set.
Seeing both CQE widths reaped from one ring is the whole point: you pay for
the 32 bytes only on the completion that opted in, not on every CQE.
Companion to the io_uring chronology:
see docs/research/async-io/io-uring/timeline.md
§ "6.18 — Mixed-size CQE (≈ November 2025, per tree)".
Run with: dub run --single cqe-mixed.d
Portability
if the running kernel has no io_uring at all, or is older than
6.18 and rejects IORING_SETUP_CQE_MIXED with -EINVAL, the program prints a
SKIP: line and exits 0 so it stays green in CI regardless of host kernel.
io_uring_cqe_mixed;
import (module) duringSimple idiomatic dlang wrapper around linux io_uring
(see: https://kernel.dk/io_uring.pdf) asynchronous API.
during;
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_cqe_mixed.EINVAL = int core.stdc.errno.EINVAL = 22EINVAL;
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_cqe_mixed.writefln = std.stdio.writefln(alias fmt, A...)(A args) if (isSomeString!(typeof(fmt)))Equivalent to writef(fmt, args, '\n').
writefln, stderr;
// `IORING_OP_NOP` flag — ask the kernel to post a wide 32-byte CQE for this NOP.
// Requires the ring to be set up with `CQE32` or `CQE_MIXED`. From Linux 6.18.
// (during exposes this as the `IORING_NOP_CQE32` enum; we name it locally for
// clarity at the call site.)
enum uint (constant) uint io_uring_cqe_mixed.NOP_WANT_CQE32 = 32uNOP_WANT_CQE32 = (constant) uint during.io_uring.IORING_NOP_CQE32 = 32uIORING_OP_NOP flag — post a 32-byte CQE for this NOP. Requires SetupFlags.CQE32 or
CQE_MIXED. From Linux 6.18.
IORING_NOP_CQE32;
int int D main()main()
{
enum ulong (constant) ulong io_uring_cqe_mixed.main.cookieNarrow = 1447446LUcookieNarrow = 0x16_16_16; // tag for the plain (16-byte) CQE
enum ulong (constant) ulong io_uring_cqe_mixed.main.cookieWide = 3289650LUcookieWide = 0x32_32_32; // tag for the wide (32-byte) CQE
(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;
// The only new ingredient vs. the NOP "hello world": the CQE_MIXED setup
// flag. On Linux < 6.18 the kernel rejects this flag with -EINVAL; we treat
// that (and a total lack of io_uring) as a clean SKIP.
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.CQE_MIXED = 262144uIORING_SETUP_CQE_MIXED (from Linux 6.18)
Allow a mix of 16- and 32-byte CQEs in the same ring (per-CQE CQEFlags.F_32 selects).
CQE_MIXED);
if ((local variable) const(int) setupRetsetupRet < 0)
{
if ((local variable) const(int) setupRetsetupRet == -(constant) int core.stdc.errno.EINVAL = 22EINVAL)
void std.stdio.writefln!(char, const(int))(in char[] fmt, const(int) __param_1) @safeEquivalent to writef(fmt, args, '\n').
writefln("SKIP: IORING_SETUP_CQE_MIXED unsupported (kernel < 6.18) — errno %d", -(local variable) const(int) setupRetsetupRet);
else
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;
}
// SQE #1: a plain NOP. With nothing special requested it completes into a
// normal 16-byte CQE — no F_32 tag.
(local variable) during.Uring ioio.putWith!((ref SubmissionEntry e) {
e.prepNop();
e.user_data = cookieNarrow;
})();
// SQE #2: a NOP that opts into a wide CQE. Setting IORING_NOP_CQE32 in the
// op-specific `nop_flags` is what makes *this one* completion 32 bytes wide;
// every other CQE on the ring stays 16 bytes. That per-SQE choice is exactly
// what CQE_MIXED unlocks.
(local variable) during.Uring ioio.putWith!((ref SubmissionEntry e) {
e.prepNop();
e.nop_flags = NOP_WANT_CQE32;
e.user_data = cookieWide;
})();
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. We don't rely on ordering: we match each CQE by its
// user_data cookie and record whether the kernel marked it as a wide (F_32)
// CQE. A NOP that should plainly succeed returning an error is a real bug.
bool (local variable) bool sawNarrowsawNarrow, (local variable) bool sawWidesawWide;
bool (local variable) bool narrowFlaggedWidenarrowFlaggedWide, (local variable) bool wideFlaggedWidewideFlaggedWide;
foreach ((local variable) int __; 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(during.io_uring.CompletionEntry) cqecqe = (local variable) during.Uring ioio.during.io_uring.CompletionEntry during.Uring.front() pure nothrow @nogc return ref @safeGet first CompletionEntry from cq ring
front;
const (local variable) const(bool) isWideisWide = ((local variable) const(during.io_uring.CompletionEntry) cqecqe.(field) during.io_uring.CQEFlags during.io_uring.CompletionEntry.flagsflags & (enum) during.io_uring.CQEFlagsFlags used with CompletionEntry
CQEFlags.(enum value) during.io_uring.CQEFlags.F_32 = 32768uIORING_CQE_F_32 (from Linux 6.18)
Marks a 32-byte CQE in a ring configured with SetupFlags.CQE_MIXED.
F_32) != 0;
if ((local variable) const(during.io_uring.CompletionEntry) cqecqe.(field) int during.io_uring.CompletionEntry.resresult code for this event
res < 0)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("NOP (user_data 0x%X) completed with error: errno %d",
(local variable) const(during.io_uring.CompletionEntry) cqecqe.(field) ulong during.io_uring.CompletionEntry.user_datasqe->data submission passed back
user_data, -(local variable) const(during.io_uring.CompletionEntry) cqecqe.(field) int during.io_uring.CompletionEntry.resresult code for this event
res);
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
return 1;
}
if ((local variable) const(during.io_uring.CompletionEntry) cqecqe.(field) ulong during.io_uring.CompletionEntry.user_datasqe->data submission passed back
user_data == (constant) ulong io_uring_cqe_mixed.main.cookieNarrow = 1447446LUcookieNarrow)
{
(local variable) bool sawNarrowsawNarrow = true;
(local variable) bool narrowFlaggedWidenarrowFlaggedWide = (local variable) const(bool) isWideisWide;
}
else if ((local variable) const(during.io_uring.CompletionEntry) cqecqe.(field) ulong during.io_uring.CompletionEntry.user_datasqe->data submission passed back
user_data == (constant) ulong io_uring_cqe_mixed.main.cookieWide = 3289650LUcookieWide)
{
(local variable) bool sawWidesawWide = true;
(local variable) bool wideFlaggedWidewideFlaggedWide = (local variable) const(bool) isWideisWide;
}
else
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("unexpected user_data on CQE: 0x%X", (local variable) const(during.io_uring.CompletionEntry) cqecqe.(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();
return 1;
}
(local variable) during.Uring ioio.void during.Uring.popFront() pure nothrow @nogc @safeMove to next CompletionEntry
popFront();
}
if (!(local variable) bool sawNarrowsawNarrow || !(local variable) bool sawWidesawWide)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("missing completion(s): sawNarrow=%s sawWide=%s", (local variable) bool sawNarrowsawNarrow, (local variable) bool sawWidesawWide);
return 1;
}
// The plain NOP must NOT be flagged wide; the opted-in NOP MUST be — that
// contrast is the proof that the ring really mixed CQE sizes. (Some kernels
// may legitimately decline to widen a NOP that carries no extra payload; if
// the wide CQE came back narrow we report that rather than failing, since
// the mixed-ring setup itself — the 6.18 feature — already succeeded.)
if ((local variable) bool narrowFlaggedWidenarrowFlaggedWide)
{
stderr.std.stdio.File std.stdio.makeGlobal!"core.stdc.stdio.stderr"() nothrow @nogc @property ref @systemwritefln("plain NOP unexpectedly tagged F_32 (32-byte CQE)");
return 1;
}
if ((local variable) bool wideFlaggedWidewideFlaggedWide)
void std.stdio.writefln!(char, ulong, ulong)(in char[] fmt, ulong __param_1, ulong __param_2) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: CQE_MIXED ring reaped a 16-byte CQE (data 0x%X) and a 32-byte CQE "
~ "(data 0x%X, F_32 set) from one ring — no ring-wide CQE32 doubling needed",
(constant) ulong io_uring_cqe_mixed.main.cookieNarrow = 1447446LUcookieNarrow, (constant) ulong io_uring_cqe_mixed.main.cookieWide = 3289650LUcookieWide);
else
void std.stdio.writefln!char(in char[] fmt) @safeEquivalent to writef(fmt, args, '\n').
writefln("ok: CQE_MIXED ring set up and both NOPs completed (the kernel kept the "
~ "opted-in NOP at 16 bytes; the 6.18 mixed-CQE ring itself works)");
return 0;
}