; Tests that CoroSplit does not place a dynamically sized alloca (a VLA) on the
; coroutine frame when it does not actually live across a suspend point, even if
; its address escapes to a call.
;
; This is the glibc _nl_load_locale shape on the wasm32 coro arm: a VLA path
; buffer is filled and handed by pointer to a (non-capturing) syscall wrapper,
; all before the first suspend, and is never used afterwards. The generic
; "escaped => must live on frame" heuristic used to collect such an alloca onto
; the frame; FrameTypeBuilder::addFieldForAllocas then sorted the frame allocas
; by AllocaInst::getAllocationSize(), which is std::nullopt for a VLA, and (with
; assertions disabled, as in the shipped toolchain) dereferenced the empty
; optional. That made the sort comparator non-deterministic and corrupted the
; frame layout -> SIGSEGV while splitting the coroutine. With assertions it hit
; "Coroutines cannot handle non static allocas yet".
;
; A VLA whose uses do not cross a suspend does not belong on the frame at all --
; it stays a normal stack alloca in the ramp function -- so the coroutine now
; splits cleanly. reuse-storage selects OptimizeFrame, matching clang at -O2.
;
; RUN: opt < %s -passes='cgscc(coro-split<reuse-storage>)' -S | FileCheck %s

define ptr @f(i32 %n) presplitcoroutine {
entry:
  %id = call token @llvm.coro.id(i32 0, ptr null, ptr null, ptr null)
  %size = call i32 @llvm.coro.size.i32()
  %alloc = call ptr @malloc(i32 %size)
  %hdl = call ptr @llvm.coro.begin(token %id, ptr %alloc)
  ; A dynamically sized alloca whose address escapes (no nocapture) to a call,
  ; all before the suspend, and which is dead afterwards.
  %vla = alloca i8, i32 %n, align 16
  call void @use(ptr %vla)
  %sp = call i8 @llvm.coro.suspend(token none, i1 false)
  switch i8 %sp, label %suspend [i8 0, label %resume
                                 i8 1, label %cleanup]
resume:
  br label %cleanup

cleanup:
  %mem = call ptr @llvm.coro.free(token %id, ptr %hdl)
  call void @free(ptr %mem)
  br label %suspend

suspend:
  call void @llvm.coro.end(ptr %hdl, i1 0, token none)
  ret ptr %hdl
}

; The coroutine splits cleanly (no crash). The VLA stays a normal dynamic alloca
; in the ramp function rather than being materialized as a frame field.

; CHECK-LABEL: define ptr @f(
; CHECK:         %vla = alloca i8, i32 %n
; CHECK:         call void @use(ptr %vla)

; The resume clone exists (i.e. the coroutine was actually split).
; CHECK-LABEL: define internal fastcc void @f.resume(

declare token @llvm.coro.id(i32, ptr, ptr, ptr)
declare i32 @llvm.coro.size.i32()
declare ptr @llvm.coro.begin(token, ptr)
declare i8 @llvm.coro.suspend(token, i1)
declare ptr @llvm.coro.free(token, ptr)
declare void @llvm.coro.end(ptr, i1, token)
declare ptr @malloc(i32)
declare void @free(ptr)
declare void @use(ptr)
