// RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -O0 -fclangir -emit-cir %s -o %t.cir // RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s // RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -O0 -fclangir -emit-llvm %s -o %t-cir.ll // RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s // RUN: %clang_cc1 -triple aarch64-none-linux-android21 -std=c++20 -O0 -emit-llvm %s -o %t.ll // RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s struct Base { virtual ~Base(); virtual int foo(); }; struct Final final : Base { ~Final(); int foo() override; }; // Devirtualized destructor call: calling a virtual destructor on a type // that the compiler can statically resolve (e.g. a final class). void test_devirtualized_dtor(Final *f) { f->~Final(); } // CIR-LABEL: @_Z23test_devirtualized_dtorP5Final // CIR: cir.call @_ZN5FinalD1Ev({{.*}}) nothrow // LLVM-LABEL: @_Z23test_devirtualized_dtorP5Final // LLVM: call void @_ZN5FinalD1Ev( // OGCG-LABEL: @_Z23test_devirtualized_dtorP5Final // OGCG: call void @_ZN5FinalD1Ev( // Devirtualized method call: calling a virtual method on a final class. int test_devirtualized_method(Final *f) { return f->foo(); } // CIR-LABEL: @_Z25test_devirtualized_methodP5Final // CIR: cir.call @_ZN5Final3fooEv( // LLVM-LABEL: @_Z25test_devirtualized_methodP5Final // LLVM: call noundef i32 @_ZN5Final3fooEv( // OGCG-LABEL: @_Z25test_devirtualized_methodP5Final // OGCG: call noundef i32 @_ZN5Final3fooEv( // Covariant return type: clone() returns Final* in the derived class but // Base* in the base. Because the return types differ, we skip devirtualization // and go through the vtable. struct Covariant : Base { virtual Covariant* clone(); }; struct FinalCovariant final : Covariant { FinalCovariant* clone() override; }; Base* test_covariant_return(FinalCovariant *f) { return static_cast(f)->clone(); } // CIR-LABEL: @_Z21test_covariant_returnP14FinalCovariant // CIR: cir.vtable.get_virtual_fn_addr // CIR: cir.call %{{.*}}({{.*}}) // LLVM-LABEL: @_Z21test_covariant_returnP14FinalCovariant // LLVM: call {{.*}} %{{.*}}( // OGCG-LABEL: @_Z21test_covariant_returnP14FinalCovariant // OGCG: call {{.*}} %{{.*}}( // FinalNoOverride: final class that does not override foo(). // The compiler knows the dynamic type, so it devirtualizes to Base::foo(). struct FinalNoOverride final : Base {}; void test_final_no_override() { FinalNoOverride local; local.foo(); } // CIR-LABEL: @_Z22test_final_no_overridev // CIR: cir.call @_ZN4Base3fooEv( // LLVM-LABEL: @_Z22test_final_no_overridev // LLVM: call noundef i32 @_ZN4Base3fooEv( // OGCG-LABEL: @_Z22test_final_no_overridev // OGCG: call noundef i32 @_ZN4Base3fooEv( // Cross-class: method is defined in B but called through a C* (separate base). // getCXXRecord(base)=C != devirtualizedClass=B, so devirtualization is cancelled // and a virtual call is emitted. struct CrossA { virtual void f(); }; struct CrossB : CrossA { void f() override; }; struct CrossC : CrossA { }; struct CrossMulti final : CrossB, CrossC {}; void test_cross_class(CrossMulti *m) { static_cast(m)->f(); } // CIR-LABEL: @_Z16test_cross_classP10CrossMulti // CIR: cir.vtable.get_virtual_fn_addr // CIR: cir.call %{{.*}}({{.*}}) // LLVM-LABEL: @_Z16test_cross_classP10CrossMulti // LLVM: call void %{{.*}}( // OGCG-LABEL: @_Z16test_cross_classP10CrossMulti // OGCG: call void %{{.*}}(