; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s | FileCheck %s ; RUN: llc -mtriple=x86_64-unknown-windows-msvc -o - %s \ ; RUN: -x86-wineh-unwindv2-unwind-codes-threshold=8 | FileCheck %s \ ; RUN: -check-prefixes=ALLOWLESS,CHECK define dso_local void @no_epilog() local_unnamed_addr { entry: ret void } ; CHECK-LABEL: no_epilog: ; CHECK-NOT: .seh_ ; CHECK: retq define dso_local void @stack_alloc_no_pushes() local_unnamed_addr { entry: call void @a() ret void } ; CHECK-LABEL: stack_alloc_no_pushes: ; CHECK: .seh_unwindversion 2 ; CHECK-NOT: .seh_pushreg ; CHECK: .seh_stackalloc ; CHECK: .seh_endprologue ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: addq ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: retq define dso_local i32 @stack_alloc_and_pushes(i32 %x) local_unnamed_addr { entry: %call = tail call i32 @c(i32 %x) %call1 = tail call i32 @c(i32 %x) %add = add nsw i32 %call1, %call %call2 = tail call i32 @c(i32 %x) %call3 = tail call i32 @c(i32 %call2) %add4 = add nsw i32 %add, %call3 ret i32 %add4 } ; CHECK-LABEL: stack_alloc_and_pushes: ; CHECK: .seh_unwindversion 2 ; CHECK: .seh_pushreg %rsi ; CHECK: .seh_pushreg %rdi ; CHECK: .seh_pushreg %rbx ; CHECK: .seh_stackalloc ; CHECK: .seh_endprologue ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: addq ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: popq %rbx ; CHECK-NEXT: popq %rdi ; CHECK-NEXT: popq %rsi ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: retq define dso_local i32 @tail_call(i32 %x) local_unnamed_addr { entry: %call = tail call i32 @c(i32 %x) %call1 = tail call i32 @c(i32 %call) ret i32 %call1 } ; CHECK-LABEL: tail_call: ; CHECK: .seh_unwindversion 2 ; CHECK-NOT: .seh_pushreg ; CHECK: .seh_stackalloc ; CHECK: .seh_endprologue ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: addq ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: jmp define dso_local i32 @multiple_epilogs(i32 %x) local_unnamed_addr { entry: %call = tail call i32 @c(i32 noundef %x) %cmp = icmp sgt i32 %call, 0 br i1 %cmp, label %if.then, label %if.else if.then: %call1 = tail call i32 @c(i32 noundef %call) ret i32 %call1 if.else: %call2 = tail call i32 @b() ret i32 %call2 } ; CHECK-LABEL: multiple_epilogs: ; CHECK: .seh_unwindversion 2 ; CHECK-NOT: .seh_pushreg ; CHECK: .seh_stackalloc ; CHECK: .seh_endprologue ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: addq ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: jmp ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: addq ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: jmp define dso_local i32 @mismatched_terminators() local_unnamed_addr { entry: %call = tail call i32 @b() %cmp = icmp sgt i32 %call, 0 br i1 %cmp, label %if.then, label %if.else if.then: %call1 = tail call i32 @b() ret i32 %call1 if.else: ret i32 %call } ; CHECK-LABEL: mismatched_terminators: ; CHECK: .seh_unwindversion 2 ; CHECK-NOT: .seh_pushreg ; CHECK: .seh_stackalloc ; CHECK: .seh_endprologue ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: addq ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: jmp ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: addq ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: ret define dso_local void @dynamic_stack_alloc(i32 %x) local_unnamed_addr { entry: %y = alloca i32, i32 %x ret void } ; CHECK-LABEL: dynamic_stack_alloc: ; CHECK: .seh_unwindversion 2 ; CHECK: .seh_pushreg %rbp ; CHECK: .seh_setframe %rbp, 0 ; CHECK: .seh_endprologue ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: movq %rbp, %rsp ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: popq %rbp ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: retq ; CHECK-NEXT: .seh_endproc define dso_local void @large_aligned_alloc() align 16 { %1 = alloca [128 x i8], align 64 ret void } ; CHECK-LABEL: large_aligned_alloc: ; CHECK: .seh_unwindversion 2 ; CHECK: .seh_pushreg %rbp ; CHECK: .seh_stackalloc 176 ; CHECK: .seh_setframe %rbp, 128 ; CHECK: .seh_endprologue ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: leaq 48(%rbp), %rsp ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: popq %rbp ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: retq ; CHECK-NEXT: .seh_endproc define dso_local void @set_frame_only() local_unnamed_addr { tail call i64 @llvm.x86.flags.read.u64() ret void } ; CHECK-LABEL: set_frame_only: ; CHECK: .seh_unwindversion 2 ; CHECK: .seh_pushreg %rbp ; CHECK: .seh_setframe %rbp, 0 ; CHECK: .seh_endprologue ; CHECK-NOT: .seh_endproc ; CHECK: .seh_startepilogue ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: popq %rbp ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: retq ; CHECK-NEXT: .seh_endproc attributes #1 = { noreturn } define dso_local void @no_return_func() local_unnamed_addr #1 { entry: call void @d() unreachable } ; CHECK-LABEL: no_return_func: ; CHECK-NOT: .seh_unwindversion 2 ; CHECK: .seh_stackalloc ; CHECK-NEXT: .seh_endprologue ; CHECK-NOT: .seh_startepilogue ; CHECK-NOT: .seh_unwindv2start ; CHECK: int3 ; CHECK-NEXT: .seh_endproc define dso_local i32 @has_funclet(i32 %x) local_unnamed_addr personality ptr @__C_specific_handler { entry: %call = invoke i32 @c(i32 %x) to label %call.block.1 unwind label %cleanup call.block.1: %call1 = invoke i32 @c(i32 %x) to label %call.block.2 unwind label %cleanup call.block.2: %add = add nsw i32 %call1, %call %call2 = invoke i32 @c(i32 %x) to label %call.block.3 unwind label %cleanup call.block.3: %call3 = invoke i32 @c(i32 %call2) to label %call.block.4 unwind label %cleanup call.block.4: %add4 = add nsw i32 %add, %call3 ret i32 %add4 cleanup: %cleanup_token = cleanuppad within none [] call fastcc void @is_funclet(i32 %x) #18 [ "funclet"(token %cleanup_token) ] cleanupret from %cleanup_token unwind to caller } define internal fastcc void @is_funclet(i32 %x) local_unnamed_addr { entry: %y = alloca i32, i32 %x ret void } ; CHECK-LABEL: has_funclet: ; CHECK: .seh_pushreg %rbp ; CHECK: .seh_pushreg %rsi ; CHECK: .seh_pushreg %rdi ; CHECK: .seh_stackalloc 48 ; CHECK: .seh_setframe %rbp, 48 ; CHECK: .seh_endprologue ; CHECK: .seh_startepilogue ; CHECK: .seh_unwindv2start ; CHECK-NEXT: popq %rdi ; CHECK-NEXT: popq %rsi ; CHECK-NEXT: popq %rbp ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: retq ; ALLOWLESS-NEXT: .seh_splitchained ; ALLOWLESS-NEXT: .seh_endprologue ; CHECK-NEXT: .seh_handlerdata ; CHECK: .text ; CHECK-NEXT: .seh_endproc ; CHECK-LABEL: "?dtor$5@?0?has_funclet@4HA": ; CHECK: .seh_pushreg %rbp ; CHECK: .seh_pushreg %rsi ; CHECK: .seh_pushreg %rdi ; CHECK: .seh_stackalloc 32 ; CHECK: .seh_endprologue ; CHECK: .seh_startepilogue ; CHECK: .seh_unwindv2start ; CHECK-NEXT: popq %rdi ; CHECK-NEXT: popq %rsi ; CHECK-NEXT: popq %rbp ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: retq ; CHECK: .seh_handlerdata ; CHECK-NEXT: .text ; CHECK-NEXT: .seh_endproc ; CHECK-LABEL: is_funclet: ; CHECK: .seh_unwindversion 2 ; CHECK: .seh_pushreg %rbp ; CHECK: .seh_setframe %rbp, 0 ; CHECK: .seh_endprologue ; CHECK: .seh_startepilogue ; CHECK: .seh_unwindv2start ; CHECK: .seh_endepilogue ; CHECK-NEXT: retq ; CHECK-NEXT: .seh_endproc define dso_local void @set_frame_and_alloc(i32 %x) local_unnamed_addr { %y = alloca i32, i32 %x %z = alloca ptr, align 8 ret void } ; CHECK-LABEL: set_frame_and_alloc: ; CHECK: .seh_unwindversion 2 ; CHECK: .seh_pushreg %rbp ; CHECK: .seh_stackalloc 16 ; CHECK: .seh_setframe %rbp, 16 ; CHECK: .seh_endprologue ; CHECK: .seh_startepilogue ; CHECK-NEXT: movq %rbp, %rsp ; CHECK-NEXT: .seh_unwindv2start ; CHECK-NEXT: popq %rbp ; CHECK-NEXT: .seh_endepilogue ; CHECK-NEXT: retq ; CHECK-NEXT: .seh_endproc declare i64 @llvm.x86.flags.read.u64() declare void @a() local_unnamed_addr declare i32 @b() local_unnamed_addr declare i32 @c(i32) local_unnamed_addr declare void @d() local_unnamed_addr #1 declare dso_local i32 @__C_specific_handler(...) !llvm.module.flags = !{!0} !0 = !{i32 1, !"winx64-eh-unwindv2", i32 2}