aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJohannes Stoelp <johannes.stoelp@gmail.com>2024-04-24 01:07:55 +0200
committerJohannes Stoelp <johannes.stoelp@gmail.com>2024-04-24 01:07:55 +0200
commitaa3f6e0a8109ab4cc759b23c9feccff9cee4e876 (patch)
treef31e5f8541fbc02fe00a1fe2bbd030d444dd4c75
parentbc6907e58ba86da8c30a29c98b998832c4b260d3 (diff)
downloadblog-aa3f6e0a8109ab4cc759b23c9feccff9cee4e876.tar.gz
blog-aa3f6e0a8109ab4cc759b23c9feccff9cee4e876.zip
fn-wrapper: initial version of macro magic
-rw-r--r--content/2024-04-24-fn-wrapper-macro-magic/Makefile11
-rw-r--r--content/2024-04-24-fn-wrapper-macro-magic/index.md147
-rw-r--r--content/2024-04-24-fn-wrapper-macro-magic/wrap-v1.cc19
-rw-r--r--content/2024-04-24-fn-wrapper-macro-magic/wrap-v2.cc14
-rw-r--r--content/2024-04-24-fn-wrapper-macro-magic/wrap-v3.cc30
-rw-r--r--content/2024-04-24-fn-wrapper-macro-magic/wrap-v4.cc47
6 files changed, 268 insertions, 0 deletions
diff --git a/content/2024-04-24-fn-wrapper-macro-magic/Makefile b/content/2024-04-24-fn-wrapper-macro-magic/Makefile
new file mode 100644
index 0000000..2347615
--- /dev/null
+++ b/content/2024-04-24-fn-wrapper-macro-magic/Makefile
@@ -0,0 +1,11 @@
+check:
+ g++ -fsyntax-only wrap-v1.cc
+ g++ -fsyntax-only wrap-v2.cc
+ g++ -fsyntax-only wrap-v3.cc
+ g++ -fsyntax-only wrap-v4.cc
+
+cpp:
+ g++ -E wrap-v1.cc | clang-format
+ g++ -E wrap-v2.cc | clang-format
+ g++ -E wrap-v3.cc | clang-format
+ g++ -E wrap-v4.cc | clang-format
diff --git a/content/2024-04-24-fn-wrapper-macro-magic/index.md b/content/2024-04-24-fn-wrapper-macro-magic/index.md
new file mode 100644
index 0000000..fe45614
--- /dev/null
+++ b/content/2024-04-24-fn-wrapper-macro-magic/index.md
@@ -0,0 +1,147 @@
++++
+title = "Generate function wrapper with macro magic"
+
+[taxonomies]
+tags = ["c++"]
++++
+
+**Short disclaimer at front**.
+This post discusses about c/cpp macros, which in general, I try to use as
+little as possible or avoid at all. However, they have their application and
+can be useful.
+All I want to say is, use the right amount of macros for the corresponding
+context and think about your colleagues if used in production. :^)
+
+---
+
+Recently I had the need to wrap a list of c functions and perform some common
+work before and after each wrapped function had been invoked. Hence, the body
+of each wrapper function was identical.
+The wrappers were compiled and linked into a shared library which then was used
+for **LD_PRELOAD**ing.
+
+In this post I want to collect and archive a few approaches to generate the
+required boilerplate code using preprocessor macros. Going from top to bottom,
+the amount of macro code (*magic*?) increases.
+
+To inspect the generated code, one can use the following command with all the
+code examples from this post.
+```
+g++ -E file.cc | clang-format
+```
+> With the **-E** option, g++ stops after the preprocessor stage; output is
+> written to *stdout*.
+> Alternatively, one can just invoke the preprocessor **cpp file.cc**.
+
+To syntax check the code examples, one can use the following command.
+```
+g++ -fsyntax-only file.cc
+```
+
+## Version 1
+
+The first version provides a *function definition* like macro, here called
+`WRAP`. When creating a wrapper function, this macro is directly used to define
+the wrapper. One declares the signature in the WRAP macro, which is then
+followed by curly braces including the wrapper body.
+
+The benefit of this approach is that the wrapper implementation is quite
+explicit and a reader, not familiar with the WRAP macro, might have a good
+chance to reason about what the code is doing. The draw back is that bodies
+have to be repeated for each wrapper.
+
+```cpp
+{{ include(path="content/2024-04-22-fn-wrapper-macro-magic/wrap-v1.cc") }}
+```
+
+> In the code examples, I use the `MOCK_WRAPPER_IMPL`<sup><a href="#sup-1">1</a></sup>
+> macro, to mock away the common pre and post work and to invoke the real
+> function.
+
+## Version 2
+
+The second version provides a simple approach to also generate the wrapper
+bodies by explicitly writing out a typed list of arguments and a list of
+argument names.
+
+The main drawbacks are that the wrapper definition as well as the body of the
+`WRAP` macro are somewhat obfuscated, which makes it hard for a reader to
+reason about the code. Additionally, the arguments must be specified twice,
+when defining a wrapper.
+
+```cpp
+{{ include(path="content/2024-04-22-fn-wrapper-macro-magic/wrap-v2.cc") }}
+```
+
+## Version 3
+
+The third version provides macros to generate the list of typed arguments as
+well as the list of argument names. Then for the different *arity* of the
+wrapper function, corresponding `WRAP1, WRAP2` macros are provided to generate
+the boilerplate code with the correct number of arguments.
+
+The example only supports function with **one** or **two** arguments, but the
+code can easily be extended.
+
+```cpp
+{{ include(path="content/2024-04-22-fn-wrapper-macro-magic/wrap-v3.cc") }}
+```
+
+## Version 4
+
+The fourth version improves the third one by automatically using the correct
+`TYPEDARGS` and `ARGS` macro depending on the *arity* of the wrapper function.
+
+This is achieved by dynamically constructing the correct macro name during
+preprocessing time. The technique to count the number of elements in the
+variadic argument list [__VA_ARGS__][va-args] is presented in
+[__VA_NARG__][va-narg].
+
+```cpp, hide_lines=41-100
+{{ include(path="content/2024-04-22-fn-wrapper-macro-magic/wrap-v4.cc") }}
+```
+
+## Appendix: Listify codegen data
+
+A hacker friend once showed me some neat macro magic, to organize data,
+relevant for generating code in some list, which can then be used at multiple
+places.
+
+Since it fits topic wise, I want to archive this technique here as well.
+
+The idea is that the list accepts a macro, which is applied to each entry. The
+following is an example, using the `WRAP` macro from [version 4](#version-4).
+
+```cpp
+#define API(M) \
+ M(int, foo, const char*) \
+ M(int, bar, const char*, const char*) \
+ M(int, baz, char, int, unsigned)
+
+API(WRAP)
+```
+
+To close this appendix with some words the same great hacker friend once said:
+> *This is the excel-ification of cpp code*.
+
+## References
+
+- Count number of elements in VA_ARGS [[ref][va-narg]]
+- Variadic macros [[ref][va-args]]
+- Preprocessor concatenation [[ref][cpp-concatenation]]
+- Preprocessor macro argument handling [[ref][cpp-macro-args]]
+- Preprocessor argument prescan [[ref][cpp-args-prescan]]
+
+## Footnotes
+
+<div id="sup-1"></div>
+
+**1)** In the examples above, **MOCK_WRAPPER_IMPL** is a macro. However it does
+not need to be one, as it can be seen in the [fn_hook.cc][fn-hook] example.
+
+[va-narg]: https://groups.google.com/g/comp.std.c/c/d-6Mj5Lko_s
+[va-args]: https://gcc.gnu.org/onlinedocs/cpp/Variadic-Macros.html
+[cpp-concatenation]: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html
+[cpp-macro-args]: https://gcc.gnu.org/onlinedocs/cpp/Macro-Arguments.html
+[cpp-args-prescan]: https://gcc.gnu.org/onlinedocs/cpp/Argument-Prescan.html
+[fn-hook]: https://git.memzero.de/testground/cpp-templates/tree/fn_hook.cc
diff --git a/content/2024-04-24-fn-wrapper-macro-magic/wrap-v1.cc b/content/2024-04-24-fn-wrapper-macro-magic/wrap-v1.cc
new file mode 100644
index 0000000..d3f3ddf
--- /dev/null
+++ b/content/2024-04-24-fn-wrapper-macro-magic/wrap-v1.cc
@@ -0,0 +1,19 @@
+// Mock the common wrapper functionality. For the purpose of this discussion,
+// this is just a sink accepting any function argument.
+#define MOCK_WRAPPER_IMPL(ret, fn) \
+ /* do common work */ \
+ static ret wrap_##fn(...);
+
+// Utility to generate wrapper boilerplate.
+#define WRAP(ret, fn, ...) \
+ MOCK_WRAPPER_IMPL(ret, fn) \
+ \
+ extern "C" ret fn(__VA_ARGS__)
+
+WRAP(int, foo, const char* name) {
+ return wrap_foo(name);
+}
+
+WRAP(int, bar, const char* name, const char* value) {
+ return wrap_bar(name, value);
+}
diff --git a/content/2024-04-24-fn-wrapper-macro-magic/wrap-v2.cc b/content/2024-04-24-fn-wrapper-macro-magic/wrap-v2.cc
new file mode 100644
index 0000000..4a71635
--- /dev/null
+++ b/content/2024-04-24-fn-wrapper-macro-magic/wrap-v2.cc
@@ -0,0 +1,14 @@
+#define MOCK_WRAPPER_IMPL(ret, fn) \
+ /* do common work */ \
+ static ret wrap_##fn(...);
+
+// Utility to generate wrapper boilerplate.
+#define WRAP(ret, fn, typed_args, args) \
+ MOCK_WRAPPER_IMPL(ret, fn) \
+ \
+ extern "C" ret fn typed_args { \
+ return wrap_##fn args; \
+ }
+
+WRAP(int, foo, (const char* name), (name))
+WRAP(int, bar, (const char* name, const char* value), (name, value))
diff --git a/content/2024-04-24-fn-wrapper-macro-magic/wrap-v3.cc b/content/2024-04-24-fn-wrapper-macro-magic/wrap-v3.cc
new file mode 100644
index 0000000..26e0913
--- /dev/null
+++ b/content/2024-04-24-fn-wrapper-macro-magic/wrap-v3.cc
@@ -0,0 +1,30 @@
+#define ARGS0()
+#define ARGS1() a0
+#define ARGS2() a1, ARGS1()
+
+#define TYPEDARGS0()
+#define TYPEDARGS1(type) type a0
+#define TYPEDARGS2(type, ...) type a1, TYPEDARGS1(__VA_ARGS__)
+
+#define MOCK_WRAPPER_IMPL(ret, fn) \
+ /* do common work */ \
+ static ret wrap_##fn(...);
+
+// Utility to generate wrapper boilerplate for functions with *one* argument.
+#define WRAP1(ret, fn, ty1) \
+ MOCK_WRAPPER_IMPL(ret, fn) \
+ \
+ extern "C" ret fn(TYPEDARGS1(ty1)) { \
+ return wrap_##fn(ARGS1()); \
+ }
+
+// Utility to generate wrapper boilerplate for functions with *two* arguments.
+#define WRAP2(ret, fn, ty1, ty2) \
+ MOCK_WRAPPER_IMPL(ret, fn) \
+ \
+ extern "C" ret fn(TYPEDARGS2(ty1, ty2)) { \
+ return wrap_##fn(ARGS2()); \
+ }
+
+WRAP1(int, foo, const char*)
+WRAP2(int, bar, const char*, const char*)
diff --git a/content/2024-04-24-fn-wrapper-macro-magic/wrap-v4.cc b/content/2024-04-24-fn-wrapper-macro-magic/wrap-v4.cc
new file mode 100644
index 0000000..ea5e41e
--- /dev/null
+++ b/content/2024-04-24-fn-wrapper-macro-magic/wrap-v4.cc
@@ -0,0 +1,47 @@
+// Get Nth argument.
+#define CPP_NTH(_0, _1, _2, _3, n, ...) n
+// Get number of arguments (uses gcc/clang extension for empty argument).
+#define CPP_ARGC(...) CPP_NTH(_0, ##__VA_ARGS__, 3, 2, 1, 0)
+
+// Utility to concatenate preprocessor tokens.
+#define CONCAT2(lhs, rhs) lhs##rhs
+#define CONCAT1(lhs, rhs) CONCAT2(lhs, rhs)
+
+#define ARGS0()
+#define ARGS1() a0
+#define ARGS2() a1, ARGS1()
+#define ARGS3() a2, ARGS2()
+
+// Invoke correct ARGSn macro depending on #arguments.
+#define ARGS(...) CONCAT1(ARGS, CPP_ARGC(__VA_ARGS__))()
+
+#define TYPEDARGS0()
+#define TYPEDARGS1(ty) ty a0
+#define TYPEDARGS2(ty, ...) ty a1, TYPEDARGS1(__VA_ARGS__)
+#define TYPEDARGS3(ty, ...) ty a2, TYPEDARGS2(__VA_ARGS__)
+
+// Invoke correct TYPEDARGSn macro depending on #arguments.
+#define TYPEDARGS(...) CONCAT1(TYPEDARGS, CPP_ARGC(__VA_ARGS__))(__VA_ARGS__)
+
+#define MOCK_WRAPPER_IMPL(ret, fn) \
+ /* do common work */ \
+ static ret wrap_##fn(...);
+
+// Utility to generate wrapper boilerplate.
+#define WRAP(ret, fn, ...) \
+ MOCK_WRAPPER_IMPL(ret, fn) \
+ \
+ extern "C" ret fn(TYPEDARGS(__VA_ARGS__)) { \
+ return wrap_##fn(ARGS(__VA_ARGS__)); \
+ }
+
+WRAP(int, foo, const char*)
+WRAP(int, bar, const char*, const char*)
+WRAP(int, baz, char, int, unsigned)
+
+// -- TESTS --------------------------------------------------------------------
+
+static_assert(CPP_ARGC() == 0);
+static_assert(CPP_ARGC(int) == 1);
+static_assert(CPP_ARGC(int, int) == 2);
+static_assert(CPP_ARGC(int, int, int) == 3);