/* tt.h - Tiny threads * SPDX-License-Identifier: 0BSD or CC0-1.0 * Jon Mayo - created August 23, 2005 ; updated September 23, 2026 */ #ifndef TT_H #define TT_H typedef struct tt_state { unsigned line; } tt_state_t; /* Marks the deliberate fall-through into the resumed case label, so modern * compilers do not warn about it. Resolves to the fall-through attribute the * current compiler understands, or to nothing when none is available. */ #if defined(__has_attribute) # if __has_attribute(fallthrough) # define TT_FALLTHROUGH __attribute__((fallthrough)) # endif #endif #ifndef TT_FALLTHROUGH # if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L # define TT_FALLTHROUGH [[fallthrough]] # else # define TT_FALLTHROUGH ((void)0) # endif #endif #define TT_STATUS_WAITING 1 #define TT_STATUS_EXITED 0 #define TT_BEGIN(tts) switch((tts)->line) { case 0: #define TT_END(tts) } TT_EXIT(tts); #define TT_INITIALIZE(tts) ((tts)->line=0) #define TT_INITIALIZER {0} /* Sets the state; on reentry the program continues at this point. */ #define TT_SET(tts) (tts)->line=__LINE__; TT_FALLTHROUGH; case __LINE__: /* Wait while condition cond is true. */ #define TT_WAIT_WHILE(tts, cond) do { TT_SET(tts); if((cond)) { return TT_STATUS_WAITING; } } while(0) /* Wait until condition cond is true. */ #define TT_WAIT_UNTIL(tts, cond) do { TT_SET(tts); if(!(cond)) { return TT_STATUS_WAITING; } } while(0) /* yields once, then continues on the next call. Use this to make progress one * step per call without waiting on a condition. The resume point sits after * the return, so it fires exactly once with no per-thread flag. */ #define TT_YIELD(tts) do { (tts)->line=__LINE__; return TT_STATUS_WAITING; case __LINE__:; } while(0) /* Restarts the state machine from the beginning. */ #define TT_RESTART(tts) do { TT_INITIALIZE(tts); return TT_STATUS_WAITING; } while(0) #define TT_EXIT(tts) do { TT_INITIALIZE(tts); return TT_STATUS_EXITED; } while(0) /* Use this for debugging; evaluates to the source line the thread is waiting on. */ #define TT_LINE(tts) ((tts)->line) #endif /* Usage: ***************************************************************************** * A tiny thread is an ordinary function that returns a TT_STATUS value. Its * local progress lives in a tt_state_t that the caller owns, so the function * itself keeps no static state and is safe to run many times over. The body * sits between TT_BEGIN and TT_END. Each call resumes at the point where the * previous call chose to wait. * * Rules to keep in mind: * 1. Everything before the first wait runs once per entry (start and restarts). * 2. Local variables do not survive across a wait, because the function * returns and is called again. Therefore, store anything that must persist * in a struct passed by the caller, as shown by the "struct worker" * below. * 3. A wait macro must not sit inside a switch of your own, since the * BEGIN/END pair is itself a switch. * * --------------------------------------------------------------------------- * Example of a worker thread that waits for input, drains it, then reports done: * * struct worker { * tt_state_t tts; * int pending; // work handed in by the outside world * int processed; // running total, must persist across waits * }; * * unsigned worker_th(struct worker *w) * { * TT_BEGIN(&w->tts); * // This block runs once each time the thread starts or restarts. * w->processed = 0; * printf("worker: ready\n"); * * // Block here until the outside world sets w->pending. * TT_WAIT_UNTIL(&w->tts, w->pending > 0); * * // Drain the work one item per call. TT_YIELD returns to the caller and * // resumes here on the next call, so the caller stays responsive while * // this thread makes progress one step at a time. Unlike the wait * // macros, TT_YIELD does not re-test a condition, so mutations made just * // before it (here the decrement) are not repeated on resume. * while (w->pending > 0) { * w->pending--; * w->processed++; * TT_YIELD(&w->tts); * } * * printf("worker: processed %d item(s)\n", w->processed); * * // Uncomment to loop forever instead of exiting: * // TT_RESTART(&w->tts); * * TT_END(&w->tts); * } * * --------------------------------------------------------------------------- * Example of driving the thread from a scheduler loop. Call the thread * repeatedly and watch its return value. * TT_STATUS_WAITING means "call me again later" * TT_STATUS_EXITED means the thread has finished * * int main(void) * { * struct worker w = { TT_INITIALIZER, 0, 0 }; * * // Hand in some work after a few idle ticks. * for (int tick = 0; ; tick++) { * if (tick == 3) * w.pending = 4; * * unsigned status = worker_th(&w); * if (status == TT_STATUS_EXITED) * break; * * // TT_LINE reports the source line the thread is parked on, which * // is handy when debugging a stuck scheduler. * printf("tick %d: waiting at line %u\n", tick, TT_LINE(&w.tts)); * } * * printf("worker: exited\n"); * return 0; * } * * --------------------------------------------------------------------------- * To reset a thread and run it again, either let it reach TT_END (which calls * TT_INITIALIZE for you) or call TT_INITIALIZE(&w.tts) directly before the * next call. ***************************************************************************** */