From aa3f6e0a8109ab4cc759b23c9feccff9cee4e876 Mon Sep 17 00:00:00 2001 From: Johannes Stoelp Date: Wed, 24 Apr 2024 01:07:55 +0200 Subject: fn-wrapper: initial version of macro magic --- content/2024-04-24-fn-wrapper-macro-magic/Makefile | 11 ++ content/2024-04-24-fn-wrapper-macro-magic/index.md | 147 +++++++++++++++++++++ .../2024-04-24-fn-wrapper-macro-magic/wrap-v1.cc | 19 +++ .../2024-04-24-fn-wrapper-macro-magic/wrap-v2.cc | 14 ++ .../2024-04-24-fn-wrapper-macro-magic/wrap-v3.cc | 30 +++++ .../2024-04-24-fn-wrapper-macro-magic/wrap-v4.cc | 47 +++++++ 6 files changed, 268 insertions(+) create mode 100644 content/2024-04-24-fn-wrapper-macro-magic/Makefile create mode 100644 content/2024-04-24-fn-wrapper-macro-magic/index.md create mode 100644 content/2024-04-24-fn-wrapper-macro-magic/wrap-v1.cc create mode 100644 content/2024-04-24-fn-wrapper-macro-magic/wrap-v2.cc create mode 100644 content/2024-04-24-fn-wrapper-macro-magic/wrap-v3.cc create mode 100644 content/2024-04-24-fn-wrapper-macro-magic/wrap-v4.cc 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`1 +> 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 + +
+ +**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); -- cgit v1.2.3