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 <[email protected]> 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 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
