Issue 202846
Summary clang -O3 codegen mishandles a stack-top/guard pattern using __builtin_alloca(0) plus a later dynamic __builtin_alloca(bytes)
Labels
Assignees
Reporter IshwaraK
    
Look at the below reproducer.

```
#include <stddef.h>

extern size_t guard_size(void);
extern int    reuse_guard(void *old_top);
extern void install_guard(void *guard_start, size_t bytes, int reuse);
extern void use_live(void *live);

#define STACK_ALLOC(bytes) __builtin_alloca(bytes)

__attribute__((noinline))
int repro_tiny_no_touch(int seed)
{
  char live[48];
  size_t bytes = guard_size();
  void *old_top = STACK_ALLOC(0);
  int reuse;

  live[0] = (char)seed;
  live[47] = (char)(seed + 1);

  reuse = reuse_guard(old_top);
  if (!reuse)
  {
    void *tmp = STACK_ALLOC(bytes);
    if (tmp)
      old_top = (void *)((char *)old_top - bytes);
  }

  install_guard(old_top, bytes, reuse);
 use_live(live);

  return live[0] + live[47] + reuse;
}

__attribute__((noinline))
int repro_tiny_with_touch(int seed)
{
  char live[48];
  size_t bytes = guard_size();
  void *old_top = STACK_ALLOC(0);
  int reuse;

  live[0] = (char)seed;
  live[47] = (char)(seed + 1);

  reuse = reuse_guard(old_top);
  if (!reuse)
  {
 void *tmp = STACK_ALLOC(bytes);
    if (tmp)
    {
      *(volatile char *)tmp = 0;
      old_top = (void *)((char *)old_top - bytes);
    }
 }

  install_guard(old_top, bytes, reuse);
  use_live(live);

  return live[0] + live[47] + reuse;
}
```

There are 2 experiments, 'with_touch' and 'no_touch' functions.

1. no_touch experiment: this case is useful but less decisive. Pairwise IR shows InstCombinePass removes the dynamic alloca. 
   Before InstCombine, %1 = alloca i8, i64 %call exists.  After InstCombine, it is gone and only 
   %0 = alloca [0 x  i8] remains. This may be debated because the allocated pointer is not  touched.

2. with_touch experiment: this removes that ambiguity. Pairwise IR shows InstCombine keeps the dynamic alloca because of the volatile 
    store: %2 = alloca i8, i64 %call  and store volatile i8 0, ptr %2 remain after InstCombine. 
    The first  concrete bad frame layout appears during Prologue/Epilogue Insertion & Frame Finalization: before the pass, 
    frame objects are symbolic (fi#0, fi#1, fi#2) ; after the pass, fi#0 is [SP-40] and fi#1 is [SP-88] , 
 producing old_top = rbp - 32 and live = rbp - 80..rbp - 33.

The no_touch shows a related optimization concern, but with_touch demonstrates a stronger codegen bug: 
real dynamic stack movement exists, yet the captured old stack top is lowered to a fixed frame address that lets the guard overwrite a still-live local.

Required behavior:

  The compiler must keep the guard range inside the dynamically allocated stack area, not inside any still-live fixed-frame local.
  For with_touch, the required lowering is:

  1. Place live[48] in the fixed frame.
  2. Capture old_top so that it represents the real stack top below fixed locals.
  3. Perform dynamic stack allocation:
 rsp = rsp - align(bytes)
  4. Compute guard_start from the safe old_top / dynamic allocation boundary:
       guard_start = old_top - bytes
  5. Call:
       install_guard(guard_start, bytes, reuse)
  6. Ensure:
 [guard_start, guard_start + bytes) does not overlap live[48]

  For the current bad -O3 output:

  live[48]    = [rbp - 80, rbp - 32)
  old_top =  rbp - 32
  guard_start =  rbp - 32 - bytes

  For bytes = 48, that overlaps live[48]. Required behavior is that old_top must be below live[48], for example:

  live[48]    = [rbp - 80, rbp - 32)
  old_top     <= rbp - 80
  guard_start = old_top - bytes


_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs

Reply via email to