aboutsummaryrefslogtreecommitdiffhomepage
path: root/content/2024-04-24-fn-wrapper-macro-magic/index.md
blob: 708336b2accbbfa6e9e89c3f3acc9c84646b3c84 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
+++
title = "Generate function wrapper with macro magic"

[taxonomies]
tags = ["c++"]
+++

**Short disclaimer at front**.
This post discusses about c/cpp macros, which I generally try to minimize or
avoid altogether. However, they do have their applications and can be useful.
All I want to say is, use the right amount of macros for the specific context
and consider your colleagues if used in production. :^)

---

Recently I had the need to wrap a list of c functions and execute some common
task before and after each wrapped function call. Hence, the body of each
wrapper function was identical. Theses 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. Progressing from top to
bottom, the amount of macro code (*magic*?) increases.

To examine 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-24-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-24-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-24-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-24-fn-wrapper-macro-magic/wrap-v4.cc") }}
```
> The implementation to handle empty arguments **CPP_ARGC() == 0** uses a gcc
> extension for the **##** operator. This is also supported by clang, but not
> by msvc. Maybe I will come back in the future to update this, if I have the
> need.

## Appendix: CONCAT1 and CONCAT2?

> *Macro arguments are completely macro-expanded before they are substituted
> into a macro body, unless they are stringized or pasted with other tokens
> [..]* - [gcc][cpp-args-prescan]

```cpp
#define FOO() ABC

#define CONCAT2(lhs, rhs) lhs ## rhs
#define CONCAT1(lhs, rhs) CONCAT2(lhs, rhs)

CONCAT2(MOOOSE_, FOO())
// expands to:
//   MOOOSE_FOO()

CONCAT1(MOOOSE_, FOO())
// expands to:
//   MOOOSE_ABC
```

## Appendix: Listify codegen data

A hacker friend once showed me some neat macro magic for organizing data
relevant to code generation within a list that can be reused in multiple
contexts.

I want to archive this technique here, as it fits well in the topic.

The idea is that the list can accept a macro, which is then applied to each
entry. The processing macros, may not need to use all the elements in an entry.

The example below is somewhat arbitrary and one may not see the benefit, but it
is serves to demonstrate the technique. However, this technique really shines
when dealing with larger datasets that are used in different locations to
generate code.

```cpp
#define EXCEPTIONS(M)            \
  M(ill_opc  , "Illegal opcode") \
  M(mem_fault, "Memory fault")   \
  M(inv_perm , "Invalid permissions")

enum exception {
#define ELEM(e, _) e,
  EXCEPTIONS(ELEM)
#undef ELE
};

const char* describe(exception e) {
  switch (e) {
#define CASE(e, d) \
  case e:          \
    return d;
    EXCEPTIONS(CASE)
#undef CASE
  }
}
```

This technique can also be used to generate the function wrappers when 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 conclude this appendix, let me share a quote from that same great hacker
friend:
> *This is the excel-ification of cpp code*.

## References

- Count number of elements in VA_ARGS [[ref][va-narg]]
- Preprocessor 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