Rewrite GCC cleanup macro with nested function for Clang?
Asked Answered
I

3

6

I'm trying to work through an issue on a third party library. The issue is the library uses GCC's nested functions buried in a macro, and Clang does not support nested functions and has no plans to do so (cf., Clang Bug 6378 - error: illegal storage class on function).

Here's the macro that's the pain point for me and Clang:

#define RAII_VAR(vartype, varname, initval, dtor) \
    /* Prototype needed due to http://gcc.gnu.org/bugzilla/show_bug.cgi?id=36774 */ \
    auto void _dtor_ ## varname (vartype * v); \
    void _dtor_ ## varname (vartype * v) { dtor(*v); } \
    vartype varname __attribute__((cleanup(_dtor_ ## varname))) = (initval)

And here's how its used (from the code comments):

 * void do_stuff(const char *name)
 * {
 *     RAII_VAR(struct mything *, thing, find_mything(name), ao2_cleanup);
 *     if (!thing) {
 *         return;
 *     }
 *     if (error) {
 *         return;
 *     }
 *     do_stuff_with_thing(thing);
 * }

The Clang User Manual states to use C++ and a lambda function to emulate. I'm not sure that's the best strategy, and a C project will likely not accept a C++ patch (they would probably tar and feather me first).

Is there a way to rewrite the macro so that's its (1) more accommodating to Clang, and (2) preserves original function semantics?

Ishtar answered 25/7, 2014 at 15:38 Comment(4)
Another offender is GLIBC. See Why do I get:'#error "glibc cannot be compiled without optimization"', when trying to compile GNU libc with GNU CC?Ishtar
By the way, that's Asterisk: svn.asterisk.org/svn/asterisk/trunk/include/asterisk/utils.h If you have gotten clang to compile the RAII_VAR macro, pushing that contribution up stream would be hugely appreciated. There's several open issues for that.Knepper
Unless, of course, one of you did contribute the patch on this issue: issues.asterisk.org/jira/browse/ASTERISK-20850 :-)Knepper
@Matt - yes, Asterisk is the project I was trying to compile :)Ishtar
G
8

Clang doesn't support GCC nested functions, but it does support Objective C-style "blocks", even in C mode:

void f(void * d) {
    void (^g)(void *) = ^(void * d){ };
    g(d);
}

You need to invoke it with the clang command rather than gcc, and also (?) pass -fblocks -lBlocksRuntime to the compiler.

You can't use a block as a cleanup value directly, since it has to be a function name, so (stealing ideas from here) you need to add a layer of indirection. Define a single function to clean up void blocks, and make your RAII'd variable the block that you want to run at the end of the scope:

typedef void (^cleanup_block)(void);
static inline void do_cleanup(cleanup_block * b) { (*b)(); }

void do_stuff(const char *name) {
    cleanup_block __attribute__((cleanup(do_cleanup))) __b = ^{ };
}

Because blocks form closures, you can then place the operations on your variables to cleanup directly inside that block...

void do_stuff(const char *name) {
    struct mything * thing;
    cleanup_block __attribute__((cleanup(do_cleanup))) __b = ^{ ao2_cleanup(thing); };
}

...and that should run at the end of the scope as before, being invoked by the cleanup on the block. Rearrange the macro and add a __LINE__ so it works with multiple declarations:

#define CAT(A, B) CAT_(A, B)
#define CAT_(A, B) A##B

#define RAII_VAR(vartype, varname, initval, dtor) \
    vartype varname = (initval); \
    cleanup_block __attribute__((cleanup(do_cleanup))) CAT(__b_, __LINE__) = ^{ dtor(varname); };

void do_stuff(const char *name) {
    RAII_VAR(struct mything *, thing, NULL, ao2_cleanup);
    ...

Something like that, anyway.

Gleesome answered 25/7, 2014 at 16:45 Comment(0)
K
1

I believe you can do this without using a clang-specific version, I'd try something like this (untested, may require a few extra casts):

struct __destructor_data {
    void (*func)(void *);
    void **data;
}

static inline __destructor(struct __destructor_data *data)
{
    data->func(*data->data);
}

#define RAII_VAR(vartype, varname, initval, dtor)  \
    vartype varname = initval;                     \
    __attribute((cleanup(__destructor)))           \
        struct __destructor_data __dd ## varname = \
             { dtor, &varname };

In our project we have a gcc-specific _auto_(dtor) macro that precedes the normal variable declaration, e.g.:

_auto_(free) char *str = strdup("hello");

In this case our macro can't add anything after the variable declaration and also doesn't know the name of the variable, so to avoid using gcc-specific nested functions I came up with the following hackish version in case this helps anyone:

static void *__autodestruct_value = NULL;
static void (*__autodestruct_dtor)(void *) = NULL;

static inline void __autodestruct_save_dtor(void **dtor)
{
       __autodestruct_dtor = *dtor;
       __autodestruct_dtor(__autodestruct_value);
}

static inline void __autodestruct_save_value(void *data)
{
       __autodestruct_value = *(void **) data;
}

#define __AUTODESTRUCT(var, func)                              \
       __attribute((cleanup(__autodestruct_save_dtor)))      \
               void *__dtor ## var = (void (*)(void *))(func); \
       __attribute((cleanup(__autodestruct_save_value)))
 
#define _AUTODESTRUCT(var, func)                       \
       __AUTODESTRUCT(var, func)

#define _auto_(func)                                    \
        _AUTODESTRUCT(__COUNTER__, func)

This is hackish because it depends on the order the destructors are called by the compiler being the reverse of the order of the declarations, and it has a few obvious downsides compared to the gcc-specific version but it works with both compilers.

Krusche answered 17/5, 2021 at 22:1 Comment(2)
The second solution seems thread-unsafe.Coinstantaneous
Yep, I think it is. Would declaring the variables static __thread fix this?Krusche
B
1

Building on the answers above, here's my hack to allow clang to compile nested procedures written in gcc-extension style. I needed this myself to support a source-to-source translator for an Algol-like language (Imp) which makes heavy use of nested procedures.

#if   defined(__clang__)
  #define _np(name, args) (^name)args = ^args
  #define auto
#elif defined(__GNUC__)
  #define _np(name, args) name args
#else
  #error Nested functions not supported
#endif

int divide(int a, int b) {

  #define replace(args...) _np(replace, (args))
  auto int replace(int x, int y, int z) {
    #undef replace

    if (x == y) return z; else return x;

  };

  return a / replace(b,0,1);
}

int main(int argc, char **argv) {
  int a = 6, b = 0;
  fprintf(stderr, "a / b = %d\n", divide(a, b));
  return 0;
}
Belshin answered 24/3, 2022 at 23:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.