-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Hi everyone! If you're reading this, then the Github "projects" integration must have notified you. This is a place in repos to create tickets and plan features.
I'm having a tough time with part of our utility macros API involving the usage of G, where G stands for "global".
Here's some background to explain the problem that macros like this and Zephyr's Z_ macros solve.
Take the classic max macro:
#define MAX(a, b) (((a) > (b)) ? (a) : (b))There is an inherent problem with this macro, which is that it should NEVER be used with function/macro calls as arguments because they will be evaluated multiple times. At best, it's a performance issue, at worst, the functions have side effects.
int _ = MAX(my_function(d, c), func(1, 2, 3));
// expands to
int _ = my_function(d, c) > func(1, 2, 3) ? my_function(d, c) : func(1, 2, 3);Note that each function is evaluated twice.
Zephyr's Z_MAX and our IC_MAX, allow this usage by forcing single evaluation of every expression passed to a macro.
int _ = IC_MAX(my_function(d, c), func(1, 2, 3));
// expands to
int _ = ({
__auto_type const a = my_function(d, c);
__auto_type const b = func(1, 2, 3);
(((a) > (b)) ? (a) : (b));
)};This is great! So you can use ALL of our macros and know that it's OK to use functions as arguments. Every expression is guaranteed to be evaluated only once.
Somewhat surprisingly, the GCC statement expression will also evaluate at compile-time, if possible. That is, if the expressions are both compile time constants, then IC_MAX will simply evaluate the entire expression at compile time; e.g. it evaluates to 42, not a ternary.
But there's a catch that I didn't notice until I tried using this library in practice. The catch is that statement expressions are not allowed outside of block scope; AKA not allowed at global scope. Probably an unnecessary restriction but I'm not gonna try to upstream GCC. The thing with global scope, is that the compiler IS going to enforce that all values are compile time constants, so we don't have to worry about any funny business there. The problem is that I couldn't come up with a HACK to intelligently switch the macro for the global-scope compatible version. I did get a "IS_GLOBAL" macro working, but it only works after the preprocessor so does not solve the problem.
So, we end up with the G macros; think of them like the "basic" macros, like MAX above. This discussion is to decide how to deal with usage and documentation. The G macros are troublesome because they DO NOT guarantee single evaluation and there is not way to prevent someone from using them inside block scope where the non G variant should be used.
Topics
- All G variants are prefixed. So if you're in global scope, you search for IC_G_. This works well for my autocompletion. But I started to wonder, maybe it should be a postfix? That way you're typing IC_MAX and then you'll get suggestions for the G variant and hey, maybe that's what you need.
- Any way around this? I put in some work to try to hack it and can't recommend it.
- Documentation. I tried to make the warning that you shouldn't use the G variants obvious - it's the beginning of the docstring. After seeing the way doxygen formats things, I think I should add a note section for every one warning that they should ONLY be used in global scope. https://intercreate.github.io/ic-macros/util_8h.html#a416b919c427c849c1fbf06bee40e393b
- Documentation. The way doxygen sorts, if we prefix with G then they all get grouped together. If we postfix with G then the documentation for IC_MAX_G will be right after IC_MAX - this seems preferable!
Thanks for taking a look! I think that all of this work will pay off in code safety.