[clang] [clang] Allow devirtualisation of indirect calls to final virtual methods (PR #165341)
Oliver Hunt via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 27 17:41:15 PDT 2025
https://github.com/ojhunt created https://github.com/llvm/llvm-project/pull/165341
When -fstrict-vtable-pointers is set we can devirtualise calls to
virtual functions when called indirectly through a separate function
that does not locally know the exact type it is operating on.
This only permits the optimization for regular methods, not any kind
of constructor or destructor.
>From f2b5020e406c521dd185659c04817be0e5c97e3e Mon Sep 17 00:00:00 2001
From: Oliver Hunt <oliver at apple.com>
Date: Mon, 27 Oct 2025 15:12:17 -0700
Subject: [PATCH] [clang] Allow devirtualisation of indirect calls to final
virtual methods
When -fstrict-vtable-pointers is set we can devirtualise calls to
virtual functions when called indirectly through a separate function
that does not locally know the exact type it is operating on.
This only permits the optimization for regular methods, not any kind
of constructor or destructor.
---
clang/lib/CodeGen/CodeGenFunction.cpp | 12 ++-
.../indirect-final-vcall-through-base.cpp | 95 +++++++++++++++++++
2 files changed, 106 insertions(+), 1 deletion(-)
create mode 100644 clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp
index 88628530cf66b..73ce40739d581 100644
--- a/clang/lib/CodeGen/CodeGenFunction.cpp
+++ b/clang/lib/CodeGen/CodeGenFunction.cpp
@@ -1316,7 +1316,17 @@ void CodeGenFunction::StartFunction(GlobalDecl GD, QualType RetTy,
// fast register allocator would be happier...
CXXThisValue = CXXABIThisValue;
}
-
+ if (CGM.getCodeGenOpts().StrictVTablePointers) {
+ const CXXRecordDecl *ThisRecordDecl = MD->getParent();
+ bool IsPolymorphicObject = ThisRecordDecl->isPolymorphic();
+ bool IsStructor = isa<CXXDestructorDecl, CXXConstructorDecl>(MD);
+ bool IsFinal = ThisRecordDecl->isEffectivelyFinal();
+ // We do not care about whether this is a virtual method, because even
+ // if the current method is not virtual, it may be calling another method
+ // that calls a virtual function.
+ if (IsPolymorphicObject && !IsStructor && IsFinal)
+ EmitVTableAssumptionLoads(ThisRecordDecl, LoadCXXThisAddress());
+ }
// Check the 'this' pointer once per function, if it's available.
if (CXXABIThisValue) {
SanitizerSet SkippedChecks;
diff --git a/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp b/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
new file mode 100644
index 0000000000000..9b79f421216f2
--- /dev/null
+++ b/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
@@ -0,0 +1,95 @@
+// RUN: %clang_cc1 -std=c++26 %s -emit-llvm -O3 -o - | FileCheck %s
+// RUN: %clang_cc1 -std=c++26 %s -emit-llvm -O3 -fstrict-vtable-pointers -o - | FileCheck %s --check-prefix=STRICT
+
+using size_t = unsigned long;
+using int64_t = long;
+
+class Base {
+ public:
+ virtual int64_t get(size_t i) const = 0;
+ virtual int64_t getBatch(size_t offset, size_t len, int64_t arr[]) const {
+ int64_t result = 0;
+ for (size_t i = 0; i < len; ++i) {
+ result += get(offset + i);
+ arr[i] = get(offset + i);
+ }
+ return result;
+ }
+ virtual int64_t getSumLen(size_t offset, size_t len) const {
+ int64_t result = 0;
+ for (size_t i = 0; i < len; ++i) {
+ result += get(offset + i);
+ }
+ return result;
+ }
+};
+
+class Derived1 final : public Base {
+public:
+ int64_t get(size_t i) const override {
+ return i;
+ }
+
+ int64_t getBatch(size_t offset, size_t len, int64_t arr[]) const override;
+ virtual int64_t getSumLen(size_t offset, size_t len) const override;
+ int64_t directCall(size_t offset, size_t len);
+ int64_t directBaseCall(size_t offset, size_t len);
+};
+
+int64_t Derived1::directCall(size_t offset, size_t len) {
+ return getSumLen(offset, len);
+}
+
+int64_t Derived1::directBaseCall(size_t offset, size_t len) {
+ return Base::getSumLen(offset, len);
+}
+
+int64_t Derived1::getBatch(size_t offset, size_t len, int64_t arr[]) const {
+ return Base::getBatch(offset, len, arr);
+}
+
+int64_t Derived1::getSumLen(size_t offset, size_t len) const {
+ return Base::getSumLen(offset, len);
+}
+
+// CHECK-LABEL: i64 @_ZN8Derived110directCallEmm(
+// CHECK: for.body
+// CHECK: %[[VCALL_SLOT1:.*]] = load ptr, ptr %vtable
+// CHECK: tail call noundef i64 %[[VCALL_SLOT1]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZNK8Derived19getSumLenEmm
+// CHECK: %[[VCALL_SLOT1:.*]] = load ptr, ptr %vtable
+// CHECK: tail call noundef i64 %[[VCALL_SLOT1]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZN8Derived114directBaseCallEmm
+// CHECK: for.body
+// CHECK: %[[VCALL_SLOT1:.*]] = load ptr, ptr %vtable
+// CHECK: tail call noundef i64 %[[VCALL_SLOT1]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZNK8Derived18getBatchEmmPl
+// CHECK: for.
+// CHECK: %[[VCALL_SLOT1:.*]] = load ptr, ptr %vtable
+// CHECK: tail call noundef i64 %[[VCALL_SLOT1]](
+// CHECK: %[[VCALL_SLOT2:.*]] = load ptr, ptr %vtable
+// CHECK: tail call noundef i64 %[[VCALL_SLOT2]](
+// CHECK: ret i64
+
+
+// STRICT-LABEL: i64 @_ZN8Derived110directCallEmm(
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZNK8Derived19getSumLenEmm(
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZN8Derived114directBaseCallEmm(
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZNK8Derived18getBatchEmmPl
+// STRICT-NOT: call
+// STRICT: ret i64
More information about the cfe-commits
mailing list