5

This is ultimately a C question that arose when studying code in completion.h of the Linux kernel source, where I see a C technique I've never used in C before. Although have a vague sense what it's doing, I'd like to fine tune my understanding with a precise description, and I'm not quite sure how to search for the answer with Google without a potentially a long ordeal.

The relevant lines of code from the linux kernel's completion.h:

struct completion {
    unsigned int done;
    wait_queue_head_t wait;
};

#define COMPLETION_INITIALIZER_ONSTACK(work) \
    (*({ init_completion(&work); &work; }))

#define DECLARE_COMPLETION_ONSTACK(work) \
    struct completion work = COMPLETION_INITIALIZER_ONSTACK(work)

static inline void init_completion(struct completion *x)
{
    x->done = 0;
    init_waitqueue_head(&x->wait);
}

and in use:

int myFunc()
{
   DECLARE_COMPLETION_ON_STACK(comp);
   .
   .
   .
   wait_for_completion(&comp);
}

Specifically, I want to understand the code of COMPLETION_INITIALIZER_ON_STACK.

I believe the braced body of two statements { init_completion(&work); &work; } results in simply a value, &work (a NOP statement), which from what I know about braced blocks in C, yeilds the value of the last assignment, in this case, the address of a struct.

But it is the enclosing of all of that in *( ) that gets interesting (and where I am bewildered).

  1. What is that 'fetch' doing exactly?
  2. Is it resulting in the function init_completion() being invoked (probably)?
  3. And what is the result of a pointer to a struct as a fetched object?
  4. In what contexts can it be applied?

I'm not sure what is happening, how to conceive of it, and how it is it possible to assign that result to struct completion work as is done in in DECLARE_COMPLETION_ON_STACK.

Any education about this would be appreciated.

bolov
  • 72,283
  • 15
  • 145
  • 224
clearlight
  • 12,255
  • 11
  • 57
  • 75
  • 1
    https://stackoverflow.com/questions/6305396/whats-this-c-syntax-that-puts-a-brace-surrounded-block-where-an-expression-is – bolov Aug 14 '19 at 02:47
  • Does this answer your question? [What's this C++ syntax that puts a brace-surrounded block where an expression is expected?](https://stackoverflow.com/questions/6305396/whats-this-c-syntax-that-puts-a-brace-surrounded-block-where-an-expression-is) – cigien Dec 23 '21 at 04:46

2 Answers2

6

The syntax of statements within a ({ ... }) block is a statement expression which is a GCC extension. It allows you to run a series of statements where the last statement in the block is an expression which becomes the value of the full statement expression. So in this case the statement expression has the value &work.

Since the statement expression evaluates to &work, the * right before the statement expression gives you *&work, or equivalently work as the value of the macro COMPLETION_INITIALIZER_ONSTACK.

Now let's look at DECLARE_COMPLETION_ONSTACK. When it is used:

DECLARE_COMPLETION_ON_STACK(comp);

It expands to:

struct completion comp= COMPLETION_INITIALIZER_ONSTACK(comp);

Which further expands to:

struct completion comp = (*({ init_completion(&comp ); ∁ }))

Breaking this down, the variable comp is being initialized with a statement expression. The first statement in that expression is a call to the function init_completion which is passed the address of the new variable. This function sets the values of the variable which at this point hasn't actually been initialized yet. The next (and last) statement in the statement expression is &comp which is the value of the statement expression. This address is then dereferenced giving us comp which is then assigned to comp. So the variable is being validly initialized with itself!

Normally initializing a variable with itself would invoke undefined behavior because you would be trying to read an uninitialized variable, but not in this case because the variable's address is passed to a function which assigns values to its fields before it's initialized.

You might ask why COMPLETION_INITIALIZER_ONSTACK was not defined like this:

#define COMPLETION_INITIALIZER_ONSTACK(work) \
    ({ init_completion(&work); work; })

If done this way, a temporary variable is created on the stack. Using the addrress prevents this from happening. In fact the code originally did this but was changed to what you see in the following commit:

https://github.com/torvalds/linux/commit/ec81048cc340bb03334e6ca62661ecc0a684897a#diff-f4f6d7a50d07f6f07835787ec35565bb

dbush
  • 205,898
  • 23
  • 218
  • 273
  • 1
    I was working on an answer and I got hung up on explaining why the statement expression uses `*({...;&work;})` instead of simply `({...;work;})`. Why the need to take the address and then dereference the "return value"? – AShelly Aug 14 '19 at 02:52
  • 1
    @AShelly That I'm not so sure of. Taking the address of the variable prevents undefined behavior when reading an uninitialized variable, however the function call is doing that already. – dbush Aug 14 '19 at 02:56
  • Ahh, so the part I wasn't getting was that I can initialize a struct from another struct without using a pointer (also didn't know about statement expressions as a gcc extension, so thanks for that): Apparently this is legit, at least in gcc, because it compiled: `struct foo { int a, b }; struct foo zoo = { 1, 2 }; struct foo bar = zoo;` So that completes my understanding and accepting your answer now. – clearlight Aug 14 '19 at 03:06
  • Fantastic edit @dbush, that reference to the linux kernel source change is really helpful. Interesting to see how it evolved and why. Great explanation! – clearlight Aug 14 '19 at 03:14
4

The answer from dbush is excellent in showing what a statement expression is. I would like however to add what is achieved with this contrived way. The main purpose of the macro is to force the compiler to allocate the stack for the object. Without it, the optimizer could elide it.

I've created a simpler but equivalent code:

struct X
{
    int a;
    long long b;
};

void init_x(struct X*);
X make_x();

int test_classic()
{
    struct X x = make_x();

    return x.a; // we are returning a member of `x`
                // and still the optimizer will skip the creation of x on the stack
}

int test_on_stack()
{
    struct X x = (*({init_x(&x); &x;}));  

    return 24;  // even if x is unused after the initializer
                // the compiler is forced to allocate space for it on the stack
}

On the classic way of initializing a variable the compiler can and gcc indeed does eliminate the object from the stack (in this case because the result is already in eax after calling make_x):

test_classic():
        sub     rsp, 8
        call    make_x()
        add     rsp, 8
        ret

However with the linux DECLARE_COMPLETION_ONSTACK equivalent the compiler is forced to create the object on the stack as there is a call to a function that passes the address of the object, so the object creation cannot be elided:

test_on_stack():
        sub     rsp, 24
        mov     rdi, rsp
        call    init_x(X*)
        mov     eax, DWORD PTR [rsp]
        add     rsp, 24
        ret

I guess the same could still be achieved by calling init after the initialization:

struct X x;
init_x(&x);

Maybe someone more experienced could shed further light here.

bolov
  • 72,283
  • 15
  • 145
  • 224
  • It seems that your point is that a compiler cannot optimize out the statically allocated data structure, x in test_on_classic function, when it is used inside the statement expression, but the variable is not allocated on the stack when it is used in the usual form, right? Then what is the advantage of having a local variable in the stack that is not used anymore outside of the function by making use of the statement expression? – ruach Aug 20 '19 at 01:58
  • As far as I know, the statement expression is useful when the macro function is used with the argument that has a side effect, which means preventing multiple side effects could ensue during macro expansion. Although it seems that kernel code utilizes statement expression to shorten the code line by intermingling expression and initialization, which might not follow the original design purpose of the statement expression, I cannot find any code example that can make use of this stack-allocated properties. Is there any code example that rely on this feature in other ways? – ruach Aug 20 '19 at 01:58