feat(udp): Udp.* - polled IPv4 datagrams on macOS and Windows
Udp.open/port/send/recv/from_ip/from_port/close/resolve/local_ip/ip/ip_text, native BSD sockets (udp.ll) and Winsock (udp_win.ll, -lws2_32), linked only when a program uses Udp.*; example, docs and tests. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
6a7f1dd393
commit
ebb1a00352
24 changed files with 68630 additions and 66360 deletions
258
runtime/native/udp.ll
Normal file
258
runtime/native/udp.ll
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
; ============================================================================
|
||||
; udp.ll — UDP datagrams behind Udp.* (macOS / POSIX, BSD sockets).
|
||||
;
|
||||
; Linked into any program that uses Udp.* (selfhost/main.ludic, tools/ludic-cli/build.ludic).
|
||||
; udp_win.ll is the same interface over Winsock.
|
||||
;
|
||||
; lu_udp_open(port) -> int a non-blocking IPv4 socket bound to 0.0.0.0:port
|
||||
; (0 picks one); a handle 1..16, or 0 on failure
|
||||
; lu_udp_port(h) -> int the port it is bound to
|
||||
; lu_udp_send(h, ip, port, buf, len) one datagram; bytes sent, or -1
|
||||
; lu_udp_recv(h, buf, cap, ipport) one waiting datagram into buf: its length, or 0 when
|
||||
; none is waiting; the sender's ip and port go to
|
||||
; ipport[0] and ipport[1] (two i32s)
|
||||
; lu_udp_close(h)
|
||||
; lu_udp_resolve(name) -> int the first IPv4 address of a host name, or 0
|
||||
; lu_udp_local_ip() -> int the address this machine reaches the internet from
|
||||
; (no packet is sent), or 0
|
||||
;
|
||||
; An address is a host-order i32: a.b.c.d is (a << 24) | (b << 16) | (c << 8) | d.
|
||||
; The handle table keeps the file descriptors, so a handle is the same small number on both
|
||||
; platforms (a Winsock SOCKET is 64 bits wide).
|
||||
; ============================================================================
|
||||
|
||||
declare i32 @socket(i32, i32, i32)
|
||||
declare i32 @bind(i32, ptr, i32)
|
||||
declare i32 @connect(i32, ptr, i32)
|
||||
declare i32 @getsockname(i32, ptr, ptr)
|
||||
declare i32 @fcntl(i32, i32, ...)
|
||||
declare i64 @sendto(i32, ptr, i64, i32, ptr, i32)
|
||||
declare i64 @recvfrom(i32, ptr, i64, i32, ptr, ptr)
|
||||
declare i32 @close(i32)
|
||||
declare i32 @getaddrinfo(ptr, ptr, ptr, ptr)
|
||||
declare void @freeaddrinfo(ptr)
|
||||
declare i32 @llvm.bswap.i32(i32)
|
||||
declare i16 @llvm.bswap.i16(i16)
|
||||
|
||||
@LU_fd = internal global [16 x i32] zeroinitializer ; fd + 1 per handle, 0 = free
|
||||
|
||||
; sockaddr_in (BSD): len, family, port (network order), addr (network order), 8 zero
|
||||
define internal void @lu_addr(ptr %sa, i32 %ip, i32 %port) {
|
||||
entry:
|
||||
store i64 0, ptr %sa
|
||||
%z8 = getelementptr i8, ptr %sa, i64 8
|
||||
store i64 0, ptr %z8
|
||||
store i8 16, ptr %sa
|
||||
%fam = getelementptr i8, ptr %sa, i64 1
|
||||
store i8 2, ptr %fam
|
||||
%p16 = trunc i32 %port to i16
|
||||
%pn = call i16 @llvm.bswap.i16(i16 %p16)
|
||||
%pp = getelementptr i8, ptr %sa, i64 2
|
||||
store i16 %pn, ptr %pp
|
||||
%in = call i32 @llvm.bswap.i32(i32 %ip)
|
||||
%ap = getelementptr i8, ptr %sa, i64 4
|
||||
store i32 %in, ptr %ap
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal i32 @lu_fd_of(i32 %h) {
|
||||
entry:
|
||||
%lo = icmp slt i32 %h, 1
|
||||
%hi = icmp sgt i32 %h, 16
|
||||
%bad = or i1 %lo, %hi
|
||||
br i1 %bad, label %none, label %look
|
||||
look:
|
||||
%i = sub i32 %h, 1
|
||||
%slot = getelementptr [16 x i32], ptr @LU_fd, i32 0, i32 %i
|
||||
%v = load i32, ptr %slot
|
||||
%fd = sub i32 %v, 1
|
||||
ret i32 %fd
|
||||
none:
|
||||
ret i32 -1
|
||||
}
|
||||
|
||||
define i32 @lu_udp_open(i32 %port) {
|
||||
entry:
|
||||
%sa = alloca [16 x i8]
|
||||
%fd = call i32 @socket(i32 2, i32 2, i32 17)
|
||||
%bad = icmp slt i32 %fd, 0
|
||||
br i1 %bad, label %fail, label %bindit
|
||||
bindit:
|
||||
call void @lu_addr(ptr %sa, i32 0, i32 %port)
|
||||
%b = call i32 @bind(i32 %fd, ptr %sa, i32 16)
|
||||
%bb = icmp ne i32 %b, 0
|
||||
br i1 %bb, label %closefail, label %nonblock
|
||||
nonblock:
|
||||
%fl = call i32 (i32, i32, ...) @fcntl(i32 %fd, i32 3, i32 0)
|
||||
%nb = or i32 %fl, 4
|
||||
%s = call i32 (i32, i32, ...) @fcntl(i32 %fd, i32 4, i32 %nb)
|
||||
br label %find
|
||||
find:
|
||||
%i = phi i32 [ 0, %nonblock ], [ %inext, %next ]
|
||||
%done = icmp sge i32 %i, 16
|
||||
br i1 %done, label %closefail, label %check
|
||||
check:
|
||||
%slot = getelementptr [16 x i32], ptr @LU_fd, i32 0, i32 %i
|
||||
%v = load i32, ptr %slot
|
||||
%free = icmp eq i32 %v, 0
|
||||
br i1 %free, label %take, label %next
|
||||
next:
|
||||
%inext = add i32 %i, 1
|
||||
br label %find
|
||||
take:
|
||||
%fd1 = add i32 %fd, 1
|
||||
store i32 %fd1, ptr %slot
|
||||
%h = add i32 %i, 1
|
||||
ret i32 %h
|
||||
closefail:
|
||||
%c = call i32 @close(i32 %fd)
|
||||
br label %fail
|
||||
fail:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define i32 @lu_udp_port(i32 %h) {
|
||||
entry:
|
||||
%sa = alloca [16 x i8]
|
||||
%len = alloca i32
|
||||
store i32 16, ptr %len
|
||||
%fd = call i32 @lu_fd_of(i32 %h)
|
||||
%bad = icmp slt i32 %fd, 0
|
||||
br i1 %bad, label %none, label %ask
|
||||
ask:
|
||||
%r = call i32 @getsockname(i32 %fd, ptr %sa, ptr %len)
|
||||
%rb = icmp ne i32 %r, 0
|
||||
br i1 %rb, label %none, label %read
|
||||
read:
|
||||
%pp = getelementptr i8, ptr %sa, i64 2
|
||||
%pn = load i16, ptr %pp
|
||||
%ph = call i16 @llvm.bswap.i16(i16 %pn)
|
||||
%p = zext i16 %ph to i32
|
||||
ret i32 %p
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define i32 @lu_udp_send(i32 %h, i32 %ip, i32 %port, ptr %buf, i32 %n) {
|
||||
entry:
|
||||
%sa = alloca [16 x i8]
|
||||
%fd = call i32 @lu_fd_of(i32 %h)
|
||||
%bad = icmp slt i32 %fd, 0
|
||||
br i1 %bad, label %none, label %go
|
||||
go:
|
||||
call void @lu_addr(ptr %sa, i32 %ip, i32 %port)
|
||||
%n64 = sext i32 %n to i64
|
||||
%r = call i64 @sendto(i32 %fd, ptr %buf, i64 %n64, i32 0, ptr %sa, i32 16)
|
||||
%r32 = trunc i64 %r to i32
|
||||
ret i32 %r32
|
||||
none:
|
||||
ret i32 -1
|
||||
}
|
||||
|
||||
define i32 @lu_udp_recv(i32 %h, ptr %buf, i32 %cap, ptr %ipport) {
|
||||
entry:
|
||||
%sa = alloca [16 x i8]
|
||||
%len = alloca i32
|
||||
store i32 16, ptr %len
|
||||
%fd = call i32 @lu_fd_of(i32 %h)
|
||||
%bad = icmp slt i32 %fd, 0
|
||||
br i1 %bad, label %none, label %go
|
||||
go:
|
||||
%c64 = sext i32 %cap to i64
|
||||
%r = call i64 @recvfrom(i32 %fd, ptr %buf, i64 %c64, i32 0, ptr %sa, ptr %len)
|
||||
%got = icmp sgt i64 %r, 0
|
||||
br i1 %got, label %from, label %none
|
||||
from:
|
||||
%ap = getelementptr i8, ptr %sa, i64 4
|
||||
%an = load i32, ptr %ap
|
||||
%ah = call i32 @llvm.bswap.i32(i32 %an)
|
||||
store i32 %ah, ptr %ipport
|
||||
%pp = getelementptr i8, ptr %sa, i64 2
|
||||
%pn = load i16, ptr %pp
|
||||
%ph = call i16 @llvm.bswap.i16(i16 %pn)
|
||||
%p = zext i16 %ph to i32
|
||||
%o = getelementptr i8, ptr %ipport, i64 4
|
||||
store i32 %p, ptr %o
|
||||
%r32 = trunc i64 %r to i32
|
||||
ret i32 %r32
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define void @lu_udp_close(i32 %h) {
|
||||
entry:
|
||||
%fd = call i32 @lu_fd_of(i32 %h)
|
||||
%bad = icmp slt i32 %fd, 0
|
||||
br i1 %bad, label %done, label %go
|
||||
go:
|
||||
%c = call i32 @close(i32 %fd)
|
||||
%i = sub i32 %h, 1
|
||||
%slot = getelementptr [16 x i32], ptr @LU_fd, i32 0, i32 %i
|
||||
store i32 0, ptr %slot
|
||||
br label %done
|
||||
done:
|
||||
ret void
|
||||
}
|
||||
|
||||
; addrinfo (macOS): flags, family, socktype, protocol (i32 each), addrlen (u32), then
|
||||
; canonname, addr, next (pointers at 24, 32, 40)
|
||||
define i32 @lu_udp_resolve(ptr %name) {
|
||||
entry:
|
||||
%hints = alloca [48 x i8]
|
||||
%res = alloca ptr
|
||||
store ptr null, ptr %res
|
||||
call void @llvm.memset.p0.i64(ptr %hints, i8 0, i64 48, i1 false)
|
||||
%fam = getelementptr i8, ptr %hints, i64 4
|
||||
store i32 2, ptr %fam
|
||||
%st = getelementptr i8, ptr %hints, i64 8
|
||||
store i32 2, ptr %st
|
||||
%r = call i32 @getaddrinfo(ptr %name, ptr null, ptr %hints, ptr %res)
|
||||
%rb = icmp ne i32 %r, 0
|
||||
br i1 %rb, label %none, label %read
|
||||
read:
|
||||
%ai = load ptr, ptr %res
|
||||
%isnull = icmp eq ptr %ai, null
|
||||
br i1 %isnull, label %none, label %addr
|
||||
addr:
|
||||
%sap = getelementptr i8, ptr %ai, i64 32
|
||||
%sa = load ptr, ptr %sap
|
||||
%ap = getelementptr i8, ptr %sa, i64 4
|
||||
%an = load i32, ptr %ap
|
||||
%ah = call i32 @llvm.bswap.i32(i32 %an)
|
||||
call void @freeaddrinfo(ptr %ai)
|
||||
ret i32 %ah
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
|
||||
|
||||
; connect a scratch socket toward a public address (UDP: no packet leaves) and read back
|
||||
; the local address the system picked for that route
|
||||
define i32 @lu_udp_local_ip() {
|
||||
entry:
|
||||
%sa = alloca [16 x i8]
|
||||
%len = alloca i32
|
||||
store i32 16, ptr %len
|
||||
%fd = call i32 @socket(i32 2, i32 2, i32 17)
|
||||
%bad = icmp slt i32 %fd, 0
|
||||
br i1 %bad, label %none, label %go
|
||||
go:
|
||||
; 8.8.8.8:53
|
||||
call void @lu_addr(ptr %sa, i32 134744072, i32 53)
|
||||
%c = call i32 @connect(i32 %fd, ptr %sa, i32 16)
|
||||
%cb = icmp ne i32 %c, 0
|
||||
br i1 %cb, label %closenone, label %ask
|
||||
ask:
|
||||
store i32 16, ptr %len
|
||||
%r = call i32 @getsockname(i32 %fd, ptr %sa, ptr %len)
|
||||
%ap = getelementptr i8, ptr %sa, i64 4
|
||||
%an = load i32, ptr %ap
|
||||
%ah = call i32 @llvm.bswap.i32(i32 %an)
|
||||
%x = call i32 @close(i32 %fd)
|
||||
ret i32 %ah
|
||||
closenone:
|
||||
%y = call i32 @close(i32 %fd)
|
||||
br label %none
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
124
runtime/native/udp.ludic
Normal file
124
runtime/native/udp.ludic
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
# ============================================================================
|
||||
# udp.ludic — the Udp.* standard library.
|
||||
#
|
||||
# Plain IPv4 datagrams, polled. A socket is opened on a port (0 lets the system pick one),
|
||||
# datagrams are sent to an address and a port, and each frame the game drains what arrived
|
||||
# with Udp.recv, which never blocks: it returns 0 when nothing is waiting. The sender of the
|
||||
# last datagram read on a socket is Udp.from_ip / Udp.from_port. This is the transport under
|
||||
# a game's own netcode - peer-to-peer play, a LAN lobby, a STUN query - and it carries no
|
||||
# guarantees of its own: datagrams can be lost, repeated and reordered, and a protocol built
|
||||
# on it says what to do about that.
|
||||
#
|
||||
# Like Http.* and Time.now it is OUT-OF-BAND: it depends on the network and the wall clock
|
||||
# and must never feed a deterministic lockstep/replay simulation directly.
|
||||
#
|
||||
# An address is an int: a.b.c.d is (a << 24) | (b << 16) | (c << 8) | d. Udp.ip("10.0.0.2")
|
||||
# and Udp.ip_text(ip) convert, Udp.resolve("example.com") looks a host name up (it blocks
|
||||
# for as long as the system's resolver takes), and Udp.local_ip() is the address this
|
||||
# machine reaches the internet from - the one a player on the same network would dial.
|
||||
#
|
||||
# The transport is native: udp.ll (BSD sockets, macOS) and udp_win.ll (Winsock), linked
|
||||
# only into a program that uses Udp.*.
|
||||
# ============================================================================
|
||||
|
||||
extern function lu_udp_open(port: int) -> int = "lu_udp_open"
|
||||
extern function lu_udp_port(h: int) -> int = "lu_udp_port"
|
||||
extern function lu_udp_send(h: int, ip: int, port: int, buf: pointer, n: int) -> int = "lu_udp_send"
|
||||
extern function lu_udp_recv(h: int, buf: pointer, cap: int, ipport: pointer) -> int = "lu_udp_recv"
|
||||
extern function lu_udp_close(h: int) -> void = "lu_udp_close"
|
||||
extern function lu_udp_resolve(name: pointer) -> int = "lu_udp_resolve"
|
||||
extern function lu_udp_local_ip() -> int = "lu_udp_local_ip"
|
||||
|
||||
const UDP_HANDLES: int = 16
|
||||
|
||||
var u_ready: bool = false
|
||||
var u_ipport: pointer = null # the native side writes the sender here (two i32s)
|
||||
var u_from_ip: words = null # per handle: the sender of the last datagram read
|
||||
var u_from_port: words = null
|
||||
|
||||
function udp_init() -> void {
|
||||
if u_ready { return }
|
||||
u_ipport = bytes(8)
|
||||
u_from_ip = words(UDP_HANDLES + 1)
|
||||
u_from_port = words(UDP_HANDLES + 1)
|
||||
var i = 0
|
||||
while i <= UDP_HANDLES { u_from_ip[i] = 0; u_from_port[i] = 0; i += 1 }
|
||||
u_ready = true
|
||||
}
|
||||
|
||||
# a socket bound to `port` on every interface (0 = any free port): a handle, or 0
|
||||
function udp_open(port: int) -> int {
|
||||
udp_init()
|
||||
if (port < 0) or (port > 65535) { return 0 }
|
||||
return lu_udp_open(port)
|
||||
}
|
||||
# the port a socket is bound to (the one the system picked, for port 0)
|
||||
function udp_port(h: int) -> int { return lu_udp_port(h) }
|
||||
# one datagram of `n` bytes from `buf` to ip:port: the bytes sent, or -1
|
||||
function udp_send(h: int, ip: int, port: int, buf: pointer, n: int) -> int {
|
||||
if (buf == null) or (n <= 0) { return -1 }
|
||||
return lu_udp_send(h, ip, port, buf, n)
|
||||
}
|
||||
# the next waiting datagram, up to `cap` bytes, into `buf`: its length, or 0 when none waits
|
||||
function udp_recv(h: int, buf: pointer, cap: int) -> int {
|
||||
udp_init()
|
||||
if (buf == null) or (cap <= 0) { return 0 }
|
||||
let n = lu_udp_recv(h, buf, cap, u_ipport)
|
||||
if (n > 0) and (h >= 1) and (h <= UDP_HANDLES) {
|
||||
u_from_ip[h] = udp_le32(u_ipport, 0)
|
||||
u_from_port[h] = udp_le32(u_ipport, 4)
|
||||
}
|
||||
return n
|
||||
}
|
||||
function udp_from_ip(h: int) -> int {
|
||||
udp_init()
|
||||
if (h < 1) or (h > UDP_HANDLES) { return 0 }
|
||||
return u_from_ip[h]
|
||||
}
|
||||
function udp_from_port(h: int) -> int {
|
||||
udp_init()
|
||||
if (h < 1) or (h > UDP_HANDLES) { return 0 }
|
||||
return u_from_port[h]
|
||||
}
|
||||
function udp_close(h: int) -> void { lu_udp_close(h) }
|
||||
function udp_resolve(name: pointer) -> int {
|
||||
if name == null { return 0 }
|
||||
return lu_udp_resolve(name)
|
||||
}
|
||||
function udp_local_ip() -> int { return lu_udp_local_ip() }
|
||||
|
||||
# a little-endian i32 out of a byte buffer
|
||||
function udp_le32(p: pointer, o: int) -> int {
|
||||
return p[o] | (p[o + 1] << 8) | (p[o + 2] << 16) | (p[o + 3] << 24)
|
||||
}
|
||||
# "a.b.c.d" to an address; 0 when it is not one
|
||||
function udp_ip(text: pointer) -> int {
|
||||
if text == null { return 0 }
|
||||
var ip = 0
|
||||
var part = 0
|
||||
var digits = 0
|
||||
var dots = 0
|
||||
var i = 0
|
||||
while text[i] != 0 {
|
||||
let c = text[i]
|
||||
if (c >= '0') and (c <= '9') {
|
||||
part = part * 10 + (c - '0')
|
||||
digits += 1
|
||||
if (part > 255) or (digits > 3) { return 0 }
|
||||
} else if c == '.' {
|
||||
if (digits == 0) or (dots == 3) { return 0 }
|
||||
ip = (ip << 8) | part
|
||||
part = 0; digits = 0; dots += 1
|
||||
} else { return 0 }
|
||||
i += 1
|
||||
}
|
||||
if (dots != 3) or (digits == 0) { return 0 }
|
||||
return (ip << 8) | part
|
||||
}
|
||||
function udp_ip_text(ip: int) -> string {
|
||||
let a = (ip >> 24) & 255
|
||||
let b = (ip >> 16) & 255
|
||||
let c = (ip >> 8) & 255
|
||||
let d = ip & 255
|
||||
return `{a}.{b}.{c}.{d}`
|
||||
}
|
||||
253
runtime/native/udp_win.ll
Normal file
253
runtime/native/udp_win.ll
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
; ============================================================================
|
||||
; udp_win.ll — UDP datagrams behind Udp.* (Windows, Winsock 2).
|
||||
;
|
||||
; The interface of udp.ll over ws2_32: WSAStartup on first use, a SOCKET per handle (64 bits,
|
||||
; kept in the table), ioctlsocket(FIONBIO) for non-blocking reads, and the Windows
|
||||
; sockaddr_in (a 16-bit family, no length byte). See udp.ll for the interface.
|
||||
; ============================================================================
|
||||
|
||||
declare i32 @WSAStartup(i16, ptr)
|
||||
declare i64 @socket(i32, i32, i32)
|
||||
declare i32 @bind(i64, ptr, i32)
|
||||
declare i32 @connect(i64, ptr, i32)
|
||||
declare i32 @getsockname(i64, ptr, ptr)
|
||||
declare i32 @ioctlsocket(i64, i32, ptr)
|
||||
declare i32 @sendto(i64, ptr, i32, i32, ptr, i32)
|
||||
declare i32 @recvfrom(i64, ptr, i32, i32, ptr, ptr)
|
||||
declare i32 @closesocket(i64)
|
||||
declare i32 @getaddrinfo(ptr, ptr, ptr, ptr)
|
||||
declare void @freeaddrinfo(ptr)
|
||||
declare i32 @llvm.bswap.i32(i32)
|
||||
declare i16 @llvm.bswap.i16(i16)
|
||||
declare void @llvm.memset.p0.i64(ptr, i8, i64, i1)
|
||||
|
||||
@LU_sock = internal global [16 x i64] zeroinitializer ; SOCKET + 1 per handle, 0 = free
|
||||
@LU_wsa = internal global i32 0
|
||||
@LU_wsadata = internal global [512 x i8] zeroinitializer
|
||||
|
||||
define internal void @lu_start() {
|
||||
entry:
|
||||
%s = load i32, ptr @LU_wsa
|
||||
%ok = icmp ne i32 %s, 0
|
||||
br i1 %ok, label %done, label %go
|
||||
go:
|
||||
%r = call i32 @WSAStartup(i16 514, ptr @LU_wsadata)
|
||||
store i32 1, ptr @LU_wsa
|
||||
br label %done
|
||||
done:
|
||||
ret void
|
||||
}
|
||||
|
||||
; sockaddr_in (Winsock): family (u16), port (network order), addr (network order), 8 zero
|
||||
define internal void @lu_addr(ptr %sa, i32 %ip, i32 %port) {
|
||||
entry:
|
||||
store i64 0, ptr %sa
|
||||
%z8 = getelementptr i8, ptr %sa, i64 8
|
||||
store i64 0, ptr %z8
|
||||
store i16 2, ptr %sa
|
||||
%p16 = trunc i32 %port to i16
|
||||
%pn = call i16 @llvm.bswap.i16(i16 %p16)
|
||||
%pp = getelementptr i8, ptr %sa, i64 2
|
||||
store i16 %pn, ptr %pp
|
||||
%in = call i32 @llvm.bswap.i32(i32 %ip)
|
||||
%ap = getelementptr i8, ptr %sa, i64 4
|
||||
store i32 %in, ptr %ap
|
||||
ret void
|
||||
}
|
||||
|
||||
define internal i64 @lu_sock_of(i32 %h) {
|
||||
entry:
|
||||
%lo = icmp slt i32 %h, 1
|
||||
%hi = icmp sgt i32 %h, 16
|
||||
%bad = or i1 %lo, %hi
|
||||
br i1 %bad, label %none, label %look
|
||||
look:
|
||||
%i = sub i32 %h, 1
|
||||
%slot = getelementptr [16 x i64], ptr @LU_sock, i32 0, i32 %i
|
||||
%v = load i64, ptr %slot
|
||||
%s = sub i64 %v, 1
|
||||
ret i64 %s
|
||||
none:
|
||||
ret i64 -1
|
||||
}
|
||||
|
||||
define i32 @lu_udp_open(i32 %port) {
|
||||
entry:
|
||||
call void @lu_start()
|
||||
%sa = alloca [16 x i8]
|
||||
%arg = alloca i32
|
||||
%s = call i64 @socket(i32 2, i32 2, i32 17)
|
||||
%bad = icmp eq i64 %s, -1
|
||||
br i1 %bad, label %fail, label %bindit
|
||||
bindit:
|
||||
call void @lu_addr(ptr %sa, i32 0, i32 %port)
|
||||
%b = call i32 @bind(i64 %s, ptr %sa, i32 16)
|
||||
%bb = icmp ne i32 %b, 0
|
||||
br i1 %bb, label %closefail, label %nonblock
|
||||
nonblock:
|
||||
store i32 1, ptr %arg
|
||||
; FIONBIO = 0x8004667E
|
||||
%nb = call i32 @ioctlsocket(i64 %s, i32 -2147195266, ptr %arg)
|
||||
br label %find
|
||||
find:
|
||||
%i = phi i32 [ 0, %nonblock ], [ %inext, %next ]
|
||||
%done = icmp sge i32 %i, 16
|
||||
br i1 %done, label %closefail, label %check
|
||||
check:
|
||||
%slot = getelementptr [16 x i64], ptr @LU_sock, i32 0, i32 %i
|
||||
%v = load i64, ptr %slot
|
||||
%free = icmp eq i64 %v, 0
|
||||
br i1 %free, label %take, label %next
|
||||
next:
|
||||
%inext = add i32 %i, 1
|
||||
br label %find
|
||||
take:
|
||||
%s1 = add i64 %s, 1
|
||||
store i64 %s1, ptr %slot
|
||||
%h = add i32 %i, 1
|
||||
ret i32 %h
|
||||
closefail:
|
||||
%c = call i32 @closesocket(i64 %s)
|
||||
br label %fail
|
||||
fail:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define i32 @lu_udp_port(i32 %h) {
|
||||
entry:
|
||||
%sa = alloca [16 x i8]
|
||||
%len = alloca i32
|
||||
store i32 16, ptr %len
|
||||
%s = call i64 @lu_sock_of(i32 %h)
|
||||
%bad = icmp eq i64 %s, -1
|
||||
br i1 %bad, label %none, label %ask
|
||||
ask:
|
||||
%r = call i32 @getsockname(i64 %s, ptr %sa, ptr %len)
|
||||
%rb = icmp ne i32 %r, 0
|
||||
br i1 %rb, label %none, label %read
|
||||
read:
|
||||
%pp = getelementptr i8, ptr %sa, i64 2
|
||||
%pn = load i16, ptr %pp
|
||||
%ph = call i16 @llvm.bswap.i16(i16 %pn)
|
||||
%p = zext i16 %ph to i32
|
||||
ret i32 %p
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define i32 @lu_udp_send(i32 %h, i32 %ip, i32 %port, ptr %buf, i32 %n) {
|
||||
entry:
|
||||
%sa = alloca [16 x i8]
|
||||
%s = call i64 @lu_sock_of(i32 %h)
|
||||
%bad = icmp eq i64 %s, -1
|
||||
br i1 %bad, label %none, label %go
|
||||
go:
|
||||
call void @lu_addr(ptr %sa, i32 %ip, i32 %port)
|
||||
%r = call i32 @sendto(i64 %s, ptr %buf, i32 %n, i32 0, ptr %sa, i32 16)
|
||||
ret i32 %r
|
||||
none:
|
||||
ret i32 -1
|
||||
}
|
||||
|
||||
define i32 @lu_udp_recv(i32 %h, ptr %buf, i32 %cap, ptr %ipport) {
|
||||
entry:
|
||||
%sa = alloca [16 x i8]
|
||||
%len = alloca i32
|
||||
store i32 16, ptr %len
|
||||
%s = call i64 @lu_sock_of(i32 %h)
|
||||
%bad = icmp eq i64 %s, -1
|
||||
br i1 %bad, label %none, label %go
|
||||
go:
|
||||
%r = call i32 @recvfrom(i64 %s, ptr %buf, i32 %cap, i32 0, ptr %sa, ptr %len)
|
||||
%got = icmp sgt i32 %r, 0
|
||||
br i1 %got, label %from, label %none
|
||||
from:
|
||||
%ap = getelementptr i8, ptr %sa, i64 4
|
||||
%an = load i32, ptr %ap
|
||||
%ah = call i32 @llvm.bswap.i32(i32 %an)
|
||||
store i32 %ah, ptr %ipport
|
||||
%pp = getelementptr i8, ptr %sa, i64 2
|
||||
%pn = load i16, ptr %pp
|
||||
%ph = call i16 @llvm.bswap.i16(i16 %pn)
|
||||
%p = zext i16 %ph to i32
|
||||
%o = getelementptr i8, ptr %ipport, i64 4
|
||||
store i32 %p, ptr %o
|
||||
ret i32 %r
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define void @lu_udp_close(i32 %h) {
|
||||
entry:
|
||||
%s = call i64 @lu_sock_of(i32 %h)
|
||||
%bad = icmp eq i64 %s, -1
|
||||
br i1 %bad, label %done, label %go
|
||||
go:
|
||||
%c = call i32 @closesocket(i64 %s)
|
||||
%i = sub i32 %h, 1
|
||||
%slot = getelementptr [16 x i64], ptr @LU_sock, i32 0, i32 %i
|
||||
store i64 0, ptr %slot
|
||||
br label %done
|
||||
done:
|
||||
ret void
|
||||
}
|
||||
|
||||
; addrinfo (Winsock): flags, family, socktype, protocol (i32 each), addrlen (size_t at 16),
|
||||
; then canonname, addr, next (pointers at 24, 32, 40)
|
||||
define i32 @lu_udp_resolve(ptr %name) {
|
||||
entry:
|
||||
call void @lu_start()
|
||||
%hints = alloca [48 x i8]
|
||||
%res = alloca ptr
|
||||
store ptr null, ptr %res
|
||||
call void @llvm.memset.p0.i64(ptr %hints, i8 0, i64 48, i1 false)
|
||||
%fam = getelementptr i8, ptr %hints, i64 4
|
||||
store i32 2, ptr %fam
|
||||
%st = getelementptr i8, ptr %hints, i64 8
|
||||
store i32 2, ptr %st
|
||||
%r = call i32 @getaddrinfo(ptr %name, ptr null, ptr %hints, ptr %res)
|
||||
%rb = icmp ne i32 %r, 0
|
||||
br i1 %rb, label %none, label %read
|
||||
read:
|
||||
%ai = load ptr, ptr %res
|
||||
%isnull = icmp eq ptr %ai, null
|
||||
br i1 %isnull, label %none, label %addr
|
||||
addr:
|
||||
%sap = getelementptr i8, ptr %ai, i64 32
|
||||
%sa = load ptr, ptr %sap
|
||||
%ap = getelementptr i8, ptr %sa, i64 4
|
||||
%an = load i32, ptr %ap
|
||||
%ah = call i32 @llvm.bswap.i32(i32 %an)
|
||||
call void @freeaddrinfo(ptr %ai)
|
||||
ret i32 %ah
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
|
||||
define i32 @lu_udp_local_ip() {
|
||||
entry:
|
||||
call void @lu_start()
|
||||
%sa = alloca [16 x i8]
|
||||
%len = alloca i32
|
||||
%s = call i64 @socket(i32 2, i32 2, i32 17)
|
||||
%bad = icmp eq i64 %s, -1
|
||||
br i1 %bad, label %none, label %go
|
||||
go:
|
||||
call void @lu_addr(ptr %sa, i32 134744072, i32 53)
|
||||
%c = call i32 @connect(i64 %s, ptr %sa, i32 16)
|
||||
%cb = icmp ne i32 %c, 0
|
||||
br i1 %cb, label %closenone, label %ask
|
||||
ask:
|
||||
store i32 16, ptr %len
|
||||
%r = call i32 @getsockname(i64 %s, ptr %sa, ptr %len)
|
||||
%ap = getelementptr i8, ptr %sa, i64 4
|
||||
%an = load i32, ptr %ap
|
||||
%ah = call i32 @llvm.bswap.i32(i32 %an)
|
||||
%x = call i32 @closesocket(i64 %s)
|
||||
ret i32 %ah
|
||||
closenone:
|
||||
%y = call i32 @closesocket(i64 %s)
|
||||
br label %none
|
||||
none:
|
||||
ret i32 0
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue