Make it more portable?
nicowilliams opened this issue · 14 comments
goto *_deferrals[--_num_deferrals]
is not portable C :(
There is good news though: you could use a Duff's device so that each deferral is a switch case and then you can while (_num_deferrals)
loop around the switch. Not unlike async.h or Simon Tatham's C co-routines.
There is a portable version using setjmp/longjmp (look at the second half of the file); unfortunately, because longjmp tries to be clever, it causes problems when optimizations are enabled, and with dynamic stack allocations.
I did attempt a switch-based version, but was unsuccessful.
Leaving this open as a tracking issue for portability, though.
The other problem with a switch/case-based version is that you need to have another macro to put a closing brace at the end of the function. Which is not a huge deal, but it still adds friction and makes the library less 'natural' to use.
Yeah, I thought of the closing brace, but I'm ok with that. I can't use this with the setjmp()
/longjmp()
approach, and I can't use GCC/clang extensions. I'm also not sure I'd use it if it used a Duff's device either, but I am sure I need this :)
Oh, I think I see the problem with a Duff's device...
EDIT: Nah, made it work! PR submitted! Feel free to reject, naturally. And, yes, sadly, I did need BeginDeferral
/EndDeferral
macros :(
Can you elaborate on why you can't use the gcc extensions? Using MSVC? Or on an embedded platform?
This can be implemented safely using relatively trivial assembly routines; if that would suffice, let me know the platform and I can whip something up.
Yeah, MSVC :(
So if I make a version which uses amd64 assembly (masm syntax, I guess?), would that work for you?
Don't waste your time on that yet. If I could use this, I'd use it in Heimdal), but that would require consensus among us maintainers. I did the Duff's device thing for the fun of it. Thanks!
I've figured out how to make it work with MSVC using ASM. Essentially we need two macros, TakeLabelAddress(destination, label_name)
and GotoLabelAddress(addr)
that look like this:
#define TakeLabelAddress(dest, l) \
do { void *_label; __asm{ mov [_label],offset l }; dest = _label; } while(0)
#define GotoLabelAddress(a) do { void *_label = (a); __asm{ jmp _label } } while (0)
A simplified defer.h
for just MSVC (i.e., w/o #ifdef
spaghetti) looks like this:
#ifndef DEFER_H
#define DEFER_H
#ifndef DEFER_MAX_DEFERRED_STATEMENTS
# define DEFER_MAX_DEFERRED_STATEMENTS 32
#endif
#define TakeLabelAddress(dest, l) \
do { void *_labelv; __asm{ mov [_labelv],offset l }; dest = _labelv; } while(0)
#define GotoLabelAddress(a) do { _label = (a); __asm{ jmp _label } } while (0)
#define Deferral \
unsigned char _num_deferrals = 0; \
void *_defer_return_loc = 0, *_deferrals[DEFER_MAX_DEFERRED_STATEMENTS] = {0};
# define Defer(block) _Defer(block, __COUNTER__)
# define Return _Return(__COUNTER__)
#define _defer_tokpaste(a, b) a ## b
#define _Defer(block, n) do { \
TakeLabelAddress(_deferrals[_num_deferrals++], _defer_tokpaste(_defer_ini, n)); \
if (0) { \
_defer_tokpaste(_defer_ini, n): \
block; \
if (_num_deferrals) { \
GotoLabelAddress(_deferrals[--_num_deferrals]); \
} else { \
GotoLabelAddress(_defer_return_loc); \
} \
} \
} while (0)
#define _Return(n) \
if (_num_deferrals) { \
TakeLabelAddress(_defer_return_loc, _defer_tokpaste(_defer_fini_, n)); \
GotoLabelAddress(_deferrals[--_num_deferrals]); \
} \
\
_defer_tokpaste(_defer_fini_, n): \
return
#endif /*DEFER_H*/
Yes, I actually made pretty much that, when I was testing initial versions. But I canned it because __asm
only works with 32-bit msvc, and I don't really care about 32-bit x86.
It doesn't work for 64-bit??
Ugh, you're right.
Old issue, but I've recently implemented an efficient, resource and user friendly solution in pure C99/C89.
- Allows the user to use natural scope syntax with starting / ending curly brackets. No end-macro. Uses an almost unknown syntactic feature of switch to achieve this (curly bracket may be moved to after the first case ':').
- Minimal stack usage, i.e. 4 bytes per defer (or whatever to store a __LINE__ value).
int process(int x) {
printf("\nx=%d:\n", x);
c_scope {
c_defer({ puts("one"); });
if (x < 0) c_return -1;
c_defer({ puts("two"); });
if (x > 0) continue; // break out
puts("OK");
}
puts("DONE");
return 0;
}
int main() {
process(0);
process(1);
process(-1);
}
x=0:
OK
two
one
DONE
x=1:
two
one
DONE
x=-1:
one
Note: I don't think any existing defer library implementation (including mine) supports nested defer-scopes. This is maybe the main reason it needs language support, along with the easy mistake to type return instead of c_return. This library solution is in any case a highly valuable tool in the toolbox. Some implementations will compile when creating nested defer scopes, but won't work correctly, e.g. for return. This implementation fails to compile in that case.
I have updated the implementation with two additional defer scope types, one which allows you to add one defer scope level inside a c_scope, called c2_scope. The other is cs_scope which allows you to have multiple defer scopes in sequence within a function (limitation of c_scope). These are setjmp-based and slow. c_scope is still the primary to use.