From: ThatWouldBeTelling@thevillage.com   
      
   Anton Ertl wrote:   
   > EricP writes:   
   >> Test1 allocates a dynamic sized buffer and has a static goto Loop   
   >> for which GCC generates a jne .L6 to a mov rsp, rbx that recovers   
   >> the stack allocation inside the {} block.   
   >>   
   >> Test2 is the same but does a goto *dest and GCC does not generate   
   >> code to recover the inner {} block allocation. It just loops over   
   >> the sub rsp, rbx so the stack space just grows.   
   >   
   > Interestingly, gcc optimizes the indirect branch with a constant   
   > target into a direct branch, but then does not continue with the same   
   > code as you get with a plain goto.   
   >   
   >> void Test2 (long len)   
   >> {   
   >> long ok;   
   >> void *dest;   
   >>   
   >> dest = &&Loop;   
   >> Loop:   
   >> {   
   >> char buf[len];   
   >>   
   >> ok = Sub (len, buf);   
   >> if (ok)   
   >> goto *dest;   
   >> }   
   >> }   
   >>   
   >> Test2(long):   
   >> push rbp   
   >> mov rbp, rsp   
   >> push r12   
   >> mov r12, rdi   
   >> push rbx   
   >> lea rbx, [rdi+15]   
   >> shr rbx, 4   
   >> sal rbx, 4   
   >> .L8:   
   >> sub rsp, rbx   
   >> mov rdi, r12   
   >> mov rsi, rsp   
   >> call Sub(long, char*)   
   >> test rax, rax   
   >> jne .L8   
   >> lea rsp, [rbp-16]   
   >> pop rbx   
   >> pop r12   
   >> pop rbp   
   >> ret   
   >   
   > Interesting that this bug has not been fixed in the >33 years that   
   > labels-as-values have been in gcc; I don't know how long these   
   > dynamically sized arrays have been in gcc, but IIRC alloca(), a   
   > similar feature, has been available at least as long as   
   > labels-as-values. The bug has apparently been avoided or worked   
   > around by the users of labels-as-values (e.g., Gforth does not use   
   > alloca or dynamically-sized arrays in the function that contains all   
   > the taken labels and all the "goto *"s.   
      
   alloca is not required to recover storage at the {} block level.   
   MS C does not recover alloca space until the subroutine returns.   
      
   But when they added dynamic allocation to C as a first class feature   
   I figured it should recover storage at the end of a {} block,   
   and I wondered it the superficially non-deterministic nature of   
   goto variable would be a problem.   
      
   > As long as all taken labels have the same stack depth, the bugfix does   
   > not look particularly hard: just put code before each goto * that   
   > adjusts the stack depth to the depth of these labels.   
   >   
   > Things become more interesting if there are labels with different   
   > stack depths, because labels are stored in "void *" variables, and   
   > there is not enough room for a target and a stack depth. One can ue   
   > the same approach as is used in Test1, however: have the stack depth   
   > for a specific target in some location, and have a copy from that   
   > location to the stack pointer right behind the label.   
   >   
   > ....   
   >> jmp .L2   
   >> .L6:   
   >> mov rsp, rbx   
   >> .L2:   
   > ....   
   >> jne .L6   
   >   
   > All the code that works now would not need these extra copy   
   > intructions, so the bugfix should special-case the case where all the   
   > targets have the same depth.   
   >   
   > - anton   
      
   Below in Test3 I replace the goto variable with a switch statement   
   arranged to be nondeterministic, and it does get it right.   
   I suggest GCC forgot to treat the goto variable as equivalent to a switch   
   statement and threw up its hands and treated the buffer as an alloca.   
      
   This all relates to Niklas's comments as to why the label variables must   
   all be within the current context, so it knows when to recover storage.   
   If the language had destructors the goto variable could have to call them   
   which alloca also does not deal with.   
      
   long Sub (long len, char buf[]);   
      
   void Test3 (long len)   
   {   
    long ok, dest;   
      
    dest = 0;   
    Loop:   
    {   
    char buf[len];   
      
    ok = Sub (len, buf);   
    if (ok)   
    dest = 1;   
      
    switch (dest)   
    {   
    case 0:   
    goto Loop;   
    case 1:   
    goto Out;   
    }   
    Out:   
    ;   
    }   
   }   
      
   # Compilation provided by Compiler Explorer at https://godbolt.org/   
   Test3(long):   
    push rbp   
    mov rbp, rsp   
    push r13   
    mov r13, rdi   
    push r12   
    lea r12, [rdi+15]   
    push rbx   
    shr r12, 4   
    sal r12, 4   
    sub rsp, 8   
    jmp .L2   
   .L6:   
    mov rsp, rbx   
   .L2:   
    mov rbx, rsp   
    sub rsp, r12   
    mov rdi, r13   
    mov rsi, rsp   
    call Sub(long, char*)   
    test rax, rax   
    je .L6   
    lea rsp, [rbp-24]   
    pop rbx   
    pop r12   
    pop r13   
    pop rbp   
    ret   
      
   --- SoupGate-Win32 v1.05   
    * Origin: you cannot sedate... all the things you hate (1:229/2)   
|