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