Add library/modules/std/net/epoll.sx — raw epoll bindings, the linux twin of
std/net/kqueue.sx — and branch std.event.Loop on `inline if OS` so the
OS-neutral readiness Loop runs on linux (epoll) as well as darwin (kqueue);
callers never see the backend.
epoll_event has no packed-struct primitive in sx, so it is modelled as an
arch-branched struct of u32 fields — { events, data_lo, data_hi } → 12 bytes on
x86_64 (matching __attribute__((packed))), { events, pad, data_lo, data_hi } →
16 bytes on aarch64 — every field 4-aligned, so the layout is byte-exact for the
kernel ABI with no packed attribute and no unaligned access. The fd is stashed
in data_lo (epoll echoes one data word, not the fd separately).
epoll.sx is self-contained (libc only, no build.sx): the `inline if ARCH`
selecting the struct is resolved by the compiler's flatten pre-pass, so the
module's IR stays small. The epoll backend is imported INSIDE event.sx's
`inline if OS == .linux` branch (not top level): event.sx rides the std.sx
barrel, so a top-level import would register epoll's types into every std
program's type table on darwin and drift every .ir snapshot.
The epoll Loop keeps a small per-fd registration table (combined EPOLLIN/OUT
mask via EPOLL_CTL_ADD/MOD/DEL), maps the fd back to the caller's udata, arms
EPOLLRDHUP so a peer half-close surfaces as Event.eof (matching kqueue EV_EOF),
and uses an eventfd as the cross-thread wake channel (kqueue's EVFILT_USER).
Validation: the kqueue path runs end-to-end on the macOS host (1632 unchanged);
the epoll bindings + ABI layout are corpus-locked ir-only by
examples/event/1633 (x86_64-linux, both arches probe-verified). The epoll Loop
is verified to lower clean for both linux arches and self-reviewed, but is not
corpus-snapshotted (a Loop example drags the std barrel → ~18k-line brittle IR);
runtime behavior validates on a linux runner.
33 lines
1.6 KiB
Plaintext
33 lines
1.6 KiB
Plaintext
// std/net/epoll (the linux twin of std/net/kqueue): the raw bindings lower for
|
|
// a linux target with a byte-exact `epoll_event` layout — 12-byte stride on
|
|
// x86_64 (packed), modelled as an arch-branched `{events, data_lo, data_hi}`
|
|
// struct of u32 fields (no packed attribute, no unaligned access). Exercises
|
|
// create / ctl / wait + the readiness accessors so the IR covers the surface.
|
|
//
|
|
// Imports ONLY epoll.sx (libc-only — no std/build) so the .ir snapshot stays
|
|
// small and churns only when the bindings change. ir-only on the aarch64-macOS
|
|
// dev host (target x86_64-linux mismatches host arch+os → the runner asserts
|
|
// .exit + .ir + .stderr from `sx ir --target`); runtime behavior validates on a
|
|
// linux runner (see the module header's VALIDATION NOTE). The `inline if ARCH`
|
|
// in epoll.sx is resolved by the compiler's flatten pre-pass, so no build.sx.
|
|
ep :: #import "modules/std/net/epoll.sx";
|
|
|
|
main :: () -> i32 {
|
|
epfd := ep.ep_create();
|
|
// register read + peer-close interest on a fd, then drain readiness
|
|
if !ep.ep_ctl(epfd, ep.EPOLL_CTL_ADD, 1, ep.EPOLLIN | ep.EPOLLRDHUP) { return 2; }
|
|
ep.ep_ctl(epfd, ep.EPOLL_CTL_MOD, 1, ep.EPOLLIN | ep.EPOLLOUT);
|
|
|
|
evs : [8]ep.EpollEvent = ---;
|
|
sl : []ep.EpollEvent = .{ ptr = @evs[0], len = 8 };
|
|
n := ep.ep_wait(epfd, sl, 8, 100);
|
|
if n > 0 {
|
|
if ep.ev_readable(evs[0]) { return ep.ev_fd(evs[0]); }
|
|
if ep.ev_writable(evs[0]) { return 4; }
|
|
if ep.ev_eof(evs[0]) { return 9; }
|
|
if ep.ev_err(evs[0]) { return 8; }
|
|
}
|
|
ep.ep_ctl(epfd, ep.EPOLL_CTL_DEL, 1, 0);
|
|
return xx size_of(ep.EpollEvent); // 12 on x86_64
|
|
}
|