Implementing UDP Networking in Wave: Direct recvfrom Syscall
On September 5, 2025, Wave successfully achieved its first UDP network communication.This post documents that milestone. Although Wave is still in a pre-beta stage and currently runs only on Linux x86-64,this experiment proves that Wave can directly ...
Implementing UDP Networking in Wave: Direct recvfrom Syscall
On September 5, 2025, Wave successfully achieved its first UDP network communication.
This post documents that milestone.
Although Wave is still in a pre-beta stage and currently runs only on Linux x86-64,
this experiment proves that Wave can directly interact with the operating system to perform network I/O.
recvfrom in C
Normally, in C, receiving UDP packets looks like this:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
This is a familiar libc function.
But internally, it’s just a thin wrapper that calls the Linux kernel’s syscall.
Inside glibc, the implementation is essentially:
return syscall(SYS_recvfrom, sockfd, buf, len, flags, src_addr, addrlen);
So recvfrom() is not magic — it’s just calling system call number 45.
C vs Wave: Call Flow
C Call Flow
sequenceDiagram
participant C as C Code
participant L as glibc Library
participant K as Linux Kernel
participant N as Network Stack
C->>L: recvfrom call
L->>K: "syscall SYS_recvfrom"
K->>N: forward packet
N-->>K: data
K-->>L: result (n bytes)
L-->>C: result (n bytes)
Wave Call Flow
sequenceDiagram
participant W as Wave Code + inline asm
participant K as Linux Kernel
participant N as Network Stack
W->>K: "mov rax=45"
K->>N: forward packet
N-->>K: data
K-->>W: result (n bytes)
👉 The only difference:
C goes through glibc wrapper
Wave talks to the kernel directly
Final Wave Code
const AF_INET: i32 = 2;
const SOCK_DGRAM: i32 = 2;
const SYS_SOCKET: i64 = 41;
const SYS_BIND: i64 = 49;
const SYS_RECVFROM: i64 = 45;
const SYS_WRITE: i64 = 1;
fun main() {
// 1. Create socket
var sockfd: i64;
asm {
"mov rax, 41" // SYS_SOCKET
"syscall"
out("rax") sockfd
in("rdi") AF_INET
in("rsi") SOCK_DGRAM
in("rdx") 0
}
println("socket fd = {}", sockfd);
// 2. Bind to port 8080
var addr: array<i8, 16> = [2, 0, 0x1F, 0x90, 0,0,0,0, 0,0,0,0, 0,0,0,0];
var ret: i64;
asm {
"mov rax, 49" // SYS_BIND
"syscall"
out("rax") ret
in("rdi") sockfd
in("rsi") &addr
in("rdx") 16
}
println("bind ret = {}", ret);
// 3. Prepare buffer
var buf: array<i8, 128>;
var src: array<i8, 16>;
var srclen: i32 = 16;
// 4. Receive data
var n: i64;
asm {
"mov rax, 45" // SYS_RECVFROM
"syscall"
out("rax") n
in("rdi") sockfd
in("rsi") &buf
in("rdx") 128
in("r10") 0 // flags = 0 (blocking)
in("r8") &src
in("r9") &srclen
}
println("recvfrom got {} bytes", n);
// 5. Write to stdout
var ret2: i64;
asm {
"mov rax, 1" // SYS_WRITE
"syscall"
out("rax") ret2
in("rdi") 1
in("rsi") &buf
in("rdx") n
}
}
How It Works
Socket Creation
CallsSYS_SOCKET (41)with parameters(AF_INET, SOCK_DGRAM)to create a UDP socket.Binding
CallsSYS_BIND (49)to bind the socket to port 8080.
Theaddrarray represents a rawsockaddr_instructure.Receiving Data
CallsSYS_RECVFROM (45)with correct register mapping:rdi = sockfd
rsi = buffer pointer
rdx = buffer length
r10 = flags (0, blocking mode)
r8 = src_addr pointer
r9 = addrlen pointer
Writing Output
CallsSYS_WRITE (1)to print the received data directly to stdout.
Running the Program
Run Wave program:
./wavec run test/test61.waveIn another terminal, send a UDP packet:
echo "hi" | nc -u 127.0.0.1 8080Output:
hi socket fd = 3 bind ret = 0 recvfrom got 3 bytes
Significance
This experiment shows that Wave can now:
Directly call Linux system calls
Implement UDP networking without libc
Receive real packets and interact with the OS network stack
Even though Wave is still pre-beta with no standard library,
we have demonstrated that it can already act as a system programming language.
Future extensions will include:
sendto()for UDP sendingconnect()/accept()for TCPHTTP library built in Wave
Asynchronous I/O for high-performance servers