Hello everyone! We would like to propose a new attribute callsite_wrapper to be able to replace functions with a wrapper that is instantiated for every callsite and has access to the source location (file, line) of the callsite.
The main goal of this RFC is to discuss the possibility of having such an attribute in Clang, and the secondary goal is to discuss possible implementation. We have created a proof of concept for this attribute, so we know it is possible, but it would be nice to find an alternative, simpler implementation.
Problem
In the Linux kernel, we have a need to collect information about every memory allocation and attribute this information to the place where the allocation originated. We need to achieve this with almost zero performance penalty, so regular memory sanitizing instrumentation is not a solution for us.
To do this, we currently use the preprocessor to replace all memory-allocating functions with a block that creates a static variable with callsite information (file, line, etc.) and accumulated statistics (e.g., how much memory was allocated/freed). This allows us to prepare these structures for every callsite at compile time, and the only additional thing happening at runtime is accumulating statistics, so the performance of this solution is quite good. You can see the implementation here and usage here. I will put these examples here for convenience:
#define vmalloc(...) alloc_hooks(vmalloc_noprof(__VA_ARGS__))
#define alloc_hooks(_do_alloc) \
({ \
DEFINE_ALLOC_TAG(_alloc_tag); \
alloc_hooks_tag(&_alloc_tag, _do_alloc); \
})
#define DEFINE_ALLOC_TAG(_alloc_tag) \
static struct alloc_tag _alloc_tag __used __aligned(8) \
__section(ALLOC_TAG_SECTION_NAME) = { \
.ct = CODE_TAG_INIT, \
.counters = &_shared_alloc_tag };
#define CODE_TAG_INIT { \
.modname = CT_MODULE_NAME, \
.function = __func__, \
.filename = __FILE__, \
.lineno = __LINE__, \
.flags = 0, \
}
We only need macros because we need source location substitutions (__FILE__, __LINE__, etc.) to point to the callsite. And macros also help to âinstantiateâ these static variables for every callsite. But the problem with using the preprocessor is that it is not context-aware and will replace everything that is named like a memory-allocating function. For example, if you name a variable malloc to store a pointer to an allocating function, you would get a compile error because macros would try to replace its name and will fail. And yes, getting a pointer of an instrumented allocation function is also not possible with macros.
Proposed solution
Instead of having a âpreprocessor function,â we can have a regular function that has these properties:
- Every call to this function will instantiate a new function, so that each callsite could have its own static variables.
- Every source location expression like
__builtin_FILE()in the function should point to the callee. We canât use preprocessor macros like__FILE__because the preprocessor will replace it before the function is instantiated.
I have created a proof of concept for this attribute here.
It integrates this feature in Sema and uses a combination of SubstDecl and InstantiateFunctionDefinition to instantiate function clones, see BuildCallsiteWrapperDeclarationNameExpr. Then it customizes TreeTransform, so that SourceLocExpr in the instantiated function gets Context and Loc from the callsite. The rest of the code is needed for intercepting function dereferences (see BuildDeclarationNameExpr) and to make instantiation work properly outside of template context.
Alternative solutions
There are no existing solutions in the compiler to achieve the same behavior and performance as we have with the preprocessor implementation. However, it is worth mentioning a few relevant things:
Default arguments with source location expressions:
void foo(const char* file = __builtin_FILE()) {
...
}
Default arguments are only allowed in C++, not in C. Even if we had default arguments in C, this solution doesnât clone functions and doesnât allow per-callsite static storage.
Templates
It would be nice to have a non-type template argument that defaults to a source location expression:
template <int line = __builtin__LINE()>
void foo() {
...
}
This would achieve our goals of instantiating different functions for different callsites. But templates currently donât have support for non-type string variables, which we would need for __builtin__FILE(). And we donât have templates in C. Any attempt to bring it there, even without template syntax, is doomed. See my talk at the LPC 2023 conference.