CX Framework
Cross-platform C utility framework
Loading...
Searching...
No Matches
pblock.h
Go to the documentation of this file.
1#pragma once
2
5
54
55#include <cx/meta/block.h>
57#include <setjmp.h>
58
59enum _pblock_unwind_const { _pblock_unwind_top = 0 };
60
63typedef struct _pblock_jmp_buf_node _pblock_jmp_buf_node;
64typedef struct _pblock_jmp_buf_node {
65 _pblock_jmp_buf_node* next;
66 jmp_buf buf;
67 volatile int target;
68} _pblock_jmp_buf_node;
69
70typedef _pblock_jmp_buf_node _pblock_jmp_buf[1];
71#define BLOCK_JMPBUF_INIT \
72 { \
73 [0] = {.target = 0 } \
74 }
75
76_meta_inline /*noreturn*/ void _pbLongjmp(_pblock_jmp_buf_node* node, int val)
77{
78 node->target = val;
79 longjmp(node->buf, 1);
80}
81
126#define pblock \
127 _blkStart _inhibitReturn /* set to true during the unwinding process */ \
128 _blkDef(volatile bool _pblock_unwind = false) \
129 _blkFull(/* BEFORE */ volatile int _pblock_target = 0, \
130 true, /* After we exit the block through whatever means, if we are unwinding, try \
131 * to unwind the next level. Since this loop is above the one that defines \
132 * _pblock_unwind_top, the jump buffer here points to the one set by the \
133 * block that encloses this one and we'll jump back into it, giving it the \
134 * ability to run any PBLOCK_AFTER cleanup and continue the chain. In the \
135 * outermost block, _pblock_unwind_top will instead be 0 from the global \
136 * enum, and pblockUnwind will do nothing. If _pblock_target reaches 0, \
137 * this will also exit the loop and continue execution as normal. */ \
138 /* AFTER */ \
139 (_pblock_unwind ? \
140 pblockUnwind(_pblock_target <= 0 ? _pblock_target : _pblock_target - 1) : \
141 nop_stmt)) /* allocate a jump buffer on the stack for this level of \
142 recursion */ \
143 _blkDef(_pblock_jmp_buf \
144 _pblock_unwind_top = tokeval(BLOCK_JMPBUF_INIT)) /* set the longjump target to \
145 be within the inntermost \
146 loop and coerce the return \
147 value to 1 or 0 */ \
148 switch (setjmp(_pblock_unwind_top[0] \
149 .buf)) /* case 2 doesn't actually exist, it serves as a place to hang \
150 an if-else construct so we can define two case labels here \
151 instead of only one - case 0 for the user block to execute, \
152 and a default case label that acts as a fallback for case 1 \
153 not being defined, which can happen if the user does not \
154 include a PBLOCK_AFTER label. */ \
155 case 2: \
156 if (0) { \
157 default: \
158 /* if there isn't a PBLOCK_AFTER but we got here as a longjmp target, we need to take care \
159 * of setting up the variable for unwinding, otherwise the AFTER blocks will fail to jump \
160 * to the next target in the chain and we'll stop unwinding early. */ \
161 _pblock_target = _pblock_unwind_top[0].target; \
162 _pblock_unwind = !!_pblock_target; \
163 } else \
164 /* This is the case for when we get here the first time, right after calling setjmp. The \
165 * user-provided block slots in directly after this label. */ \
166 case 0:
167
168_meta_inline void _pblockUnwind(void* top, int cond)
169{
170 if (cond && top)
171 _pbLongjmp(top, cond);
172}
173
203#define pblockUnwind(num) _pblockUnwind((void*)_pblock_unwind_top, (num))
204
235#define PBLOCK_AFTER \
236 if (0) { \
237 /* Even though this case 1 label is placed into the user-defined block, it technically exists \
238 * within the switch statement at the end of the pblock macro. When the user provides a \
239 * PBLOCK_AFTER target, it defines this case 1 which overrides the default case label (the one \
240 * that's hidden in the if(0) after the case 2:) and comes here instead. */ \
241 case 1: \
242 _pblock_target = _pblock_unwind_top[0].target; \
243 _pblock_unwind = !!_pblock_target; \
244 /* Execution falls through to the statement below. */ \
245 } \
246 /* This odd looking construct exists to swallow the ':' after the fake PBLOCK_AFTER label. */ \
247 switch (0) \
248 case 0
249
Block wrapping macros for automatic resource management.
Macros for suppressing compiler warnings.