Saturday, January 12, 2013

gcc: moving to compile time

NOTE: below is gcc specific.

It's always hard to come up with examples, let's say we have a processing item

struct __item {
        int flags;
        int prio;
        void *priv;

a number of possible types

#define IT_TYPE_A       (1 << 4)
#define IT_TYPE_B       (1 << 3)
#define IT_TYPE_C       (1 << 2)
#define IT_TYPE_ANY     (1 << 1)

and a usage example:

        struct __item it = {
                .flags = IT_TYPE_ANY | IT_TYPE_C,
                .prio = 0,
                .priv = "cron item processing",

Let's say, we deprecate (you know, because of reasons(tm)) IT_TYPE_ANY field.
Now we need to check and correct passed items each time process_item() called,
even for compile-time known flag values, like the above one. In a trivial situation
this might be almost free operation, but it depends.

A solution could be to extract a happy path -- for compile time known
values, and a slow path -- for run-time checks and adjustments.

IOW, we need to hide our original process_item() and define new function

__fortify_function int process_item(struct __item *it)
        if (__builtin_constant_p (it->flags)) {
                if ((it->flags & IT_TYPE_ANY) != 0) {
                        it->flags &= ~IT_TYPE_ANY;
                        it->flags |= IT_TYPE_A;

                        /** something important **/
        return __process_item(it);

that'd be able to test and correct passed items. __builtin_constant_p() is a
gcc internal, that returns 1 if compiler can prove that value is correct and known
at compile time (with some exceptions), and __fortify_function is
__extern_always_inline __attribute_artificial__

We also might be interested in informing programmer about usage of a
deprecated flag value, therefore we define empty function

extern void __attribute__((deprecated)) __warn_type_any_deprecated() {};

Now compiler fires a warning each time it sees __warn_type_any_deprecated():

gcc -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -O2 test.c -o a.out
In file included from test.c:21:0:
item.h: In function ‘process_item’:
item.h:34:4: warning: ‘__warn_type_any_deprecated’ is deprecated (declared at item.h:25) [-Wdeprecated-declarations]

The good thing about this is that for __builtin_constant_p() case compiler will try
to do all checks and flag adjustments behind the scene, producing code directly
for "IT_TYPE_A | IT_TYPE_C" case:

    movl   $0x0,0x4(%rsp)
    movq   $0x4006be,0x8(%rsp)
    movl   $0x14,(%rsp)
    callq  0x400580 <__process_item>

note movl   $0x14,(%rsp) instead of movl   $0x6,(%rsp).

Developer, however, is still able to ignore (or simply miss) warning, so
for radical cases we can fail build process.

sys/cdefs.h header file contains several defines that could be useful

145 #if __GNUC_PREREQ (4,3)
146 # define __warndecl(name, msg) \
147   extern void name (void) __attribute__((__warning__ (msg)))
148 # define __warnattr(msg) __attribute__((__warning__ (msg)))
149 # define __errordecl(name, msg) \
150   extern void name (void) __attribute__((__error__ (msg)))
151 #else                                                                                
152 # define __warndecl(name, msg) extern void name (void)
153 # define __warnattr(msg)
154 # define __errordecl(name, msg) extern void name (void)
155 #endif

Changing __warn_type_any_deprecated() prototype to

__warndecl (__warn_type_any_deprecated, "\n\tWARNING: TYPE ANY has been deprecated");

will change output to:

gcc -D_FORTIFY_SOURCE=2 -D_GNU_SOURCE -O2 test.c -o a.out
In file included from test.c:21:0:
In function ‘process_item’,
    inlined from ‘main’ at test.c:63:14:
item.h:34:30: warning: call to ‘__warn_type_any_deprecated’ declared with attribute warning:
    WARNING: TYPE ANY has been deprecated [enabled by default]

and fail linkage (pretty hard to ignore), because __warndecl() declares function
w/o body:

test.c:(.text.startup+0x1d): undefined reference to `__warn_type_any_deprecated'