llvm-project/llvm/test/MC/AsmParser/seh-directive-errors.s
Daniel Paoliello 72c3ed6745
[win][x64] Unwind v2 3/n: Add support for emitting unwind v2 information (equivalent to MSVC /d2epilogunwind) (#129142)
Adds support for emitting Windows x64 Unwind V2 information, includes
support `/d2epilogunwind` in clang-cl.

Unwind v2 adds information about the epilogs in functions such that the
unwinder can unwind even in the middle of an epilog, without having to
disassembly the function to see what has or has not been cleaned up.

Unwind v2 requires that all epilogs are in "canonical" form:
* If there was a stack allocation (fixed or dynamic) in the prolog, then
the first instruction in the epilog must be a stack deallocation.
* Next, for each `PUSH` in the prolog there must be a corresponding
`POP` instruction in exact reverse order.
* Finally, the epilog must end with the terminator.

This change adds a pass to validate epilogs in modules that have Unwind
v2 enabled and, if they pass, emits new pseudo instructions to MC that
1) note that the function is using unwind v2 and 2) mark the start of
the epilog (this is either the first `POP` if there is one, otherwise
the terminator instruction). If a function does not meet these
requirements, it is downgraded to Unwind v1 (i.e., these new pseudo
instructions are not emitted).

Note that the unwind v2 table only marks the size of the epilog in the
"header" unwind code, but it's possible for epilogs to use different
terminator instructions thus they are not all the same size. As a work
around for this, MC will assume that all terminator instructions are
1-byte long - this still works correctly with the Windows unwinder as it
is only using the size to do a range check to see if a thread is in an
epilog or not, and since the instruction pointer will never be in the
middle of an instruction and the terminator is always at the end of an
epilog the range check will function correctly. This does mean, however,
that the "at end" optimization (where an epilog unwind code can be
elided if the last epilog is at the end of the function) can only be
used if the terminator is 1-byte long.

One other complication with the implementation is that the unwind table
for a function is emitted during streaming, however we can't calculate
the distance between an epilog and the end of the function at that time
as layout hasn't been completed yet (thus some instructions may be
relaxed). To work around this, epilog unwind codes are emitted via a
fixup. This also means that we can't pre-emptively downgrade a function
to Unwind v1 if one of these offsets is too large, so instead we raise
an error (but I've passed through the location information, so the user
will know which of their functions is problematic).
2025-05-09 10:42:10 -07:00

162 lines
4.7 KiB
ArmAsm

# RUN: not llvm-mc -triple x86_64-windows-msvc %s -filetype=obj -o /dev/null 2>&1 | FileCheck %s --implicit-check-not=error:
# RUN: not llvm-mc -triple x86_64-windows-msvc %s -o /dev/null 2>&1 | FileCheck %s --implicit-check-not=error:
.text
# CHECK: error: .seh_ directive must appear within an active frame
.seh_handlerdata
.seh_pushreg %rsi
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
.seh_stackalloc 32
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
.seh_startepilogue
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
.seh_endepilogue
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
.seh_unwindv2start
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
.def f;
.scl 2;
.type 32;
.endef
.globl f # -- Begin function f
.p2align 4, 0x90
f: # @f
.seh_proc f
pushq %rsi
.seh_pushreg %rsi
pushq %rdi
.seh_pushreg %rdi
pushq %rbx
.seh_pushreg %rbx
subq $32, %rsp
.seh_stackalloc 0
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: stack allocation size must be non-zero
.seh_stackalloc 7
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: stack allocation size is not a multiple of 8
.seh_stackalloc 32
.seh_startepilogue
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: starting epilogue (.seh_startepilogue) before prologue has ended (.seh_endprologue) in f
.seh_endprologue
nop
.seh_endepilogue
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Stray .seh_endepilogue in f
.seh_startepilogue
addq $32, %rsp
popq %rbx
popq %rdi
popq %rsi
.seh_endepilogue
retq
.seh_handlerdata
.text
.seh_endproc
.seh_pushreg %rsi
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: .seh_ directive must appear within an active frame
g:
.seh_proc g
pushq %rbp
.seh_pushreg %rbx
pushq %rsi
.seh_pushreg %rsi
.seh_endprologue
.seh_setframe 3 255
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: you must specify a stack pointer offset
.seh_setframe 3, 255
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: offset is not a multiple of 16
.seh_setframe 3, 256
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: frame offset must be less than or equal to 240
.seh_setframe 3, 128
.seh_setframe 3, 128
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: frame register and offset can be set at most once
nop
.seh_startepilogue
popq %rsi
popq %rbp
.seh_endepilogue
retq
.seh_endproc
.globl h # -- Begin function h
.p2align 4, 0x90
h: # @h
.seh_proc h
# %bb.0: # %entry
subq $72, %rsp
.seh_stackalloc 72
movaps %xmm7, 48(%rsp) # 16-byte Spill
.seh_savexmm 7 44
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: you must specify an offset on the stack
.seh_savexmm %xmm7, 44
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: offset is not a multiple of 16
.seh_savexmm %xmm7, 48
movaps %xmm6, 32(%rsp) # 16-byte Spill
.seh_savexmm %xmm6, 32
.seh_endprologue
movapd %xmm0, %xmm6
callq getdbl
movapd %xmm0, %xmm7
addsd %xmm6, %xmm7
callq getdbl
addsd %xmm7, %xmm0
.seh_startepilogue
movaps 32(%rsp), %xmm6 # 16-byte Reload
movaps 48(%rsp), %xmm7 # 16-byte Reload
addq $72, %rsp
.seh_endepilogue
retq
.seh_handlerdata
.text
.seh_endproc
# -- End function
.globl i
.def i; .scl 2; .type 32; .endef
.p2align 4, 0x90
i:
.seh_proc i
pushq %rbp
.seh_pushreg 32
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: incorrect register number for use with this directive
pushq %rbx
.seh_pushreg %xmm0
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: register is not supported for use with this directive
leaq 16(%rsp), %rbp
.seh_setframe %xmm0, 16
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: register is not supported for use with this directive
.seh_endprologue
ret
.seh_endproc
j:
.seh_proc j
.seh_unwindversion 1
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Unsupported version specified in .seh_unwindversion in j
.seh_unwindversion 2
.seh_unwindversion 2
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Duplicate .seh_unwindversion in j
.seh_endprologue
.seh_unwindv2start
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Stray .seh_unwindv2start in j
.seh_startepilogue
.seh_endepilogue
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Missing .seh_unwindv2start in j
ret
.seh_startepilogue
.seh_unwindv2start
.seh_unwindv2start
# CHECK: :[[@LINE-1]]:{{[0-9]+}}: error: Duplicate .seh_unwindv2start in j
.seh_endepilogue
ret
.seh_endproc