// 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