shithub: mc

Download patch

ref: 7c2f25e309226a9506ea4b37e94e560017e83994
parent: 5d5d5e417324785a9fc78ffd14a4ad5393e0fb06
author: iriri <iri@konnichiwastevenspielbergde.su>
date: Fri Jun 22 22:07:49 EDT 2018

Add semaphores and wrapper for various futexesque system calls

--- /dev/null
+++ b/bld.tags
@@ -1,0 +1,4 @@
+futex: freebsd
+futex: linux
+futex: openbsd:6.2
+futex: osx
--- a/lib/std/errno.myr
+++ b/lib/std/errno.myr
@@ -38,4 +38,5 @@
 	const Epipe	: errno = (sys.Epipe	: errno)
 	const Edom	: errno = (sys.Edom	: errno)
 	const Erange	: errno = (sys.Erange	: errno)
+	const Etimedout	: errno = (sys.Etimedout	: errno)
 ;;
--- a/lib/sys/sys+freebsd-x64.myr
+++ b/lib/sys/sys+freebsd-x64.myr
@@ -581,6 +581,13 @@
 		node				: uint8[_Uuidnodesz];
 	;;
 	
+	const Umtxabstime = 1
+	type _umtx_time = struct
+		_timeout	: timespec
+		_flags		: uint32
+		_clockid	: uint32
+	;;
+	
 	/* open options */
 	const Ordonly  	: fdopt = 0x0
 	const Owronly  	: fdopt = 0x1
--- a/lib/sys/sys+osx-x64.myr
+++ b/lib/sys/sys+osx-x64.myr
@@ -18,6 +18,7 @@
 	type machport	= int32
 	type signo	= int32
 	type sigflags	= int32
+	type ulockop	= uint32
 
 	type fdset = struct
 		bits	: int32[1024/4]
@@ -388,6 +389,14 @@
 	const Sigusr1	: signo = 30	/* user defined signal 1 */
 	const Sigusr2	: signo = 31	/* user defined signal 2 */
 
+	/* ulock ops */
+	const Ulockcompareandwait		: ulockop = 0x00000001
+	const Ulockunfairlock			: ulockop = 0x00000002
+	const Ulockulfwakeall			: ulockop = 0x00000100
+	const Ulockulfwakethread		: ulockop = 0x00000200
+	const Ulockwaitworkqdatacontention	: ulockop = 0x00010000
+	const Ulocknoerrno			: ulockop = 0x01000000
+
 	/* syscalls.
 	note, creat() implemented as open(path, Creat|Trunc|Wronly) */
 	const Syssyscall	: scno = 0x2000000
@@ -737,6 +746,8 @@
 	const Syspid_resume		: scno = 0x20001af
 	const Sysfileport_makeport	: scno = 0x20001b0
 	const Sysfileport_makefd	: scno = 0x20001b1
+	const Sysulock_wait		: scno = 0x2000203
+	const Sysulock_wake		: scno = 0x2000204
 
 	extern const syscall : (sc:scno, args:... -> int64)
 
@@ -823,6 +834,10 @@
 		new : void#, newsz : size# \
 		-> int)
 
