I hope it's not too late to resurrect this thread (and it's too late to stop me if it is

).
I just had a thought today about implementing mutual exclusions (mutexes) in Z80 code. Mutexes are usually implemented using an atomic test-and-set instruction, which the Z80 lacks, but we can do an implicit test-and-set using SMC.
The idea is to create the mutex using an instruction that overwrites itself with something else. Obviously, the original instruction will be an LD, and we want to replace it with something useful. The caveat is that the new instruction must only be one byte, since we need to do this in one instruction. We could use two-bytes if you use an immediate address (like ld (xxxx), hl), but then the mutexes must be hard-coded.
The (or A) solution is to use a RET instruction ($c9). A mutex then consists of 4 bytes:
Code:
mutex:
ld (hl), $c9 ; overwrite self with "ret" insn.
scf
ret
The caller of the mutex loads HL with the mutex address and resets the carry flag before calling. If the carry flag is set on return, the mutex has been acquired.
I've written 3 high-level routines (
acquire_mutex, timeout_mutex and release_mutex) to manipulate the mutexes. They seem to work fine in PTI, though I haven't had a chance to test them in multi-threaded code, since I don't have any!
Code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; acquire_mutex:
;;
;; This routine will block until the mutex is acquired.
;;
;; INPUTS:
;;
;; REGISTERS
;;
;; * HL - address of mutex to acquire.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
acquire_mutex:
or a
call $ + $0006 ; indirect call to hl (see below)
jr nc, acquire_mutex
ret
jp (hl)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; timeout_mutex:
;;
;; This routine will block until the mutex is acquired or the timeout
;; period elapses.
;;
;; INPUTS:
;;
;; REGISTERS
;;
;; * A - timeout value (0 == 256).
;; * HL - address of mutex to acquire.
;;
;; OUTPUTS:
;;
;; REGISTERS
;;
;; * F - carry flag set if mutex was acquired.
;; - carry flag clear if timeout elapsed.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
timeout_mutex:
or a
call $ + $0008 ; indirect call to hl (see below)
ret c
dec a
jr nz, timeout_mutex
ret
jp (hl)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; release_mutex:
;;
;; Releases an acquired mutex.
;;
;; INPUTS:
;;
;; REGISTERS
;;
;; * HL - address of mutex to release.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
release_mutex:
ld (hl), $36 ; overwrite mutex with LD (hl), $c9
ret
I guess you could also write an init_mutex routine to create mutexes on-the-fly. Just load the target address with the byte sequence: $36, $c9, $37, $c9.
Another variation (which I trialled before settling on this) is to use immediate addressing as described above to write a two-byte instruction. $fe18 would give you a nice succinct infinite loop (JR pc-2), but makes implementing timeouts somewhat impossible. On the other hand, it would allow the scheduler to check for blocked threads (just check for $fe18 and the PC before executing the thread) and not waste run-time on them.