[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 21:15:40 PDT 2025


https://github.com/ojhunt updated https://github.com/llvm/llvm-project/pull/165341

>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 1/4] [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

>From 9f2d9c3ed9f6bb11c2bb9f5882dd0f665e9113a0 Mon Sep 17 00:00:00 2001
From: Oliver Hunt <oliver at apple.com>
Date: Mon, 27 Oct 2025 18:55:35 -0700
Subject: [PATCH 2/4] Ensure that the test doesn't doesn't vary according to
 platform mangling

---
 .../test/CodeGenCXX/indirect-final-vcall-through-base.cpp  | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp b/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
index 9b79f421216f2..1051b05e30452 100644
--- a/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
+++ b/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
@@ -1,5 +1,8 @@
-// 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
+// Actual triple does not matter, just ensuring that the ABI being used for
+// mangling and similar is consistent. Choosing x86_64 as that seems to be a
+// configured target for most build configurations
+// RUN: %clang_cc1 -triple=x86_64 -std=c++26 %s -emit-llvm -O3                          -o - | FileCheck %s
+// RUN: %clang_cc1 -triple=x86_64 -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;

>From 9486989d3ba354ee5551c433a59335b9fbe87426 Mon Sep 17 00:00:00 2001
From: Oliver Hunt <oliver at apple.com>
Date: Mon, 27 Oct 2025 19:47:38 -0700
Subject: [PATCH 3/4] increasing test coverage

---
 .../indirect-final-vcall-through-base.cpp     | 277 +++++++++++++++++-
 1 file changed, 261 insertions(+), 16 deletions(-)

diff --git a/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp b/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
index 1051b05e30452..627ad62528ca4 100644
--- a/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
+++ b/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
@@ -7,8 +7,8 @@
 using size_t = unsigned long;
 using int64_t = long;
 
-class Base {
-  public:
+struct Base {
+    virtual int64_t sharedGet(size_t i) const = 0;
     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;
@@ -25,10 +25,14 @@ class Base {
       }
       return result;
     }
+    virtual int64_t useSharedGet(size_t i) {
+      return sharedGet(i);
+    }
 };
   
-class Derived1 final : public Base {
+struct Derived1 final : public Base {
 public:
+    virtual int64_t sharedGet(size_t i) const override { return 17; }
     int64_t get(size_t i) const override {
         return i;
     }
@@ -37,6 +41,58 @@ class Derived1 final : public Base {
     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);
+    virtual int64_t useSharedGet(size_t i) override;
+};
+
+struct Base2 {
+    virtual int64_t sharedGet(size_t i) const = 0;
+    virtual int64_t get2(size_t i) const = 0;
+    virtual int64_t getBatch2(size_t offset, size_t len, int64_t arr[]) const {
+      int64_t result = 0;
+      for (size_t i = 0; i < len; ++i) {
+        result += get2(offset + i);
+        arr[i] = get2(offset + i);
+      }
+      return result;
+    }
+};
+
+struct Derived2 final : Base, Base2 {
+    virtual int64_t sharedGet(size_t i) const override { return 19; };
+    virtual int64_t get(size_t i) const override {
+      return 7;
+    };
+    virtual int64_t get2(size_t i) const override {
+      return 13;
+    };
+    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 getBatch2(size_t offset, size_t len, int64_t arr[]) const override;
+    virtual int64_t useSharedGet(size_t i) override;
+};
+
+struct IntermediateA: virtual Base {
+
+};
+struct IntermediateB: virtual Base2 {
+
+};
+
+struct Derived3Part1: IntermediateA {
+
+};
+
+struct Derived3Part2: IntermediateB {
+
+};
+
+struct Derived3 final: Derived3Part1, Derived3Part2 {
+    virtual int64_t sharedGet(size_t i) const override { return 23; }
+    virtual int64_t get(size_t i) const override { return 27; }
+    virtual int64_t getBatch(size_t offset, size_t len, int64_t arr[]) const override;
+    virtual int64_t get2(size_t i) const override { return 29; }
+    virtual int64_t getBatch2(size_t offset, size_t len, int64_t arr[]) const override;
+    virtual int64_t useSharedGet(size_t i) override;
 };
 
 int64_t Derived1::directCall(size_t offset, size_t len) {
@@ -55,29 +111,186 @@ int64_t Derived1::getSumLen(size_t offset, size_t len) const {
   return Base::getSumLen(offset, len);
 }
 
+int64_t Derived1::useSharedGet(size_t i) {
+  return Base::useSharedGet(i);
+}
+
+int64_t Derived2::getBatch(size_t offset, size_t len, int64_t arr[]) const {
+    return Base::getBatch(offset, len, arr);
+}
+
+int64_t Derived2::getBatch2(size_t offset, size_t len, int64_t arr[]) const {
+    return Base2::getBatch2(offset, len, arr);
+}
+
+int64_t Derived2::getSumLen(size_t offset, size_t len) const {
+  return Base::getSumLen(offset, len);
+}
+
+int64_t Derived2::useSharedGet(size_t i) {
+  return Base::useSharedGet(i);
+}
+
+int64_t Derived3::getBatch(size_t offset, size_t len, int64_t arr[]) const {
+  return Base::getBatch(offset, len, arr);
+}
+int64_t Derived3::getBatch2(size_t offset, size_t len, int64_t arr[]) const {
+  return Base2::getBatch2(offset, len, arr);
+}
+
+int64_t Derived3::useSharedGet(size_t i) {
+  return Base::useSharedGet(i);
+}
+
 // CHECK-LABEL: i64 @_ZN8Derived110directCallEmm(
 // CHECK: for.body
-// CHECK: %[[VCALL_SLOT1:.*]] = load ptr, ptr %vtable
-// CHECK: tail call noundef i64 %[[VCALL_SLOT1]](
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE]]
+// CHECK: [[VFN:%.*]] = load ptr, ptr [[VFN_SLOT]]
+// CHECK: tail call noundef i64 [[VFN]](
 // CHECK: ret i64
 
-// CHECK-LABEL: i64 @_ZNK8Derived19getSumLenEmm
-// CHECK: %[[VCALL_SLOT1:.*]] = load ptr, ptr %vtable
-// CHECK: tail call noundef i64 %[[VCALL_SLOT1]](
+// CHECK-LABEL: i64 @_ZNK8Derived19getSumLenEmm(
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE]]
+// CHECK: [[VFN:%.*]] = load ptr, ptr [[VFN_SLOT]]
+// CHECK: tail call noundef i64 [[VFN]](
 // CHECK: ret i64
 
