Search code examples
cc99dead-code

Dead code removal if implementation is overwritten


I'm in the process of writing a library that provides a sha256 implementation. The library will be given to vendors that may want to provide their own sha256 functions that are optimized for their platform. So, the API of this library allows the client to pass in function pointers to their sha256 code.

int mylib_set_sha256_impl( /* function pointers */ );

Henceforth, all algorithms will use the function pointers provided internally instead of the stock sha256 code provided by the library.

The question is: how can I facilitate dead code removal during link time, such that the default sha256 implementation in this library is removed??

This is an API design question as much as it is a compiler-optimization question.


Solution

  • There are no special requirement as far as linker is concerned, and no weak aliasing need be considered.

    The basic rule is: As long as the user code doesn't reference any symbols in a given .c or .s file that you linked into your library, the said file's contents won't end up in the executable.

    In the scenario you describe, your functions may never become dead code since your "live" code perhaps references their addresses to set up default function pointer values. To make sure that doesn't happen, you have to do the following:

    1. Refer to the replaceable functions only from within a user-callable optional default_init function.

    2. The default_init must never be called anywhere in your code, neither must any of the functions be referenced anywhere but within default_init.

    3. Have the replaceable functions, and the init function, put into at least one .c or assembly file that is not used for any of the other code.

    For your user to replace all the functions, they simply have to never call the default_init function. If you wish functions to be replaceable one-by-one, you have to, additionally:

    1. Have each replaceable function in its own .c file.

    2. Have the user not call the default_init directly, but pass the desired default or user-provided implementations to your init function.

    What you're doing, in effect, isn't "overwriting" any implementation, but simply not using it at all.

    Example (include guards omitted for clarity):

    // api.h
    void api_fun1(void);
    void api_fun2(void);
    void api_default_init(void);
    void api_user_init(void (*f1)(void), void (*f2)(void));
    void api_use_funs(void);
    
    // api_internal.h
    extern void (*api_f1)(void);
    extern void (*api_f2)(void);    
    
    // common.c
    #include "api.h"
    #include "api_internal.h"
    void (*api_f1)(void);
    void (*api_f2)(void);
    
    void api_user_init(void (*f1)(void), void (*f2)(void)) {
      api_f1 = f1;
      api_f2 = f2;
    }
    
    void api_use_funs(void) {
      api_f1();
      api_f2();
    }
    
    // api_fun1.c
    #include "api.h"
    void api_fun1(void) {}
    
    // api_fun2.c
    #include "api.h"
    void api_fun2(void) {}
    
    // api_default_init.c
    #include "api.h"
    #include "api_internal.h"
    
    void api_default_init(void) {
      api_f1 = api_fun1;
      api_f2 = api_fun2;
    }
    

    Let's say that the user wants to override api_fun2 with their own:

    // main.c
    #include "api.h"
    #include <stdio.h>
    
    void my_fun2() {
      printf("%s\n", __FUNCTION__);
    }
    
    int main() {
      api_user_init(api_fun1, my_fun2);
      api_use_funs();
    }