ccompiler-optimizationcontext-switching

Why is this code acting different with a single printf? ucontext.h



When I compile my code below it prints

I am running :)

forever(Until I send KeyboardInterrupt signal to the program),
but when I uncomment // printf("done:%d\n", done);, recompile and run it, it will print only two times, prints done: 1 and then returns.
I'm new to ucontext.h and I'm very confused about how this code is working and why a single printf is changing whole behavior of the code, if you replace printf with done++; it would do the same but if you replace it with done = 2; it does not affect anything and works as we had the printf commented at first place.
Can anyone explain:
Why is this code acting like this and what's the logic behind it?
Sorry for my bad English,
Thanks a lot.

#include <ucontext.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>


int main()
{
    register int done = 0;
    ucontext_t one;
    ucontext_t two;
    getcontext(&one);
    printf("I am running :)\n");
    sleep(1);
    if (!done)
    {
        done = 1;  
        swapcontext(&two, &one);
    }
    // printf("done:%d\n", done);
    return 0;
}

Solution

  • This is a compiler optimization "problem". When the "printf()" is commented, the compiler deduces that "done" will not be used after the "if (!done)", so it does not set it to 1 as it is not worth. But when the "printf()" is present, "done" is used after "if (!done)", so the compiler sets it.

    Assembly code with the "printf()":

    $ gcc ctx.c -o ctx -g
    $ objdump -S ctx
    [...]
    int main(void)
    {
        11e9:   f3 0f 1e fa             endbr64 
        11ed:   55                      push   %rbp
        11ee:   48 89 e5                mov    %rsp,%rbp
        11f1:   48 81 ec b0 07 00 00    sub    $0x7b0,%rsp
        11f8:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
        11ff:   00 00 
        1201:   48 89 45 f8             mov    %rax,-0x8(%rbp)
        1205:   31 c0                   xor    %eax,%eax
        register int done = 0;
        1207:   c7 85 5c f8 ff ff 00    movl   $0x0,-0x7a4(%rbp) <------- done set to 0
        120e:   00 00 00 
        ucontext_t one;
        ucontext_t two;
        getcontext(&one);
        1211:   48 8d 85 60 f8 ff ff    lea    -0x7a0(%rbp),%rax
        1218:   48 89 c7                mov    %rax,%rdi
        121b:   e8 c0 fe ff ff          callq  10e0 <getcontext@plt>
        1220:   f3 0f 1e fa             endbr64 
        printf("I am running :)\n");
        1224:   48 8d 3d d9 0d 00 00    lea    0xdd9(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
        122b:   e8 70 fe ff ff          callq  10a0 <puts@plt>
        sleep(1);
        1230:   bf 01 00 00 00          mov    $0x1,%edi
        1235:   e8 b6 fe ff ff          callq  10f0 <sleep@plt>
        if (!done)
        123a:   83 bd 5c f8 ff ff 00    cmpl   $0x0,-0x7a4(%rbp)
        1241:   75 27                   jne    126a <main+0x81>
        {
            done = 1;  
        1243:   c7 85 5c f8 ff ff 01    movl   $0x1,-0x7a4(%rbp) <----- done set to 1
        124a:   00 00 00 
            swapcontext(&two, &one);
        124d:   48 8d 95 60 f8 ff ff    lea    -0x7a0(%rbp),%rdx
        1254:   48 8d 85 30 fc ff ff    lea    -0x3d0(%rbp),%rax
        125b:   48 89 d6                mov    %rdx,%rsi
        125e:   48 89 c7                mov    %rax,%rdi
        1261:   e8 6a fe ff ff          callq  10d0 <swapcontext@plt>
        1266:   f3 0f 1e fa             endbr64 
        }
        printf("done:%d\n", done);
        126a:   8b b5 5c f8 ff ff       mov    -0x7a4(%rbp),%esi
        1270:   48 8d 3d 9d 0d 00 00    lea    0xd9d(%rip),%rdi        # 2014 <_IO_stdin_used+0x14>
        1277:   b8 00 00 00 00          mov    $0x0,%eax
        127c:   e8 3f fe ff ff          callq  10c0 <printf@plt>
        return 0;
    

    Assembly code without the "printf()":

    $ gcc ctx.c -o ctx -g
    $ objdump -S ctx
    [...]
    int main(void)
    {
        11c9:   f3 0f 1e fa             endbr64 
        11cd:   55                      push   %rbp
        11ce:   48 89 e5                mov    %rsp,%rbp
        11d1:   48 81 ec b0 07 00 00    sub    $0x7b0,%rsp
        11d8:   64 48 8b 04 25 28 00    mov    %fs:0x28,%rax
        11df:   00 00 
        11e1:   48 89 45 f8             mov    %rax,-0x8(%rbp)
        11e5:   31 c0                   xor    %eax,%eax
        register int done = 0;
        11e7:   c7 85 5c f8 ff ff 00    movl   $0x0,-0x7a4(%rbp) <------ done set to 0
        11ee:   00 00 00 
        ucontext_t one;
        ucontext_t two;
        getcontext(&one);
        11f1:   48 8d 85 60 f8 ff ff    lea    -0x7a0(%rbp),%rax
        11f8:   48 89 c7                mov    %rax,%rdi
        11fb:   e8 c0 fe ff ff          callq  10c0 <getcontext@plt>
        1200:   f3 0f 1e fa             endbr64 
        printf("I am running :)\n");
        1204:   48 8d 3d f9 0d 00 00    lea    0xdf9(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
        120b:   e8 80 fe ff ff          callq  1090 <puts@plt>
        sleep(1);
        1210:   bf 01 00 00 00          mov    $0x1,%edi
        1215:   e8 b6 fe ff ff          callq  10d0 <sleep@plt>
        if (!done)
        121a:   83 bd 5c f8 ff ff 00    cmpl   $0x0,-0x7a4(%rbp)
        1221:   75 1d                   jne    1240 <main+0x77>
        {
            done = 1;                             <------------- done is no set here (it is optimized by the compiler)
            swapcontext(&two, &one);
        1223:   48 8d 95 60 f8 ff ff    lea    -0x7a0(%rbp),%rdx
        122a:   48 8d 85 30 fc ff ff    lea    -0x3d0(%rbp),%rax
        1231:   48 89 d6                mov    %rdx,%rsi
        1234:   48 89 c7                mov    %rax,%rdi
        1237:   e8 74 fe ff ff          callq  10b0 <swapcontext@plt>
        123c:   f3 0f 1e fa             endbr64 
        }
        //printf("done:%d\n", done);
        return 0;
        1240:   b8 00 00 00 00          mov    $0x0,%eax
    }
        1245:   48 8b 4d f8             mov    -0x8(%rbp),%rcx
        1249:   64 48 33 0c 25 28 00    xor    %fs:0x28,%rcx
        1250:   00 00 
        1252:   74 05                   je     1259 <main+0x90>
        1254:   e8 47 fe ff ff          callq  10a0 <__stack_chk_fail@plt>
        1259:   c9                      leaveq 
        125a:   c3                      retq   
        125b:   0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
    

    To disable the optimization on "done", add the "volatile" keyword in its definition:

    volatile register int done = 0;
    

    This makes the program work in both cases.