-// CHECK-LABEL: i64 @_ZN8Derived114directBaseCallEmm
+// CHECK-LABEL: i64 @_ZN8Derived114directBaseCallEmm(
 // CHECK: for.body
-// CHECK: %[[VCALL_SLOT1:.*]] = load ptr, ptr %vtable
-// CHECK: tail call noundef i64 %[[VCALL_SLOT1]](
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE]]
+// CHECK: [[VFN:%.*]] = load ptr, ptr [[VFN_SLOT]]
+// CHECK: tail call noundef i64 [[VFN]](
 // CHECK: ret i64
 
-// CHECK-LABEL: i64 @_ZNK8Derived18getBatchEmmPl
+// 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: [[VTABLE1:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
+// CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
+// CHECK: tail call noundef i64 [[VFN1]](
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
+// CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
+// CHECK: tail call noundef i64 [[VFN2]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZNK8Derived28getBatchEmmPl(
+// CHECK: [[VTABLE1:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
+// CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
+// CHECK: tail call noundef i64 [[VFN1]](
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
+// CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
+// CHECK: tail call noundef i64 [[VFN2]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZNK8Derived29getBatch2EmmPl(
+// CHECK: [[OFFSETBASE:%.*]] = getelementptr inbounds nuw i8, ptr %this
+// CHECK: [[VTABLE1:%.*]] = load ptr, ptr [[OFFSETBASE]]
+// CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
+// CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
+// CHECK: tail call noundef i64 [[VFN1]](
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr [[OFFSETBASE]]
+// CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
+// CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
+// CHECK: tail call noundef i64 [[VFN2]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZThn8_NK8Derived29getBatch2EmmPl(
+// CHECK: [[VTABLE1:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
+// CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
+// CHECK: tail call noundef i64 [[VFN1]](
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
+// CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
+// CHECK: tail call noundef i64 [[VFN2]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZNK8Derived29getSumLenEmm(
+// CHECK: for.body
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE]]
+// CHECK: [[VFN:%.*]] = load ptr, ptr [[VFN_SLOT]]
+// CHECK: tail call noundef i64 [[VFN]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZN8Derived212useSharedGetEm(
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN:%.*]] = load ptr, ptr [[VTABLE]]
+// CHECK: tail call noundef i64 [[VFN]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZNK8Derived38getBatchEmmPl
+// CHECK: [[VTABLE1:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
+// CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
+// CHECK: tail call noundef i64 [[VFN1]](
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
+// CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
+// CHECK: tail call noundef i64 [[VFN2]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZTv0_n40_NK8Derived38getBatchEmmPl
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[THISOFFSET_VSLOT:%.*]] = getelementptr inbounds i8, ptr [[VTABLE]], i64 -40
+// CHECK: [[THIS_OFFSET:%.*]] = load i64, ptr [[THISOFFSET_VSLOT]]
+// CHECK: [[THIS:%.*]] = getelementptr inbounds i8, ptr %this, i64 [[THIS_OFFSET]]
+// CHECK: [[VTABLE1:%.*]] = load ptr, ptr [[THIS]]
+// CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
+// CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr [[THIS]]
+// CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
+// CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
+// CHECK: tail call noundef i64 [[VFN2]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZNK8Derived39getBatch2EmmPl
+// CHECK: [[OFFSETBASE:%.*]] = getelementptr inbounds nuw i8, ptr %this
+// CHECK: [[VTABLE1:%.*]] = load ptr, ptr [[OFFSETBASE]]
+// CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
+// CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
+// CHECK: tail call noundef i64 [[VFN1]](
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr [[OFFSETBASE]]
+// CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
+// CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
+// CHECK: tail call noundef i64 [[VFN2]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZTv0_n40_NK8Derived39getBatch2EmmPl
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[THISOFFSET_VSLOT:%.*]] = getelementptr inbounds i8, ptr [[VTABLE]], i64 -40
+// CHECK: [[THIS_OFFSET:%.*]] = load i64, ptr [[THISOFFSET_VSLOT]]
+// CHECK: [[THIS:%.*]] = getelementptr inbounds i8, ptr %this, i64 [[THIS_OFFSET]]
+// CHECK: [[VTABLE_ADDR:%.*]] = getelementptr inbounds nuw i8, ptr [[THIS]], i64 8
+// CHECK: [[VTABLE1:%.*]] = load ptr, ptr [[VTABLE_ADDR]]
+// CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
+// CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr [[VTABLE_ADDR]]
+// CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
+// CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
+// CHECK: tail call noundef i64 [[VFN2]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZN8Derived312useSharedGetEm
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[VFN:%.*]] = load ptr, ptr [[VTABLE]]
+// CHECK: tail call noundef i64 [[VFN]](
+// CHECK: ret i64
+
+// CHECK-LABEL: i64 @_ZTv0_n56_N8Derived312useSharedGetEm
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+// CHECK: [[THISOFFSET_VSLOT:%.*]] = getelementptr inbounds i8, ptr [[VTABLE]], i64 -56
+// CHECK: [[THIS_OFFSET:%.*]] = load i64, ptr [[THISOFFSET_VSLOT]]
+// CHECK: [[THIS:%.*]] = getelementptr inbounds i8, ptr %this, i64 [[THIS_OFFSET]]
+// CHECK: [[VTABLE:%.*]] = load ptr, ptr [[THIS]]
+// CHECK: [[VFN:%.*]] = load ptr, ptr [[VTABLE]]
+// CHECK: tail call noundef i64 [[VFN]](
 // CHECK: ret i64
 
 
