τau / sync/atomic /

atomic.tau

source
/Users/niconex/Documents/tau/stdlib/sync/atomic/atomic.tau
1# atomic - reads and writes no other routine can see half of.2#3# A mutex makes a whole section of code the property of one routine. These4# make a single read, write or addition indivisible, which is less and is5# often all that is wanted: a counter every routine bumps, a flag one routine6# raises and the others watch. That costs one instruction here against a pipe,7# a wait and a wake with sync.Mutex.8#9# The rest of sync is written in tau because a pipe is enough to build it.10# This is not: the processor's instructions are the primitive, and no11# arrangement of pipes is one. So this module is the one place in sync with a12# shared object of its own - it cannot borrow the C library the way math does,13# since these are instructions the compiler emits inline and not functions14# with a name to look up.15#16# Where a mutex would still be needed: anything touching two values at once.17# Atomics make each of them safe on its own and say nothing about the pair.1819ffi = import("ffi")2021# The library is looked up next to this file, wherever the stdlib was put.22# Named for the module and not "libatomic", which is the name of the one GCC23# ships: a plain name is handed to the loader of the system first, and it24# would answer with that one.25lib = dlopen("libsyncatomic.so")2627at = ffi.Bind(lib, [28	"int64_t at_load(void *p)",29	"void at_store(void *p, int64_t v)",30	"int64_t at_add(void *p, int64_t delta)",31	"int64_t at_swap(void *p, int64_t v)",32	"int64_t at_cas(void *p, int64_t old, int64_t new)",33])3435# Int is an integer several routines may read and write at once.36#37#	hits = atomic.Int(0)38#	... in every routine:39#	hits.Add(1)40#	... after they are done:41#	hits.Load()42#43# The cell is eight bytes the collector owns, so an Int costs nothing to get44# rid of: it goes when nothing holds it, like any other value.45Int = fn(v) {46	a = new()47	a.cell = bytes(8)4849	# Load returns what the cell holds.50	a.Load = fn() { at.at_load(a.cell) }5152	# Store writes n into the cell.53	a.Store = fn(n) { at.at_store(a.cell, n) }5455	# Add adds delta and returns what the cell holds afterwards. That answer56	# belongs to the caller alone: no two routines adding at once are given57	# the same one, which is what makes it usable as a ticket.58	a.Add = fn(delta) { at.at_add(a.cell, delta) }5960	# Swap writes n and returns what was there before.61	a.Swap = fn(n) { at.at_swap(a.cell, n) }6263	# CompareAndSwap writes new if the cell holds old, and reports whether it64	# did. It is what a change that has to read the old value first is built65	# from: read, work out the new one, and try until this one takes.66	a.CompareAndSwap = fn(old, new) { at.at_cas(a.cell, old, new) != 0 }6768	a.Store(v)69	return a70}7172# Bool is a flag several routines may read and write at once, an Int holding73# 0 or 1.74#75#	stop = atomic.Bool(false)76#	... one routine:77#	stop.Store(true)78#	... the others:79#	if stop.Load() { return }80Bool = fn(v) {81	b = new()82	b.n = Int(0)8384	# num is the 0 or 1 a bool is kept as. Anything else is a mistake worth85	# catching here rather than storing something no Load could return.86	b.num = fn(x) {87		if type(x) != "bool" {88			return error("atomic: Bool wants a boolean, got {type(x)}")89		}90		return if x { 1 } else { 0 }91	}9293	# Load returns what the flag holds.94	b.Load = fn() { b.n.Load() != 0 }9596	# Store writes x into the flag.97	b.Store = fn(x) {98		if failed(n = b.num(x)) {99			return n100		}101		b.n.Store(n)102	}103104	# Swap writes x and returns what was there before.105	b.Swap = fn(x) {106		if failed(n = b.num(x)) {107			return n108		}109		return b.n.Swap(n) != 0110	}111112	# CompareAndSwap writes new if the flag holds old, and reports whether it113	# did.114	b.CompareAndSwap = fn(old, new) {115		if failed(o = b.num(old)) {116			return o117		}118		if failed(n = b.num(new)) {119			return n120		}121		return b.n.CompareAndSwap(o, n)122	}123124	if failed(err = b.Store(v)) {125		return err126	}127	return b128}