| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | /* | ||
| 2 | Copyright 2025 Joel Svensson svenssonjoel@yahoo.se | ||
| 3 | |||
| 4 | This program is free software: you can redistribute it and/or modify | ||
| 5 | it under the terms of the GNU General Public License as published by | ||
| 6 | the Free Software Foundation, either version 3 of the License, or | ||
| 7 | (at your option) any later version. | ||
| 8 | |||
| 9 | This program is distributed in the hope that it will be useful, | ||
| 10 | but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 12 | GNU General Public License for more details. | ||
| 13 | |||
| 14 | You should have received a copy of the GNU General Public License | ||
| 15 | along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| 16 | */ | ||
| 17 | |||
| 18 | #include "extensions/mutex_extensions.h" | ||
| 19 | |||
| 20 | #include "extensions.h" | ||
| 21 | #include "eval_cps.h" | ||
| 22 | |||
| 23 | #ifdef LBM_OPT_MUTEX_EXTENSIONS_SIZE | ||
| 24 | #pragma GCC optimize ("-Os") | ||
| 25 | #endif | ||
| 26 | #ifdef LBM_OPT_MUTEX_EXTENSIONS_SIZE_AGGRESSIVE | ||
| 27 | #pragma GCC optimize ("-Oz") | ||
| 28 | #endif | ||
| 29 | |||
| 30 | |||
| 31 | // This file provides a mutual exclusion feature based on the block_from_extension | ||
| 32 | // mechanism present in LBM. | ||
| 33 | // | ||
| 34 | // Three operations are provided: | ||
| 35 | // - mutex-create | ||
| 36 | // - mutex-lock | ||
| 37 | // - mutex-unlock | ||
| 38 | // | ||
| 39 | // It is strongly adviced to use these as a low-level interface and then to wrap | ||
| 40 | // them up in a (with-mutex-do mutex expr) that implements the locking and unlocking | ||
| 41 | // so that no mutex is left dangling. | ||
| 42 | // | ||
| 43 | // with-mutex-do can be implemented as | ||
| 44 | // | ||
| 45 | // (define with-mutex-do (lambda (mutex quoted-expr) | ||
| 46 | // (progn (mutex-lock mutex) | ||
| 47 | // (eval quoted-expr) | ||
| 48 | // (mutex-unlock mutex) | ||
| 49 | // | ||
| 50 | // | ||
| 51 | // The mutex object is a dotted pair (ls . last) which contains | ||
| 52 | // two references into a single list, implementing a O(1)-insert-last O(1)-remove-first | ||
| 53 | // queue. At the surface though, it is a regular lisp dotted pair that | ||
| 54 | // can be destroyed with standard lisp functionality, no protection! | ||
| 55 | // When a good replacement for "Custom types" is invented this will be improved. | ||
| 56 | |||
| 57 | 710 | bool is_mutex(lbm_value v) { | |
| 58 | // true if it is somewhat likely that v is a mutex. | ||
| 59 |
2/2✓ Branch 0 taken 703 times.
✓ Branch 1 taken 7 times.
|
1413 | bool res = (lbm_is_cons(v) && |
| 60 |
4/4✓ Branch 0 taken 353 times.
✓ Branch 1 taken 350 times.
✓ Branch 2 taken 352 times.
✓ Branch 3 taken 1 times.
|
703 | (!lbm_is_symbol_nil(lbm_car(v)) || lbm_is_symbol_nil(lbm_cdr(v)))); // car == nil -> cdr == nil |
| 61 | // potentially add a clause | ||
| 62 | // car == (cons a b) -> cdr == (cons c nil) | ||
| 63 | 710 | return res; | |
| 64 | } | ||
| 65 | |||
| 66 | 701 | bool is_mutex_unlocked(lbm_value v) { | |
| 67 | 701 | return (lbm_is_symbol_nil(lbm_car(v))); | |
| 68 | } | ||
| 69 | |||
| 70 | 348 | void enqueue_cid(lbm_value mutex, lbm_value cid_pair) { | |
| 71 |
2/2✓ Branch 0 taken 347 times.
✓ Branch 1 taken 1 times.
|
348 | if (lbm_is_symbol_nil(lbm_car(mutex))) { |
| 72 | 347 | lbm_set_car(mutex, cid_pair); | |
| 73 | 347 | lbm_set_cdr(mutex, cid_pair); | |
| 74 | } else { | ||
| 75 | 1 | lbm_value last = lbm_cdr(mutex); | |
| 76 | 1 | lbm_set_cdr(last, cid_pair); | |
| 77 | 1 | lbm_set_cdr(mutex, cid_pair); | |
| 78 | } | ||
| 79 | 348 | } | |
| 80 | |||
| 81 | 349 | bool dequeue_cid(lbm_value mutex, lbm_value cid) { | |
| 82 | 349 | bool res = false; | |
| 83 |
2/2✓ Branch 0 taken 348 times.
✓ Branch 1 taken 1 times.
|
349 | if (lbm_is_cons(lbm_car(mutex))) { |
| 84 | 348 | lbm_value locked_cid = lbm_car(lbm_car(mutex)); | |
| 85 |
2/2✓ Branch 0 taken 347 times.
✓ Branch 1 taken 1 times.
|
348 | if (locked_cid == cid) { // no decoding |
| 86 | 347 | res = true; | |
| 87 | 347 | lbm_value head = lbm_car(mutex); | |
| 88 | 347 | lbm_value last = lbm_cdr(mutex); | |
| 89 |
2/2✓ Branch 0 taken 346 times.
✓ Branch 1 taken 1 times.
|
347 | if (head == last) { // one element |
| 90 | 346 | lbm_set_car(mutex, ENC_SYM_NIL); | |
| 91 | 346 | lbm_set_cdr(mutex, ENC_SYM_NIL); | |
| 92 | } else { | ||
| 93 | 1 | lbm_set_car(mutex, lbm_cdr(head)); | |
| 94 | } | ||
| 95 | } | ||
| 96 | } | ||
| 97 | 349 | return res; | |
| 98 | } | ||
| 99 | |||
| 100 | 347 | lbm_value head_of_queue(lbm_value mutex) { | |
| 101 | 347 | lbm_value res = ENC_SYM_NIL; | |
| 102 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 346 times.
|
347 | if (lbm_is_cons(lbm_car(mutex))) { |
| 103 | 1 | res = lbm_car(lbm_car(mutex)); | |
| 104 | } | ||
| 105 | 347 | return res; | |
| 106 | } | ||
| 107 | |||
| 108 | 139 | static lbm_value ext_mutex_create(lbm_value *args, lbm_uint argn) { | |
| 109 | (void) args; | ||
| 110 | (void) argn; | ||
| 111 | 139 | return lbm_cons(ENC_SYM_NIL, ENC_SYM_NIL); | |
| 112 | } | ||
| 113 | |||
| 114 | 355 | static lbm_value ext_mutex_lock(lbm_value *args, lbm_uint argn) { | |
| 115 | 355 | lbm_value res = ENC_SYM_TERROR; | |
| 116 |
4/4✓ Branch 0 taken 353 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 349 times.
✓ Branch 3 taken 4 times.
|
355 | if (argn == 1 && is_mutex(args[0])) { |
| 117 | 349 | lbm_cid cid = lbm_get_current_cid(); | |
| 118 | 349 | lbm_value cid_pair = lbm_cons(lbm_enc_i(cid), ENC_SYM_NIL); | |
| 119 | 349 | res = cid_pair; // Return the error from cons if failed. | |
| 120 |
2/2✓ Branch 0 taken 348 times.
✓ Branch 1 taken 1 times.
|
349 | if (lbm_is_cons(cid_pair)) { |
| 121 | 348 | res = ENC_SYM_TRUE; | |
| 122 |
2/2✓ Branch 0 taken 347 times.
✓ Branch 1 taken 1 times.
|
348 | if (is_mutex_unlocked(args[0])) { |
| 123 | 347 | enqueue_cid(args[0], cid_pair); | |
| 124 | } else { | ||
| 125 | 1 | enqueue_cid(args[0], cid_pair); | |
| 126 | 1 | lbm_block_ctx_from_extension(); | |
| 127 | } | ||
| 128 | } | ||
| 129 | } | ||
| 130 | 355 | return res; | |
| 131 | } | ||
| 132 | |||
| 133 | 359 | static lbm_value ext_mutex_unlock(lbm_value *args, lbm_uint argn) { | |
| 134 | 359 | lbm_value res = ENC_SYM_TERROR; | |
| 135 |
4/4✓ Branch 0 taken 357 times.
✓ Branch 1 taken 2 times.
✓ Branch 2 taken 353 times.
✓ Branch 3 taken 4 times.
|
359 | if (argn == 1 && is_mutex(args[0])) { |
| 136 | 353 | res = ENC_SYM_EERROR; // no mutex is locked! | |
| 137 |
2/2✓ Branch 0 taken 349 times.
✓ Branch 1 taken 4 times.
|
353 | if (!is_mutex_unlocked(args[0])) { |
| 138 | 349 | lbm_cid cid = lbm_get_current_cid(); // this cid should be top of queue! | |
| 139 | // otherwise error | ||
| 140 |
2/2✓ Branch 0 taken 347 times.
✓ Branch 1 taken 2 times.
|
349 | if (dequeue_cid(args[0], lbm_enc_i(cid))) { |
| 141 | 347 | lbm_value h = head_of_queue(args[0]); | |
| 142 | 347 | res = ENC_SYM_TRUE; | |
| 143 |
2/2✓ Branch 0 taken 1 times.
✓ Branch 1 taken 346 times.
|
347 | if (!lbm_is_symbol_nil(h)) { |
| 144 | 1 | lbm_cid unblock = lbm_dec_i(h); | |
| 145 | 1 | lbm_unblock_ctx_unboxed(unblock, ENC_SYM_TRUE); | |
| 146 | } | ||
| 147 | } | ||
| 148 | } | ||
| 149 | } | ||
| 150 | 359 | return res; | |
| 151 | } | ||
| 152 | |||
| 153 | |||
| 154 | 66394 | void lbm_mutex_extensions_init(void) { | |
| 155 | 66394 | lbm_add_extension("mutex-create", ext_mutex_create); | |
| 156 | 66394 | lbm_add_extension("mutex-lock", ext_mutex_lock); | |
| 157 | 66394 | lbm_add_extension("mutex-unlock", ext_mutex_unlock); | |
| 158 | 66394 | } | |
| 159 |