@@ -96,3 +309,35 @@ int64_t Derived1::getSumLen(size_t offset, size_t len) const {
 // STRICT-LABEL: i64 @_ZNK8Derived18getBatchEmmPl
 // STRICT-NOT: call
 // STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZNK8Derived29getSumLenEmm(
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZN8Derived212useSharedGetEm(
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZNK8Derived38getBatchEmmPl
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZTv0_n40_NK8Derived38getBatchEmmPl
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZNK8Derived39getBatch2EmmPl
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZTv0_n40_NK8Derived39getBatch2EmmPl
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZN8Derived312useSharedGetEm
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZTv0_n56_N8Derived312useSharedGetEm
+// STRICT-NOT: call
+// STRICT: ret i64

>From 9e57865389c57e16bf1d6cd7c45acd26dd5d0255 Mon Sep 17 00:00:00 2001
From: Oliver Hunt <oliver at apple.com>
Date: Mon, 27 Oct 2025 21:13:30 -0700
Subject: [PATCH 4/4] Added the strict-vtable test results, and added tests

New tests for multiple and virtual inheritance to verify that
we still perform this adjustments even when  devirtualising the
calls
---
 .../indirect-final-vcall-through-base.cpp     | 79 ++++++++++++++++++-
 1 file changed, 75 insertions(+), 4 deletions(-)

diff --git a/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp b/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
index 627ad62528ca4..4203c869acaea 100644
--- a/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
+++ b/clang/test/CodeGenCXX/indirect-final-vcall-through-base.cpp
@@ -45,6 +45,7 @@ struct Derived1 final : public Base {
 };
 
 struct Base2 {
+    unsigned value = 0;
     virtual int64_t sharedGet(size_t i) const = 0;
     virtual int64_t get2(size_t i) const = 0;
     virtual int64_t getBatch2(size_t offset, size_t len, int64_t arr[]) const {
@@ -55,6 +56,13 @@ struct Base2 {
       }
       return result;
     }
+    virtual int64_t getValue() = 0;
+    virtual int64_t callGetValue() {
+      return getValue();
+    }
+    virtual int64_t useBase(Base *b) {
+      return b->get(0);
+    }
 };
 
 struct Derived2 final : Base, Base2 {
@@ -69,6 +77,9 @@ struct Derived2 final : Base, Base2 {
     virtual int64_t getSumLen(size_t offset, size_t len) const override;
     int64_t getBatch2(size_t offset, size_t len, int64_t arr[]) const override;
     virtual int64_t useSharedGet(size_t i) override;
+    virtual int64_t useBase(Base *b) override;
+    virtual int64_t getValue() override { return value; }
+    virtual int64_t callGetValue() override;
 };
 
 struct IntermediateA: virtual Base {
@@ -93,6 +104,9 @@ struct Derived3 final: Derived3Part1, Derived3Part2 {
     virtual int64_t get2(size_t i) const override { return 29; }
     virtual int64_t getBatch2(size_t offset, size_t len, int64_t arr[]) const override;
     virtual int64_t useSharedGet(size_t i) override;
+    virtual int64_t useBase(Base *b) override;
+    virtual int64_t getValue() override { return value; }
+    virtual int64_t callGetValue() override;
 };
 
 int64_t Derived1::directCall(size_t offset, size_t len) {
@@ -131,6 +145,14 @@ int64_t Derived2::useSharedGet(size_t i) {
   return Base::useSharedGet(i);
 }
 
+int64_t Derived2::useBase(Base *b) {
+  return Base2::useBase(this);
+}
+
+int64_t Derived2::callGetValue() {
+  return Base2::callGetValue();
+}
+
 int64_t Derived3::getBatch(size_t offset, size_t len, int64_t arr[]) const {
   return Base::getBatch(offset, len, arr);
 }
@@ -141,6 +163,13 @@ int64_t Derived3::getBatch2(size_t offset, size_t len, int64_t arr[]) const {
 int64_t Derived3::useSharedGet(size_t i) {
   return Base::useSharedGet(i);
 }
+int64_t Derived3::useBase(Base *b) {
+  return Base2::useBase(this);
+}
+
+int64_t Derived3::callGetValue() {
+  return Base2::callGetValue();
+}
 
 // CHECK-LABEL: i64 @_ZN8Derived110directCallEmm(
 // CHECK: for.body
@@ -263,15 +292,22 @@ int64_t Derived3::useSharedGet(size_t i) {
 // CHECK: ret i64
 
 // CHECK-LABEL: i64 @_ZTv0_n40_NK8Derived39getBatch2EmmPl
+// CHECK: entry:
+  // %vtable = load ptr, ptr %this, align 8, !tbaa !6
 // CHECK: [[VTABLE:%.*]] = load ptr, ptr %this
+  // %0 = getelementptr inbounds i8, ptr %vtable, i64 -40
 // CHECK: [[THISOFFSET_VSLOT:%.*]] = getelementptr inbounds i8, ptr [[VTABLE]], i64 -40
+  // %1 = load i64, ptr %0, align 8
 // CHECK: [[THIS_OFFSET:%.*]] = load i64, ptr [[THISOFFSET_VSLOT]]
-// CHECK: [[THIS:%.*]] = getelementptr inbounds i8, ptr %this, i64 [[THIS_OFFSET]]
-// CHECK: [[VTABLE_ADDR:%.*]] = getelementptr inbounds nuw i8, ptr [[THIS]], i64 8
-// CHECK: [[VTABLE1:%.*]] = load ptr, ptr [[VTABLE_ADDR]]
+  // %2 = getelementptr inbounds i8, ptr %this, i64 %1
+// CHECK: [[BASE:%.*]] = getelementptr inbounds i8, ptr %this, i64 [[THIS_OFFSET]]
+         // %add.ptr.i = getelementptr inbounds nuw i8, ptr %2, i64 16
+// CHECK: [[THIS:%.*]] = getelementptr inbounds nuw i8, ptr [[BASE]], i64 16
+// CHECK: {{for.body.*:}}
+// CHECK: [[VTABLE1:%.*]] = load ptr, ptr [[THIS]]
 // CHECK: [[VFN_SLOT1:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE1]]
 // CHECK: [[VFN1:%.*]] = load ptr, ptr [[VFN_SLOT1]]
-// CHECK: [[VTABLE2:%.*]] = load ptr, ptr [[VTABLE_ADDR]]
+// CHECK: [[VTABLE2:%.*]] = load ptr, ptr [[THIS]]
 // CHECK: [[VFN_SLOT2:%.*]] = getelementptr inbounds nuw i8, ptr [[VTABLE2]]
 // CHECK: [[VFN2:%.*]] = load ptr, ptr [[VFN_SLOT2]]
 // CHECK: tail call noundef i64 [[VFN2]](
@@ -318,6 +354,21 @@ int64_t Derived3::useSharedGet(size_t i) {
 // STRICT-NOT: call
 // STRICT: ret i64
 
+// STRICT-LABEL: i64 @_ZN8Derived27useBaseEP4Base
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZN8Derived212callGetValueEv(
+// STRICT: [[OFFSET_THIS:%.*]] = getelementptr inbounds nuw i8, ptr %this, i64 8
+// STRICT: [[INVARIANT_THIS:%.*]] = tail call ptr @llvm.strip.invariant.group.p0(ptr nonnull [[OFFSET_THIS]])
+// STRICT: [[VALUE_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[INVARIANT_THIS]], i64 8
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZThn8_N8Derived212callGetValueEv
+// STRICT: [[INVARIANT_THIS:%.*]] = tail call ptr @llvm.strip.invariant.group.p0(ptr nonnull readonly %this)
+// STRICT: [[VALUE_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[INVARIANT_THIS]], i64 8
+// STRICT: ret i64
+
 // STRICT-LABEL: i64 @_ZNK8Derived38getBatchEmmPl
 // STRICT-NOT: call
 // STRICT: ret i64
@@ -341,3 +392,23 @@ int64_t Derived3::useSharedGet(size_t i) {
 // STRICT-LABEL: i64 @_ZTv0_n56_N8Derived312useSharedGetEm
 // STRICT-NOT: call
 // STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZN8Derived37useBaseEP4Base
+// STRICT-NOT: call
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZN8Derived312callGetValueEv(
+// STRICT: [[TRUE_THIS:%.*]] = getelementptr inbounds nuw i8, ptr %this, i64 16
+// STRICT: [[INVARIANT_THIS:%.*]] = tail call ptr @llvm.strip.invariant.group.p0(ptr nonnull [[TRUE_THIS]])
+// STRICT: [[VALUE_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[INVARIANT_THIS]], i64 8
+// STRICT: ret i64
+
+// STRICT-LABEL: i64 @_ZTv0_n56_N8Derived312callGetValueEv
+// STRICT: [[VTABLE:%.*]] = load ptr, ptr %this
+// STRICT: [[THISOFFSET_VSLOT:%.*]] = getelementptr inbounds i8, ptr [[VTABLE]], i64 -48
+// STRICT: [[THIS_OFFSET:%.*]] = load i64, ptr [[THISOFFSET_VSLOT]]
+// STRICT: [[VIRTUAL_BASE:%.*]] = getelementptr inbounds i8, ptr %this, i64 [[THIS_OFFSET]]
+// STRICT: [[TRUE_THIS:%.*]] = getelementptr inbounds nuw i8, ptr [[VIRTUAL_BASE]], i64 16
+// STRICT: [[INVARIANT_THIS:%.*]] = tail call ptr @llvm.strip.invariant.group.p0(ptr nonnull [[TRUE_THIS]])
+// STRICT: [[VALUE_PTR:%.*]] = getelementptr inbounds nuw i8, ptr [[INVARIANT_THIS]], i64 8
+// STRICT: ret i64



More information about the cfe-commits mailing list