[clang] [CIR] Implement devirtualized member function calls (PR #195106)
via cfe-commits
cfe-commits at lists.llvm.org
Sat May 2 10:21:32 PDT 2026
https://github.com/AbdallahRashed updated https://github.com/llvm/llvm-project/pull/195106
>From 19298d4a4d017b116fef8e904273b08ca87706b7 Mon Sep 17 00:00:00 2001
From: AbdallahRashed <abdallah.mrashed at gmail.com>
Date: Thu, 30 Apr 2026 17:01:35 +0200
Subject: [PATCH] [CIR] Implement devirtualized member function calls
Implement the devirtualization logic in
emitCXXMemberOrOperatorMemberCallExpr that was previously guarded by
MissingFeatures::devirtualizeMemberFunction(). This enables the compiler
to emit direct calls instead of virtual dispatch when the dynamic type
can be statically resolved (e.g., final classes, local variables of
known type).
This fixes the 'devirtualized destructor call' errorNYI in
CIRGenExprCXX.cpp, which is point 5 of #192330.
The implementation follows OGCG: use getDevirtualizedMethod() to check if
devirtualization is possible, then getCorrespondingMethodInClass() to find
the target method. For destructors, use getAddrOfFunction() instead of
getAddrOfCXXStructor() since the devirtualized callee may differ from the
original.
Fixes point 5 of #192330.
---
clang/include/clang/CIR/MissingFeatures.h | 1 -
clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp | 40 ++++++-
clang/test/CIR/CodeGen/devirtualize.cpp | 102 ++++++++++++++++++
clang/test/CIR/CodeGen/vbase.cpp | 22 +---
.../test/CIR/CodeGen/virtual-fn-calls-eh.cpp | 18 +---
.../CIR/CodeGen/virtual-function-calls.cpp | 12 +--
6 files changed, 148 insertions(+), 47 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/devirtualize.cpp
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 645fae3e4404d..c85fcf6556f7b 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -248,7 +248,6 @@ struct MissingFeatures {
static bool deferredCXXGlobalInit() { return false; }
static bool deleteArray() { return false; }
static bool devirtualizeDestructor() { return false; }
- static bool devirtualizeMemberFunction() { return false; }
static bool dtorCleanups() { return false; }
static bool ehCleanupScope() { return false; }
static bool ehScopeFilter() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
index 39a2068a7073f..06c4f40fa8561 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
@@ -132,7 +132,41 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
// Compute the object pointer.
bool canUseVirtualCall = md->isVirtual() && !hasQualifier;
const CXXMethodDecl *devirtualizedMethod = nullptr;
- assert(!cir::MissingFeatures::devirtualizeMemberFunction());
+ // TODO: This devirtualization logic should be hoisted to the AST layer so it
+ // can be shared with classic codegen (see CGExprCXX.cpp).
+ if (canUseVirtualCall &&
+ md->getDevirtualizedMethod(base, getLangOpts().AppleKext)) {
+ const CXXRecordDecl *bestDynamicDecl = base->getBestDynamicClassType();
+ devirtualizedMethod = md->getCorrespondingMethodInClass(bestDynamicDecl);
+ assert(devirtualizedMethod);
+ const CXXRecordDecl *devirtualizedClass = devirtualizedMethod->getParent();
+ const Expr *inner = base->IgnoreParenBaseCasts();
+ auto getCXXRecord = [](const Expr *e) -> const CXXRecordDecl * {
+ QualType t = e->getType();
+ if (t->isRecordType())
+ return t->getAsCXXRecordDecl();
+ return t->getPointeeCXXRecordDecl();
+ };
+ if (devirtualizedMethod->getReturnType().getCanonicalType() !=
+ md->getReturnType().getCanonicalType())
+ // If the return types are not the same, this might be a case where more
+ // code needs to run to compensate for it. For example, the derived
+ // method might return a type that inherits from the return type of MD
+ // and has a prefix.
+ // For now we just avoid devirtualizing these covariant cases.
+ devirtualizedMethod = nullptr;
+ else if (getCXXRecord(inner) == devirtualizedClass)
+ // If the class of the Inner expression is where the dynamic method
+ // is defined, build the this pointer from it.
+ base = inner;
+ else if (getCXXRecord(base) != devirtualizedClass) {
+ // If the method is defined in a class that is not the best dynamic
+ // one or the one of the full expression, we would have to build
+ // a derived-to-base cast to compute the correct this pointer, but
+ // we don't have support for that yet, so do a virtual call.
+ devirtualizedMethod = nullptr;
+ }
+ }
// Note on trivial assignment
// --------------------------
@@ -214,8 +248,8 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
callee = CIRGenCallee::forDirect(
cgm.getAddrOfCXXStructor(globalDecl, fInfo, ty), globalDecl);
} else {
- cgm.errorNYI(ce->getSourceRange(), "devirtualized destructor call");
- return RValue::get(nullptr);
+ callee = CIRGenCallee::forDirect(cgm.getAddrOfFunction(globalDecl, ty),
+ globalDecl);
}
QualType thisTy =
diff --git a/clang/test/CIR/CodeGen/devirtualize.cpp b/clang/test/CIR/CodeGen/devirtualize.cpp
new file mode 100644
index 0000000000000..a488a8390fa32
--- /dev/null
+++ b/clang/test/CIR/CodeGen/devirtualize.cpp
@@ -0,0 +1,102 @@
+// 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<Covariant*>(f)->clone();
+}
+
+// CIR-LABEL: @_Z21test_covariant_returnP14FinalCovariant
+// 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<CrossC *>(m)->f(); }
+
+// CIR-LABEL: @_Z16test_cross_classP10CrossMulti
+// CIR: cir.call %{{.*}}({{.*}})
+
+// LLVM-LABEL: @_Z16test_cross_classP10CrossMulti
+// LLVM: call void %{{.*}}(
+
+// OGCG-LABEL: @_Z16test_cross_classP10CrossMulti
+// OGCG: call void %{{.*}}(
diff --git a/clang/test/CIR/CodeGen/vbase.cpp b/clang/test/CIR/CodeGen/vbase.cpp
index 3ae8d192c798a..6a6e1eeb2fdb0 100644
--- a/clang/test/CIR/CodeGen/vbase.cpp
+++ b/clang/test/CIR/CodeGen/vbase.cpp
@@ -70,22 +70,14 @@ void ppp() { B b; }
// CIR: %[[ADJ_THIS_I8:.+]] = cir.ptr_stride %[[D_I8]], %[[OFFSET]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
// CIR: %[[ADJ_THIS_D:.+]] = cir.cast bitcast %[[ADJ_THIS_I8]] : !cir.ptr<!u8i> -> !cir.ptr<!rec_Derived>
// CIR: %[[BASE_THIS:.+]] = cir.cast bitcast %[[ADJ_THIS_D]] : !cir.ptr<!rec_Derived> -> !cir.ptr<!rec_Base>
-// CIR: %[[BASE_VPTR_PTR:.+]] = cir.vtable.get_vptr %[[BASE_THIS]] : !cir.ptr<!rec_Base> -> !cir.ptr<!cir.vptr>
-// CIR: %[[BASE_VPTR:.+]] = cir.load {{.*}} %[[BASE_VPTR_PTR]] : !cir.ptr<!cir.vptr>, !cir.vptr
-// CIR: %[[SLOT_PTR:.+]] = cir.vtable.get_virtual_fn_addr %[[BASE_VPTR]][0] : !cir.vptr -> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>
-// CIR: %[[FN:.+]] = cir.load {{.*}} %[[SLOT_PTR]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>, !cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>
-// CIR: cir.call %[[FN]](%[[BASE_THIS]]) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>, !cir.ptr<!rec_Base> {{.*}}) -> ()
+// CIR: cir.call @_ZN4Base1fEv(%[[BASE_THIS]]) : (!cir.ptr<!rec_Base> {{.*}}) -> ()
// CIR: cir.return
// CIR: cir.func {{.*}}@_Z1gv()
// CIR: %[[DF:.+]] = cir.alloca !rec_DerivedFinal, !cir.ptr<!rec_DerivedFinal>, ["df", init]
// CIR: cir.call @_ZN12DerivedFinalC1Ev(%[[DF]]) nothrow : (!cir.ptr<!rec_DerivedFinal> {{.*}}) -> ()
// CIR: %[[BASE_THIS_2:.+]] = cir.base_class_addr %[[DF]] : !cir.ptr<!rec_DerivedFinal> nonnull [0] -> !cir.ptr<!rec_Base>
-// CIR: %[[BASE_VPTR_PTR_2:.+]] = cir.vtable.get_vptr %[[BASE_THIS_2]] : !cir.ptr<!rec_Base> -> !cir.ptr<!cir.vptr>
-// CIR: %[[BASE_VPTR_2:.+]] = cir.load {{.*}} %[[BASE_VPTR_PTR_2]] : !cir.ptr<!cir.vptr>, !cir.vptr
-// CIR: %[[SLOT_PTR_2:.+]] = cir.vtable.get_virtual_fn_addr %[[BASE_VPTR_2]][0] : !cir.vptr -> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>
-// CIR: %[[FN_2:.+]] = cir.load {{.*}} %[[SLOT_PTR_2]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>, !cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>
-// CIR: cir.call %[[FN_2]](%[[BASE_THIS_2]]) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>, !cir.ptr<!rec_Base> {{.*}}) -> ()
+// CIR: cir.call @_ZN4Base1fEv(%[[BASE_THIS_2]]) : (!cir.ptr<!rec_Base> {{.*}}) -> ()
// CIR: cir.return
// LLVM: define {{.*}}void @_Z1fv(){{.*}}
@@ -95,19 +87,13 @@ void ppp() { B b; }
// LLVM: %[[NEG32_PTR:.+]] = getelementptr i8, ptr %[[VPTR_ADDR]], i64 -32
// LLVM: %[[OFF:.+]] = load i64, ptr %[[NEG32_PTR]]
// LLVM: %[[ADJ_THIS:.+]] = getelementptr i8, ptr %[[D]], i64 %[[OFF]]
-// LLVM: %[[VFN_TAB:.+]] = load ptr, ptr %[[ADJ_THIS]]
-// LLVM: %[[SLOT0:.+]] = getelementptr inbounds ptr, ptr %[[VFN_TAB]], i32 0
-// LLVM: %[[VFN:.+]] = load ptr, ptr %[[SLOT0]]
-// LLVM: call void %[[VFN]](ptr {{.*}}%[[ADJ_THIS]])
+// LLVM: call void @_ZN4Base1fEv(ptr {{.*}}%[[ADJ_THIS]])
// LLVM: ret void
// LLVM: define {{.*}}void @_Z1gv(){{.*}}
// LLVM: %[[DF:.+]] = alloca {{.*}}
// LLVM: call void @_ZN12DerivedFinalC1Ev(ptr {{.*}} %[[DF]])
-// LLVM: %[[VPTR2:.+]] = load ptr, ptr %[[DF]]
-// LLVM: %[[SLOT0_2:.+]] = getelementptr inbounds ptr, ptr %[[VPTR2]], i32 0
-// LLVM: %[[VFN2:.+]] = load ptr, ptr %[[SLOT0_2]]
-// LLVM: call void %[[VFN2]](ptr {{.*}}%[[DF]])
+// LLVM: call void @_ZN4Base1fEv(ptr {{.*}}%[[DF]])
// LLVM: ret void
// OGCG: define {{.*}}void @_Z1fv()
diff --git a/clang/test/CIR/CodeGen/virtual-fn-calls-eh.cpp b/clang/test/CIR/CodeGen/virtual-fn-calls-eh.cpp
index 5ae1b5745d711..71f749d53dc58 100644
--- a/clang/test/CIR/CodeGen/virtual-fn-calls-eh.cpp
+++ b/clang/test/CIR/CodeGen/virtual-fn-calls-eh.cpp
@@ -22,11 +22,7 @@ void call_virtual_fn_in_cleanup_scope() {
// CIR: cir.call @_ZN1BC2Ev(%[[B]])
// CIR: cir.cleanup.scope {
// CIR: %[[C_LITERAL:.*]] = cir.const #cir.int<99> : !s8i
-// CIR: %[[VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[B]] : !cir.ptr<!rec_B> -> !cir.ptr<!cir.vptr>
-// CIR: %[[VPTR:.*]] = cir.load{{.*}} %[[VPTR_ADDR]]
-// CIR: %[[FN_PTR_ADDR:.*]] = cir.vtable.get_virtual_fn_addr %[[VPTR]][0] : !cir.vptr -> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>>
-// CIR: %[[FN_PTR:.*]] = cir.load{{.*}} %[[FN_PTR_ADDR:.*]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>>, !cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>
-// CIR: cir.call %[[FN_PTR]](%[[B]], %[[C_LITERAL]]) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>, !cir.ptr<!rec_B> {{.*}}, !s8i {{.*}}) -> ()
+// CIR: cir.call @_ZN1B1fEc(%[[B]], %[[C_LITERAL]]) : (!cir.ptr<!rec_B> {{.*}}, !s8i {{.*}}) -> ()
// CIR: cir.yield
// CIR: } cleanup all {
// CIR: cir.call @_ZN1BD1Ev(%[[B]]) nothrow : (!cir.ptr<!rec_B> {{.*}}) -> ()
@@ -39,11 +35,7 @@ void call_virtual_fn_in_cleanup_scope() {
// CIR-FLAT: cir.br ^[[CLEANUP_SCOPE:bb[0-9]+]]
// CIR-FLAT: ^[[CLEANUP_SCOPE]]:
// CIR-FLAT: %[[C_LITERAL:.*]] = cir.const #cir.int<99> : !s8i
-// CIR-FLAT: %[[VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[B]] : !cir.ptr<!rec_B> -> !cir.ptr<!cir.vptr>
-// CIR-FLAT: %[[VPTR:.*]] = cir.load{{.*}} %[[VPTR_ADDR]]
-// CIR-FLAT: %[[FN_PTR_ADDR:.*]] = cir.vtable.get_virtual_fn_addr %[[VPTR]][0] : !cir.vptr -> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>>
-// CIR-FLAT: %[[FN_PTR:.*]] = cir.load{{.*}} %[[FN_PTR_ADDR:.*]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>>, !cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>
-// CIR-FLAT: cir.try_call %[[FN_PTR]](%[[B]], %[[C_LITERAL]]) ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]] : (!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>, !cir.ptr<!rec_B> {{.*}}, !s8i {{.*}}) -> ()
+// CIR-FLAT: cir.try_call @_ZN1B1fEc(%[[B]], %[[C_LITERAL]]) ^[[NORMAL:bb[0-9]+]], ^[[UNWIND:bb[0-9]+]] : (!cir.ptr<!rec_B> {{.*}}, !s8i {{.*}}) -> ()
// CIR-FLAT: ^[[NORMAL]]: // pred: ^bb1
// CIR-FLAT: cir.br ^[[NORMAL_CLEANUP:bb[0-9]+]]
// CIR-FLAT: ^[[NORMAL_CLEANUP]]:
@@ -67,10 +59,7 @@ void call_virtual_fn_in_cleanup_scope() {
// LLVM: call void @_ZN1BC2Ev(ptr {{.*}} %[[B]])
// LLVM: br label %[[CLEANUP_SCOPE:.*]]
// LLVM: [[CLEANUP_SCOPE]]:
-// LLVM: %[[B_VPTR:.*]] = load ptr, ptr %[[B]]
-// LLVM: %[[FN_PTR_ADDR:.*]] = getelementptr inbounds ptr, ptr %[[B_VPTR]], i32 0
-// LLVM: %[[FN_PTR:.*]] = load ptr, ptr %[[FN_PTR_ADDR]]
-// LLVM: invoke void %[[FN_PTR]](ptr {{.*}} %[[B]], i8 {{.*}} 99)
+// LLVM: invoke void @_ZN1B1fEc(ptr {{.*}} %[[B]], i8 noundef 99)
// LLVM: to label %[[NORMAL_CONTINUE:.*]] unwind label %[[UNWIND:.*]]
// LLVM: [[NORMAL_CONTINUE]]
// LLVM: br label %[[NORMAL_CLEANUP:.*]]
@@ -95,7 +84,6 @@ void call_virtual_fn_in_cleanup_scope() {
// LLVM: [[DONE]]:
// LLVM: ret void
-// Note: OGCG devirtualizes the call. We don't do that yet in CIR.
// OGCG: define {{.*}} void @_Z32call_virtual_fn_in_cleanup_scopev()
// OGCG: %[[B:.*]] = alloca %struct.B, align 8
// OGCG: %[[EXN_SLOT:.*]] = alloca ptr
diff --git a/clang/test/CIR/CodeGen/virtual-function-calls.cpp b/clang/test/CIR/CodeGen/virtual-function-calls.cpp
index b316aa2567b65..d683bd45d7f61 100644
--- a/clang/test/CIR/CodeGen/virtual-function-calls.cpp
+++ b/clang/test/CIR/CodeGen/virtual-function-calls.cpp
@@ -95,11 +95,7 @@ void call_virtual_fn_in_cleanup_scope() {
// CIR: cir.call @_ZN1BC2Ev(%[[B]])
// CIR: cir.cleanup.scope {
// CIR: %[[C_LITERAL:.*]] = cir.const #cir.int<99> : !s8i
-// CIR: %[[VPTR_ADDR:.*]] = cir.vtable.get_vptr %[[B]] : !cir.ptr<!rec_B> -> !cir.ptr<!cir.vptr>
-// CIR: %[[VPTR:.*]] = cir.load{{.*}} %[[VPTR_ADDR]]
-// CIR: %[[FN_PTR_ADDR:.*]] = cir.vtable.get_virtual_fn_addr %[[VPTR]][0] : !cir.vptr -> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>>
-// CIR: %[[FN_PTR:.*]] = cir.load{{.*}} %[[FN_PTR_ADDR:.*]] : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>>, !cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>
-// CIR: cir.call %[[FN_PTR]](%[[B]], %[[C_LITERAL]]) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_B>, !s8i)>>, !cir.ptr<!rec_B> {{.*}}, !s8i {{.*}}) -> ()
+// CIR: cir.call @_ZN1B1fEc(%[[B]], %[[C_LITERAL]]) : (!cir.ptr<!rec_B> {{.*}}, !s8i {{.*}}) -> ()
// CIR: cir.yield
// CIR: } cleanup normal {
// CIR: cir.call @_ZN1BD1Ev(%[[B]]) nothrow : (!cir.ptr<!rec_B> {{.*}}) -> ()
@@ -111,15 +107,11 @@ void call_virtual_fn_in_cleanup_scope() {
// LLVM: call void @_ZN1BC2Ev(ptr {{.*}} %[[B]])
// LLVM: br label %[[CLEANUP_SCOPE:.*]]
// LLVM: [[CLEANUP_SCOPE]]:
-// LLVM: %[[B_VPTR:.*]] = load ptr, ptr %[[B]]
-// LLVM: %[[FN_PTR_ADDR:.*]] = getelementptr inbounds ptr, ptr %[[B_VPTR]], i32 0
-// LLVM: %[[FN_PTR:.*]] = load ptr, ptr %[[FN_PTR_ADDR]]
-// LLVM: call void %[[FN_PTR]](ptr {{.*}} %[[B]], i8 noundef 99)
+// LLVM: call void @_ZN1B1fEc(ptr {{.*}} %[[B]], i8 noundef 99)
// LLVM: br label %[[NORMAL_CLEANUP:.*]]
// LLVM: [[NORMAL_CLEANUP]]:
// LLVM: call void @_ZN1BD1Ev(ptr {{.*}} %[[B]])
-// Note: OGCG devirtualizes the call. We don't do that yet in CIR.
// OGCG: define {{.*}} void @_Z32call_virtual_fn_in_cleanup_scopev()
// OGCG: %[[B:.*]] = alloca %struct.B, align 8
// OGCG: call void @_ZN1BC2Ev(ptr {{.*}} %[[B]])
More information about the cfe-commits
mailing list