+	/* ulock */
+	const ulock_wait	: (op : ulockop, uaddr : uint64#, val : uint64, timeout : uint32 -> int)
+	const ulock_wake	: (op : ulockop, uaddr : uint64#, wakeval : uint64 -> int)
+
 	/* filled by start code */
 	extern var __cenvp : byte##
 ;;
@@ -1079,6 +1094,14 @@
 	/* all args already passed through a() or ar  ptrs */
 	-> (syscall(Sys__sysctl, \
 		(mib : int#), a(mib.len), old, oldsz, new, newsz) : int)
+}
+
+const ulock_wait = {op, uaddr, val, timeout
+	-> (syscall(Sysulock_wait, a(op), uaddr, val, a(timeout)) : int)
+}
+
+const ulock_wake = {op, uaddr, wakeval
+	-> (syscall(Sysulock_wake, a(op), uaddr, a(wakeval)) : int)
 }
 
 const waitstatus = {st
--- a/lib/sys/syserrno+linux.myr
+++ b/lib/sys/syserrno+linux.myr
@@ -35,4 +35,5 @@
 	const Epipe	: errno =	-32	/* Broken pipe */
 	const Edom	: errno =	-33	/* Math argument out of domain of func */
 	const Erange	: errno =	-34	/* Math result not representable */
+	const Etimedout	: errno =	-110	/* Operation timed out */
 ;;
--- a/lib/sys/syserrno+osx.myr
+++ b/lib/sys/syserrno+osx.myr
@@ -51,4 +51,5 @@
 	const Eprototype	: errno = -41		/* Protocol wrong type for socket */
 	const Enoprotoopt	: errno = -42		/* Protocol not available */
 	const Eprotonosupport	: errno = -43		/* Protocol not supported */
+	const Etimedout		: errno = -60		/* Operation timed out */
 ;;
--- a/lib/thread/bld.sub
+++ b/lib/thread/bld.sub
@@ -1,7 +1,10 @@
 lib thread =
 	common.myr
 	hookstd.myr	# install thread hooks
+	mutex+futex.myr
+	sem+futex.myr
 	mutex.myr	# fallback, for unimplemented platforms
+	sem.myr		# fallback, for unimplemented platforms
 
 	#generic fallbacks
 	ncpu.myr
@@ -9,7 +12,7 @@
 	# linux impl of basic thread primitives
 	#condvar+linux.myr
 	exit+linux-x64.s
-	mutex+linux.myr
+	futex+linux.myr
 	ncpu+linux.myr
 	spawn+linux.myr
 
@@ -16,7 +19,7 @@
 	# freebsd impl of thread primitives
 	#condvar+freebsd.myr
 	exit+freebsd-x64.s
-	mutex+freebsd.myr
+	futex+freebsd.myr
 	ncpu+freebsd.myr
 	spawn+freebsd.myr
 
@@ -29,6 +32,7 @@
 
 	# osx impl of thread primitives
 	#condvar+osx.myr
+	futex+osx.myr
 	spawn+osx.myr
 	start+osx-x64.s
 
@@ -37,11 +41,12 @@
 	atomic-impl+plan9-x64.s
 	mutex+plan9.myr
 	ncpu+plan9.myr
+	sem+plan9.myr
 	spawn+plan9.myr
 
 	# openbsd impl of thread primitives
 	exit+openbsd-x64.s
-	mutex+openbsd:6.2.myr
+	futex+openbsd:6.2.myr
 	ncpu+openbsd.myr
 	spawn+openbsd.myr
 
--- /dev/null
+++ b/lib/thread/futex+freebsd.myr
@@ -1,0 +1,25 @@
+use sys
+
+use "common"
+
+pkg thread =
+	const ftxwait : (uaddr : uint64#, val : uint64, timeout : sys.timespec# -> int)
+	const ftxwake : (uaddr : uint64# -> int)
+;;
+
+const ftxwait = {uaddr, val, timeout
+	if timeout == Zptr
+		-> sys.umtx_op((uaddr : void#), sys.Umtxwaituintpriv, (val : uint64), Zptr, Zptr)
+	;;
+
+	var ut : sys._umtx_time = [
+		._timeout = timeout#
+		._flags = sys.Umtxabstime
+		._clockid = 1 /* CLOCK_MONOTONIC. Not exported from sys. */
+	]
+	-> sys.umtx_op((uaddr : void#), sys.Umtxwaituintpriv, (val : uint64), (sys.sizeof(sys._umtx_time) : void#), &ut)
+}
+
+const ftxwake = {uaddr
+	-> sys.umtx_op((uaddr : void#), sys.Umtxwakepriv, 1, Zptr, Zptr)
+}
--- /dev/null
+++ b/lib/thread/futex+linux.myr
@@ -1,0 +1,16 @@
+use sys
+
+use "common"
+
+pkg thread =
+	const ftxwait : (uaddr : uint64#, val : uint64, timeout : sys.timespec# -> int)
+	const ftxwake : (uaddr : uint64# -> int)
+;;
+
+const ftxwait = {uaddr, val, timeout
+	-> sys.futex((uaddr : int32#), sys.Futexwait | sys.Futexpriv, val, timeout, Zptr, 0)
+}
+
+const ftxwake = {uaddr
+	-> sys.futex((uaddr : int32#), sys.Futexwake | sys.Futexpriv, 1, Zptr, Zptr, 0)
+}
--- /dev/null
+++ b/lib/thread/futex+openbsd:6.2.myr
@@ -1,0 +1,16 @@
+use sys
+
+use "common"
+
+pkg thread =
+	const ftxwait : (uaddr : uint64#, val : uint64, timeout : sys.timespec# -> int)
+	const ftxwake : (uaddr : uint64# -> int)
+;;
+
+const ftxwait = {uaddr, val, timeout
+	-> sys.futex((uaddr : uint32#), sys.Futexwait, val, timeout, Zptr)
+}
+
+const ftxwake = {uaddr
+	-> sys.futex((uaddr : uint32#), sys.Futexwake, 1, Zptr, Zptr)
+}
--- /dev/null
+++ b/lib/thread/futex+osx.myr
@@ -1,0 +1,44 @@
+use std
+use sys
+
+use "common"
+
+pkg thread =
+	const ftxwait : (uaddr : uint64#, val : uint64, timeout : sys.timespec# -> int)
+	const ftxwake : (uaddr : uint64# -> int)
+;;
+
+/*
+ * The ulock_ functions are undocumented but the relevant source can be found at
+ * https://github.com/apple/darwin-xnu/blob/0a798f6738bc1db01281fc08ae024145e84df927/bsd/kern/sys_ulock.c
+ */
+const ftxwait = {uaddr, val, timeout
+	if timeout == Zptr
+		-> sys.ulock_wait(sys.Ulockcompareandwait, uaddr, val, 0)
+	;;
+
+	var ts
+	var err = sys.clock_gettime(`sys.Clockmonotonic, &ts)
+	std.assert(err == 0, "error: clock_gettime returned {}\n", err)
+
+	var usec = 0
+	if timeout.sec > ts.sec
+		var sec = (timeout.sec - ts.sec) * 1000
+		std.assert(sec <= 0xffffffff, "error: maximum futex timeout exceeded\n")
+		usec = (sec : uint32)
+	;;
+	if timeout.nsec > ts.nsec
+		var nsec = (timeout.nsec - ts.nsec) / 1000
+		std.assert(usec + nsec > usec, "error: maximum futex timeout exceeded\n")
+		usec += nsec
+	;;
+
+	if usec == 0
+		-> (std.Etimedout : int)
+	;;
+	-> sys.ulock_wait(sys.Ulockcompareandwait, uaddr, val, 0)
+}
+
+const ftxwake = {uaddr
+	-> sys.ulock_wake(sys.Ulockcompareandwait, uaddr, 0)
+}
--- a/lib/thread/mutex+freebsd.myr
+++ /dev/null
@@ -1,81 +1,0 @@
-use std
-use sys
-
-use "atomic"
-use "common"
-
-pkg thread =
-	type mutex = struct
-		_state	: uint32
-	;;	
-
-	const mkmtx	: (-> mutex)
-	const mtxlock	: (mtx : mutex# -> void)
-	const mtxtrylock	: (mtx : mutex# -> bool)
-	const mtxunlock	: (mtx : mutex# -> void)
-
-	pkglocal const Unlocked = 0
-	pkglocal const Locked = 1
-	pkglocal const Contended = 2
-;;
-
-var nspin = 10	/* FIXME: pick a sane number, based on CPU count */
-
-const mkmtx = {
-	-> [._state = Unlocked]
-}
-
-const mtxlock = {mtx
-	var c
-
-	/* 
-	Uncontended case: we get an unlocked mutex, and we lock it.
-	*/
-        c = Locked
-	for var i = 0; i < nspin; i++
-		c = xcas(&mtx._state, Unlocked, Locked) 
-		if c == Unlocked
-			-> void
-		;;
-		sys.sched_yield()
-	;;
-
-	/*
-	Contended case: we set the lock state to Contended. This indicates that there
-	the lock is locked, and we potentially have threads waiting on it, which means
-	that we will need to wake them up.
-	*/
-	if c == Locked
-		c = xchg(&mtx._state, Contended)
-	;;
-
-	while c != Unlocked
-		sys.umtx_op( \
-			(&mtx._state : void#), \
-			sys.Umtxwaituintpriv, \
-			(Contended : uint64), \
-			Zptr, Zptr)
-		c = xchg(&mtx._state, Contended)
-	;;
-}
-
-const mtxtrylock = {mtx
-	-> xcas(&mtx._state, Unlocked, Locked) == Unlocked
-}
-
-const mtxunlock = {mtx
-	/*
-	Uncontended case: If the mutex state is not contended, and we still
-	are uncontended by the xchg() call, then it's safe to simply return;
-	nobody was waiting for us.
-	*/
-	if mtx._state == Contended
-		mtx._state = Unlocked
-	elif xchg(&mtx._state, Unlocked) == Locked
-		-> void
-	;;
-
-	/* wake all threads: for some reason nwake */
-	sys.umtx_op((&mtx._state : void#), sys.Umtxwakepriv, 1, Zptr, Zptr)
-}
-
--- /dev/null
+++ b/lib/thread/mutex+futex.myr
@@ -1,0 +1,75 @@
+use sys
+
+use "atomic"
+use "common"
+use "futex"
+
+pkg thread =
+	type mutex = struct
+		_state	: uint64
+	;;	
+
+	const mkmtx	: (-> mutex)
+	const mtxlock	: (mtx : mutex# -> void)
+	const mtxtrylock	: (mtx : mutex# -> bool)
+	const mtxunlock	: (mtx : mutex# -> void)
+
+	pkglocal const Unlocked = 0
+	pkglocal const Locked = 1
+	pkglocal const Contended = 2
+;;
+
+var nspin = 10	/* FIXME: pick a sane number, based on CPU count */
+
+const mkmtx = {
+	-> [._state = Unlocked]
+}
+
+const mtxlock = {mtx
+	var c
+
+	/* 
+	Uncontended case: we get an unlocked mutex, and we lock it.
+	*/
+        c = Locked
+	for var i = 0; i < nspin; i++
+		c = xcas(&mtx._state, Unlocked, Locked) 
+		if c == Unlocked
+			-> void
+		;;
+	;;
+
+	/*
+	Contended case: we set the lock state to Contended. This indicates that there
+	the lock is locked, and we potentially have threads waiting on it, which means
+	that we will need to wake them up.
+	*/
+	if c == Locked
+		c = xchg(&mtx._state, Contended)
+	;;
+
+	while c != Unlocked
+		ftxwait(&mtx._state, Contended, Zptr)
+		c = xchg(&mtx._state, Contended)
+	;;
+}
+
+const mtxtrylock = {mtx
+	-> xcas(&mtx._state, Unlocked, Locked) == Unlocked
+}
+
+const mtxunlock = {mtx
+	/*
+	Uncontended case: If the mutex state is not contended, and we still
+	are uncontended by the xchg() call, then it's safe to simply return;
+	nobody was waiting for us.
+	*/
+	if mtx._state == Contended
+		mtx._state = Unlocked
+	elif xchg(&mtx._state, Unlocked) == Locked
+		-> void
+	;;
+
+	/* wake one thread */
+	ftxwake(&mtx._state)
+}
--- a/lib/thread/mutex+linux.myr
+++ /dev/null
@@ -1,76 +1,0 @@
-use std
-use sys
-
-use "atomic"
-use "common"
-
-pkg thread =
-	type mutex = struct
-		_state	: int32
-	;;	
-
-	const mkmtx	: (-> mutex)
-	const mtxlock	: (mtx : mutex# -> void)
-	const mtxtrylock	: (mtx : mutex# -> bool)
-	const mtxunlock	: (mtx : mutex# -> void)
-
-	pkglocal const Unlocked = 0
-	pkglocal const Locked = 1
-	pkglocal const Contended = 2
-;;
-
-var nspin = 10	/* FIXME: pick a sane number, based on CPU count */
-
-const mkmtx = {
-	-> [._state = Unlocked]
-}
-
-const mtxlock = {mtx
-	var c
-
-	/* 
-	Uncontended case: we get an unlocked mutex, and we lock it.
-	*/
-        c = Locked
-	for var i = 0; i < nspin; i++
-		c = xcas(&mtx._state, Unlocked, Locked) 
-		if c == Unlocked
-			-> void
-		;;
-	;;
-
-	/*
-	Contended case: we set the lock state to Contended. This indicates that there
-	the lock is locked, and we potentially have threads waiting on it, which means
-	that we will need to wake them up.
-	*/
-	if c == Locked
-		c = xchg(&mtx._state, Contended)
-	;;
-
-	while c != Unlocked
-		sys.futex(&mtx._state, sys.Futexwait | sys.Futexpriv, Contended, Zptr, Zptr, 0)
-		c = xchg(&mtx._state, Contended)
-	;;
-}
-
-const mtxtrylock = {mtx
-	-> xcas(&mtx._state, Unlocked, Locked) == Unlocked
-}
-
-const mtxunlock = {mtx
-	/*
-	Uncontended case: If the mutex state is not contended, and we still
-	are uncontended by the xchg() call, then it's safe to simply return;
-	nobody was waiting for us.
-	*/
-	if mtx._state == Contended
-		mtx._state = Unlocked
-	elif xchg(&mtx._state, Unlocked) == Locked
-		-> void
-	;;
-
-	/* wake one thread */
-	sys.futex(&mtx._state, sys.Futexwake | sys.Futexpriv, 1, Zptr, Zptr, 0)
-}
-
--- a/lib/thread/mutex+openbsd:6.2.myr
+++ /dev/null
@@ -1,76 +1,0 @@
-use std
-use sys
-
-use "atomic"
-use "common"
-
-pkg thread =
-	type mutex = struct
-		_state	: uint32
-	;;	
-
-	const mkmtx	: (-> mutex)
-	const mtxlock	: (mtx : mutex# -> void)
-	const mtxtrylock	: (mtx : mutex# -> bool)
-	const mtxunlock	: (mtx : mutex# -> void)
-
-	pkglocal const Unlocked : uint32 = 0
-	pkglocal const Locked 	: uint32 = 1
-	pkglocal const Contended : uint32 = 2
-;;
-
-var nspin = 10	/* FIXME: pick a sane number, based on CPU count */
-
-const mkmtx = {
-	-> [._state = Unlocked]
-}
-
-const mtxlock = {mtx
-	var c
-
-	/* 
-	Uncontended case: we get an unlocked mutex, and we lock it.
-	*/
-        c = Locked
-	for var i = 0; i < nspin; i++
-		c = xcas(&mtx._state, Unlocked, Locked) 
-		if c == Unlocked
-			-> void
-		;;
-	;;
-
-	/*
-	Contended case: we set the lock state to Contended. This indicates that there
-	the lock is locked, and we potentially have threads waiting on it, which means
-	that we will need to wake them up.
-	*/
-	if c == Locked
-		c = xchg(&mtx._state, Contended)
-	;;
-
-	while c != Unlocked
-		sys.futex(&mtx._state, sys.Futexwait, (Contended : int), Zptr, Zptr)
-		c = xchg(&mtx._state, Contended)
-	;;
-}
-
-const mtxtrylock = {mtx
-	-> xcas(&mtx._state, Unlocked, Locked) == Unlocked
-}
-
-const mtxunlock = {mtx
-	/*
-	Uncontended case: If the mutex state is not contended, and we still
-	are uncontended by the xchg() call, then it's safe to simply return;
-	nobody was waiting for us.
-	*/
-	if mtx._state == Contended
-		mtx._state = Unlocked
-	elif xchg(&mtx._state, Unlocked) == Locked
-		-> void
-	;;
-
-	/* wake one thread */
-	sys.futex(&mtx._state, sys.Futexwake, 1, Zptr, Zptr)
-}
-
--- /dev/null
+++ b/lib/thread/sem+futex.myr
@@ -1,0 +1,55 @@
+use std
+use sys
+
+use "atomic"
+use "common"
+use "futex"
+
+pkg thread =
+	type sem = struct
+		_val : uint64
+	;;
+
+	const mksem : (v : uint32 -> sem)
+	const semwait : (s : sem# -> void)
+	const semtrywait : (s : sem# -> bool)
+	const sempost : (s : sem# -> void)
+;;
+
+const mksem = {v
+	-> [._val = (v : uint64)]
+}
+
+const semwait = {s
+	var v = 0
+
+	for ; ;
+		while (v = s._val) > 0
+			if xcas(&s._val, v, v - 1) == v
+				-> void
+			;;
+		;;
+		ftxwait(&s._val, v, Zptr)
+	;;
+	-> void /* Unreachable */
+}
+
+const semtrywait = {s
+	for ; ;
+		var v = xget(&s._val)
+		if v == 0
+			-> false
+		;;
+		if xcas(&s._val, v, v - 1) == v
+			-> true
+		;;
+	;;
+	-> false /* Unreachable */
+}
+
+const sempost = {s
+	std.assert((xadd(&s._val, 1) : uint32) != ~0x0, "error: semaphore overflowed\n")
+
+	/* Unconditionally wake one waiter */
+	ftxwake(&s._val)
+}
--- /dev/null
+++ b/lib/thread/sem+plan9.myr
@@ -1,0 +1,54 @@
+use std
+use sys
+
+use "atomic"
+use "common"
+
+pkg thread =
+	type sem = struct
+		_user : int32
+		_kern : int32
+	;;
+
+	const mksem : (v : uint32 -> sem)
+	const semwait : (s : sem# -> void)
+	const semtrywait : (s : sem# -> bool)
+	const sempost : (s : sem# -> void)
+;;
+
+const mksem = {v
+	-> [._user = v, ._kern = 0]
+}
+
+const semwait = {s
+	var u = xadd(&s._user, -1)
+	std.assert(u != 0xffffffff, "error: semaphore underflowed\n")
+
+	/* When the userspace value is negative we fall back on the kernel semaphore */
+	if u <= 0
+		while sys.semacquire((&s._kern : uint32), 1) < 0
+			/* Interrupted, retry */
+		;;
+	;;
+}
+
+const semtrywait = {s
+	for ; ;
+		var u = xget(&s._user)
+		if u <= 0
+			-> false
+		;;
+		if xcas(&s._user, u, u - 1) == u
+			-> true
+		;;
+	;;
+	-> false /* Unreachable */
+}
+
+const sempost = {s
+	var u = xadd(&s._user, 1)
+	std.assert(u != 0x7fffffff, "error: semaphore overflowed\n")
+	if u < 0
+		sys.semrelease((&s._kern : uint32), 1)
+	;;
+}
--- /dev/null
+++ b/lib/thread/sem.myr
@@ -1,0 +1,66 @@
+use std
+
+use "atomic"
+
+pkg thread =
+	type sem = struct
+		_val : uint32
+	;;
+
+	const mksem : (v : uint32 -> sem)
+	const semwait : (s : sem# -> void)
+	const semtrywait : (s : sem# -> bool)
+	const sempost : (s : sem# -> void)
+;;
+
+const mksem = {v
+	-> [._val = v]
+}
+
+const semwait = {s
+	var v = 0
+
+	for var i = 0; i < 1000; i++
+		if (v = xget(&s._val)) != 0 && xcas(&s._val, v, v - 1) == v
+			-> void
+		;;
+	;;
+
+	for var i = 0; i < 1000; i++
+		if (v = xget(&s._val)) != 0 && xcas(&s._val, v, v - 1) == v
+			-> void
+		;;
+		std.nanosleep(10_000)
+	;;
+
+	for var i = 0; i < 1000; i++
+		if (v = xget(&s._val)) != 0 && xcas(&s._val, v, v - 1) == v
+			-> void
+		;;
+		std.nanosleep(100_000)
+	;;
+
+	for ; ;
+		if (v = xget(&s._val)) != 0 && xcas(&s._val, v, v - 1) == v
+			-> void
+		;;
+		std.nanosleep(1_000_000)
+	;;
+}
+
+const semtrywait = {s
+	for ; ;
+		var v = xget(&s._val)
+		if v == 0
+			-> false
+		;;
+		if xcas(&s._val, v, v - 1) == v
+			-> true
+		;;
+	;;
+	-> false /* Unreachable */
+}
+
+const sempost = {s
+	std.assert(xadd(&s._val, 1) != ~0x0, "error: semaphore overflowed\n")
+}
--- a/lib/thread/spawn+osx.myr
+++ b/lib/thread/spawn+osx.myr
@@ -79,7 +79,6 @@
 const getstk = {sz
 	var p, m
 
-	std.put("allocating stack {x}\n", sz)
 	p = sys.mmap((0 : byte#), sz, sys.Mprotrw, sys.Mpriv | sys.Manon, -1, 0)
 	if p == sys.Mapbad
 		-> p
--- /dev/null
+++ b/lib/thread/test/sem.myr
@@ -1,0 +1,42 @@
+use std
+use thread
+
+const Cap = 4
+
+var write : uint32 = 0, read : uint32 = 0
+var lock
+var len
+var cap
+var buf : int[Cap]
+var done : uint32 = 0
+
+const main = {
+	lock = thread.mkmtx()
+	len = thread.mksem(0)
+	cap = thread.mksem(Cap)
+
+	thread.spawn({
+		for var i = 0; i < 100000; i++
+			thread.semwait(&cap)
+			thread.mtxlock(&lock)
+			buf[write++ & (Cap - 1)] = i
+			thread.mtxunlock(&lock)
+			thread.sempost(&len)
+		;;
+	})
+
+	thread.spawn({
+		for var i = 0; i < 100000; i++
+			thread.semwait(&len)
+			thread.mtxlock(&lock)
+			std.assert(i == buf[read++ & (Cap - 1)], "semaphores are broken\n")
+			thread.mtxunlock(&lock)
+			thread.sempost(&cap)
+		;;
+		thread.xset(&done, 1)
+	})
+
+	while thread.xget(&done) == 0
+		std.nanosleep(1_000_000)
+	;;
+}