| // REQUIRES: x86-registered-target |
| |
| // RUN: %clang_cl -c --target=x86_64-windows-msvc /EHa -O2 /GS- \ |
| // RUN: -Xclang=-import-call-optimization \ |
| // RUN: /clang:-S /clang:-o- -- %s 2>&1 \ |
| // RUN: | FileCheck %s |
| |
| #ifdef __clang__ |
| #define NO_TAIL __attribute((disable_tail_calls)) |
| #else |
| #define NO_TAIL |
| #endif |
| |
| void might_throw(); |
| void other_func(int x); |
| |
| void does_not_throw() noexcept(true); |
| |
| extern "C" void __declspec(dllimport) some_dll_import(); |
| |
| class HasDtor { |
| int x; |
| char foo[40]; |
| |
| public: |
| explicit HasDtor(int x); |
| ~HasDtor(); |
| }; |
| |
| class BadError { |
| public: |
| int errorCode; |
| }; |
| |
| void normal_has_regions() { |
| // CHECK-LABEL: .def "?normal_has_regions@@YAXXZ" |
| // CHECK: .seh_endprologue |
| |
| // <-- state -1 (none) |
| { |
| HasDtor hd{42}; |
| |
| // <-- state goes from -1 to 0 |
| // because state changes, we expect the HasDtor::HasDtor() call to have a NOP |
| // CHECK: call "??0HasDtor@@QEAA@H@Z" |
| // CHECK-NEXT: nop |
| |
| might_throw(); |
| // CHECK: call "?might_throw@@YAXXZ" |
| // CHECK-NEXT: nop |
| |
| // <-- state goes from 0 to -1 because we're about to call HasDtor::~HasDtor() |
| // CHECK: call "??1HasDtor@@QEAA@XZ" |
| // <-- state -1 |
| } |
| |
| // <-- state -1 |
| other_func(10); |
| // CHECK: call "?other_func@@YAXH@Z" |
| // CHECK-NEXT: nop |
| // CHECK: .seh_startepilogue |
| |
| // <-- state -1 |
| } |
| |
| // This tests a tail call to a destructor. |
| void case_dtor_arg_empty_body(HasDtor x) |
| { |
| // CHECK-LABEL: .def "?case_dtor_arg_empty_body@@YAXVHasDtor@@@Z" |
| // CHECK: jmp "??1HasDtor@@QEAA@XZ" |
| } |
| |
| int case_dtor_arg_empty_with_ret(HasDtor x) |
| { |
| // CHECK-LABEL: .def "?case_dtor_arg_empty_with_ret@@YAHVHasDtor@@@Z" |
| // CHECK: .seh_endprologue |
| |
| // CHECK: call "??1HasDtor@@QEAA@XZ" |
| // CHECK-NOT: nop |
| |
| // The call to HasDtor::~HasDtor() should NOT have a NOP because the |
| // following "mov eax, 100" instruction is in the same EH state. |
| |
| return 100; |
| |
| // CHECK: mov eax, 100 |
| // CHECK: .seh_startepilogue |
| // CHECK: .seh_endepilogue |
| // CHECK: .seh_endproc |
| } |
| |
| int case_noexcept_dtor(HasDtor x) noexcept(true) |
| { |
| // CHECK: .def "?case_noexcept_dtor@@YAHVHasDtor@@@Z" |
| // CHECK: call "??1HasDtor@@QEAA@XZ" |
| // CHECK-NEXT: mov eax, 100 |
| // CHECK: .seh_startepilogue |
| return 100; |
| } |
| |
| void case_except_simple_call() NO_TAIL |
| { |
| does_not_throw(); |
| } |
| // CHECK-LABEL: .def "?case_except_simple_call@@YAXXZ" |
| // CHECK: .seh_endprologue |
| // CHECK-NEXT: call "?does_not_throw@@YAXXZ" |
| // CHECK-NEXT: nop |
| // CHECK-NEXT: .seh_startepilogue |
| // CHECK: .seh_endproc |
| |
| void case_noexcept_simple_call() noexcept(true) NO_TAIL |
| { |
| does_not_throw(); |
| } |
| // CHECK-LABEL: .def "?case_noexcept_simple_call@@YAXXZ" |
| // CHECK: .seh_endprologue |
| // CHECK-NEXT: call "?does_not_throw@@YAXXZ" |
| // CHECK-NEXT: nop |
| // CHECK-NEXT: .seh_startepilogue |
| // CHECK: .seh_endepilogue |
| // CHECK-NEXT: ret |
| // CHECK-NEXT: .seh_endproc |
| |
| // This tests that the destructor is called right before SEH_BeginEpilogue, |
| // but in a function that has a return value. Loading the return value |
| // counts as a real instruction, so there is no need for a NOP after the |
| // dtor call. |
| int case_dtor_arg_calls_no_throw(HasDtor x) |
| { |
| does_not_throw(); // no NOP expected |
| return 100; |
| } |
| // CHECK-LABEL: .def "?case_dtor_arg_calls_no_throw@@YAHVHasDtor@@@Z" |
| // CHECK: .seh_endprologue |
| // CHECK: "?does_not_throw@@YAXXZ" |
| // CHECK-NEXT: nop |
| // CHECK: "??1HasDtor@@QEAA@XZ" |
| // CHECK-NEXT: mov eax, 100 |
| // CHECK: .seh_startepilogue |
| // CHECK: .seh_endproc |
| |
| // Check the behavior of CALLs that are at the end of MBBs. If a CALL is within |
| // a non-null EH state (state -1) and is at the end of an MBB, then we expect |
| // to find an EH_LABEL after the CALL. This causes us to insert a NOP, which |
| // is the desired result. |
| void case_dtor_runs_after_join(int x) { |
| // CHECK-LABEL: .def "?case_dtor_runs_after_join@@YAXH@Z" |
| // CHECK: .seh_endprologue |
| |
| // <-- EH state -1 |
| |
| // ctor call does not need a NOP, because it has real instructions after it |
| HasDtor hd{42}; |
| // CHECK: call "??0HasDtor@@QEAA@H@Z" |
| // CHECK-NEXT: nop |
| // CHECK: test |
| |
| // <-- EH state transition from -1 0 |
| if (x) { |
| might_throw(); // <-- NOP expected (at end of BB w/ EH_LABEL) |
| // CHECK: call "?might_throw@@YAXXZ" |
| // CHECK-NEXT: nop |
| } else { |
| other_func(10); // <-- NOP expected (at end of BB w/ EH_LABEL) |
| // CHECK: call "?other_func@@YAXH@Z" |
| // CHECK-NEXT: nop |
| } |
| does_not_throw(); |
| // <-- EH state transition 0 to -1 |
| // ~HasDtor() runs |
| |
| // CHECK: .seh_endproc |
| |
| // CHECK: "$ip2state$?case_dtor_runs_after_join@@YAXH@Z": |
| // CHECK-NEXT: .long [[func_begin:.Lfunc_begin([0-9]+)@IMGREL]] |
| // CHECK-NEXT: .long -1 |
| // CHECK-NEXT: .long [[tmp1:.Ltmp([0-9]+)]]@IMGREL |
| // CHECK-NEXT: .long 0 |
| // CHECK-NEXT: .long [[tmp2:.Ltmp([0-9]+)]]@IMGREL |
| // CHECK-NEXT: .long -1 |
| } |
| |
| |
| // Check the behavior of NOP padding around tail calls. |
| // We do not expect to insert NOPs around tail calls. |
| // However, the first call (to other_func()) does get a NOP |
| // because it comes before .seh_startepilogue. |
| void case_tail_call_no_eh(bool b) { |
| // tail call; no NOP padding after JMP |
| if (b) { |
| does_not_throw(); |
| // <-- no NOP here |
| return; |
| } |
| |
| other_func(20); |
| // <-- NOP does get inserted here |
| } |
| // CHECK-LABEL: .def "?case_tail_call_no_eh@@YAX_N@Z" |
| // CHECK: test |
| // CHECK-NEXT: je .LBB |
| // CHECK: jmp "?does_not_throw@@YAXXZ" |
| // CHECK-SAME: TAILCALL |
| // CHECK-NEXT: .LBB |
| // CHECK-NEXT: mov ecx, 20 |
| // CHECK-NEXT: jmp "?other_func@@YAXH@Z" |
| // CHECK-SAME: TAILCALL |
| // CHECK-NEXT: # -- End function |