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}