Proposal: Allow Function Intstrumentation

Hi all,

in Languages like C, C++, Fortran, … with the right compilers it is possible to do function instrumentation. If you don’t know what it is, let me explain:
Consider a C-function like

int foo() {
    // do some work
    return 0;
}

Function instrumentation would insert so called function hooks during compilation, such that the function that gets compiles looks more like this:

int foo() {
    function_hook_enter();
    // do some work
    function_hook_exit();
    return 0;
}

The source code remains unchanged. The function hook definition can be supplied via a library during linking, or be part of the same source file.
In gcc, clang, icc, and other compilers there exists the -finstrument-funcitons flag to trigger this behavior.
As this goes against one of Zig’s most fundamental principles (no hidden control flow) I would like to explain why such an option might be useful.
Imagine you have a complex code and you would like to profile the functions. This is often done via sampling. A sampling-profiler checks, the current call-tree from time to time and gives information about the calling statistics in the end.
This can be flawed as not everything is covered, calls might be missed. timings might be off, …
With function instrumentation, one could keep track of the exact calling order, count how often something was called, and so on. By allowing a flag like -finstrument-functions one would also be independent from some weird compiler wrapper, that instruments with predefined hooks. One could supply ones own hooks.
Some compiler vendors have more or less standardized the hook definition to look like this:

void __cyg_profile_func_enter (void *this_fn, void *call_site)
void __cyg_profile_func_exit  (void *this_fn, void *call_site)

Here is a small C-example:

#include <stdio.h>
#include <time.h>

int level = -1;
__attribute__ ((no_instrument_function))
void __cyg_profile_func_enter (void *this_fn, void *call_site) {
    level++;
    for (int i=0; i<level; i++) {printf("  ");}
    printf("Entering function %p at time %d\n", this_fn, (int)time(NULL));
}

__attribute__ ((no_instrument_function))
void __cyg_profile_func_exit  (void *this_fn, void *call_site) {
    for (int i=0; i<level; i++) {printf("  ");}
    printf("Leaving function %p at time %d\n", this_fn, (int)time(NULL));
    level--;
}

int bar() {
    sleep(1);
    return 1;
}

int foo() {
    sleep(2);
    return bar();
}


int main() {
    sleep(1);
    foo();
    bar();
    sleep(2);
    return 0;
}

When compiling with gcc and running the code one gets:

gcc -c -finstrument-functions instr.c
gcc -o instr.x -finstrument-functions instr.o 
$ ./instr.x 
Entering function 0x55c86168132c at time 1745233450
  Entering function 0x55c8616812d0 at time 1745233451
    Entering function 0x55c86168127b at time 1745233453
    Leaving function 0x55c86168127b at time 1745233454
  Leaving function 0x55c8616812d0 at time 1745233454
  Entering function 0x55c86168127b at time 1745233454
  Leaving function 0x55c86168127b at time 1745233455
Leaving function 0x55c86168132c at time 1745233457

This would give a developer a tool to figure out, if their code needs tweaking at a specific routine, that takes a lot of time, or if some routine is called so often, that the calling overhead slows down program execution. Upon release one would discard the flag from the build process (or one could introduce a DebugProfile, or ReleaseProfile mode) and the code behaves perfectly normal.

I would like to hear your thoughts about this instrumentation and would like to propose to incorporate the -finstrument-function flag into the compiler, as well as propose that the __cyg_profile_func definitions are used, as they are basically standardized among different compiler vendors.

Cheers.

1 Like

Seems like an interesting concept.
You can reach similar behavior already, by having the “library” you want to validate have exposed such hook functions, that would be noops if not provided.

I know that is not your most desired outcome, but to be honest, the chance of this getting into the language is really low (too much hidden stuff not visible from the function itself).

See this link and another link. Most likely would be added when Zig is stable in 1.0.

6 Likes

This would be a non-central example of that principle in my opinion. For example, 0.14 has an integrated fuzzer, which among other things instruments the code so that reached lines can be displayed in the WebUI.

I don’t think that’s hidden control flow in the way which matters, despite directly modifying code at link time. A more general mechanism to instrument functions shouldn’t be considered as such either.

6 Likes