Skip to content

[NFC][WPD] Add constant propagation tests checking relative vtables #138989

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 13, 2025

Conversation

PiJoules
Copy link
Contributor

@PiJoules PiJoules commented May 7, 2025

This is a patch with precommitted tests to make
#136630 easier to review. The virtual-const-prop-small-alignment-* tests check the output when the loaded int alignment is less than the vtable alignment.

This also changes some constants to make it easier to differentiate between propagated values in vtables.

@PiJoules PiJoules requested review from pcc and ilovepi May 7, 2025 23:31
@llvmbot
Copy link
Member

llvmbot commented May 7, 2025

@llvm/pr-subscribers-llvm-transforms

Author: None (PiJoules)

Changes

This is a patch with precommitted tests to make
#136630 easier to review. The virtual-const-prop-small-alignment-* tests check the output when the loaded int alignment is less than the vtable alignment.

This also changes some constants to make it easier to differentiate between propagated values in vtables.


Patch is 46.88 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/138989.diff

5 Files Affected:

  • (modified) llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-begin.ll (+96-29)
  • (modified) llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll (+71-2)
  • (modified) llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-end.ll (+74-9)
  • (added) llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-small-alignment-32.ll (+284)
  • (added) llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-small-alignment-64.ll (+285)
diff --git a/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-begin.ll b/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-begin.ll
index 11273804e2081..2163d440e9a33 100644
--- a/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-begin.ll
+++ b/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-begin.ll
@@ -1,34 +1,35 @@
 ; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility %s | FileCheck %s
 
 target datalayout = "e-p:64:64"
-target triple = "x86_64-unknown-linux-gnu"
 
-; CHECK: [[VT1DATA:@[^ ]*]] = private constant { [8 x i8], [3 x ptr], [0 x i8] } { [8 x i8] c"\00\00\00\01\00\00\00\02", [3 x ptr] [ptr @vf0i1, ptr @vf1i1, ptr @vf1i32], [0 x i8] zeroinitializer }, section "vt1sec", !type [[T8:![0-9]+]]
+;; Note that i16 is used here such that we can ensure all constants for "typeid"
+;; can come before the vtable.
+; CHECK: [[VT1DATA:@[^ ]*]] = private constant { [8 x i8], [3 x ptr], [0 x i8] } { [8 x i8] c"\00\00\00\00\00\03\00\02", [3 x ptr] [ptr @vf0i1, ptr @vf1i1, ptr @vf1i16], [0 x i8] zeroinitializer }, section "vt1sec", !type [[T8:![0-9]+]]
 @vt1 = constant [3 x ptr] [
 ptr @vf0i1,
 ptr @vf1i1,
-ptr @vf1i32
+ptr @vf1i16
 ], section "vt1sec", !type !0
 
-; CHECK: [[VT2DATA:@[^ ]*]] = private constant { [8 x i8], [3 x ptr], [0 x i8] } { [8 x i8] c"\00\00\00\02\00\00\00\01", [3 x ptr] [ptr @vf1i1, ptr @vf0i1, ptr @vf2i32], [0 x i8] zeroinitializer }, !type [[T8]]
+; CHECK: [[VT2DATA:@[^ ]*]] = private constant { [8 x i8], [3 x ptr], [0 x i8] } { [8 x i8] c"\00\00\00\00\00\04\00\01", [3 x ptr] [ptr @vf1i1, ptr @vf0i1, ptr @vf2i16], [0 x i8] zeroinitializer }, !type [[T8]]
 @vt2 = constant [3 x ptr] [
 ptr @vf1i1,
 ptr @vf0i1,
-ptr @vf2i32
+ptr @vf2i16
 ], !type !0
 
-; CHECK: [[VT3DATA:@[^ ]*]] = private constant { [5 x i8], [3 x ptr], [0 x i8] } { [5 x i8] c"\03\00\00\00\02", [3 x ptr] [ptr @vf0i1, ptr @vf1i1, ptr @vf3i32], [0 x i8] zeroinitializer }, align 1, !type [[T5:![0-9]+]]
+; CHECK: [[VT3DATA:@[^ ]*]] = private constant { [4 x i8], [3 x ptr], [0 x i8] } { [4 x i8] c"\00\05\00\02", [3 x ptr] [ptr @vf0i1, ptr @vf1i1, ptr @vf3i16], [0 x i8] zeroinitializer }, align 2, !type [[T5:![0-9]+]]
 @vt3 = constant [3 x ptr] [
 ptr @vf0i1,
 ptr @vf1i1,
-ptr @vf3i32
-], align 1, !type !0
+ptr @vf3i16
+], align 2, !type !0
 
-; CHECK: [[VT4DATA:@[^ ]*]] = private constant { [16 x i8], [3 x ptr], [0 x i8] } { [16 x i8] c"\00\00\00\00\00\00\00\00\00\00\00\04\00\00\00\01", [3 x ptr] [ptr @vf1i1, ptr @vf0i1, ptr @vf4i32], [0 x i8] zeroinitializer },  align 16, !type [[T16:![0-9]+]]
+; CHECK: [[VT4DATA:@[^ ]*]] = private constant { [16 x i8], [3 x ptr], [0 x i8] } { [16 x i8] c"\00\00\00\00\00\00\00\00\00\00\00\00\00\06\00\01", [3 x ptr] [ptr @vf1i1, ptr @vf0i1, ptr @vf4i16], [0 x i8] zeroinitializer },  align 16, !type [[T16:![0-9]+]]
 @vt4 = constant [3 x ptr] [
 ptr @vf1i1,
 ptr @vf0i1,
-ptr @vf4i32
+ptr @vf4i16
 ], align 16, !type !0
 
 ; CHECK: @vt5 = {{.*}}, !type [[T0:![0-9]+]]
@@ -38,10 +39,35 @@ ptr @__cxa_pure_virtual,
 ptr @__cxa_pure_virtual
 ], !type !0
 
+;; Test relative vtables
+; CHECK:      [[VT6RELDATA:@[^ ]*]] = private constant { [4 x i8], [3 x i32], [0 x i8] } { [4 x i8] c"\00\00\03\00", [3 x i32] [
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i16 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32)
+; CHECK-SAME: ], [0 x i8] zeroinitializer }, !type [[TREL:![0-9]+]]
+@vt6_rel = constant [3 x i32] [
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i16 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32)
+], !type !2
+
+; CHECK:      [[VT7RELDATA:@[^ ]*]] = private constant { [4 x i8], [3 x i32], [0 x i8] } { [4 x i8] c"\00\00\04\00", [3 x i32] [
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf2i16 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32)
+; CHECK-SAME: ], [0 x i8] zeroinitializer }, !type [[TREL]]
+@vt7_rel = constant [3 x i32] [
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf2i16 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32)
+], !type !2
+
 ; CHECK: @vt1 = alias [3 x ptr], getelementptr inbounds ({ [8 x i8], [3 x ptr], [0 x i8] }, ptr [[VT1DATA]], i32 0, i32 1)
 ; CHECK: @vt2 = alias [3 x ptr], getelementptr inbounds ({ [8 x i8], [3 x ptr], [0 x i8] }, ptr [[VT2DATA]], i32 0, i32 1)
-; CHECK: @vt3 = alias [3 x ptr], getelementptr inbounds ({ [5 x i8], [3 x ptr], [0 x i8] }, ptr [[VT3DATA]], i32 0, i32 1)
+; CHECK: @vt3 = alias [3 x ptr], getelementptr inbounds ({ [4 x i8], [3 x ptr], [0 x i8] }, ptr [[VT3DATA]], i32 0, i32 1)
 ; CHECK: @vt4 = alias [3 x ptr], getelementptr inbounds ({ [16 x i8], [3 x ptr], [0 x i8] }, ptr [[VT4DATA]], i32 0, i32 1)
+; CHECK: @vt6_rel = alias [3 x i32], getelementptr inbounds ({ [4 x i8], [3 x i32], [0 x i8] }, ptr [[VT6RELDATA]], i32 0, i32 1)
+; CHECK: @vt7_rel = alias [3 x i32], getelementptr inbounds ({ [4 x i8], [3 x i32], [0 x i8] }, ptr [[VT7RELDATA]], i32 0, i32 1)
 
 define i1 @vf0i1(ptr %this) readnone {
   ret i1 0
@@ -51,20 +77,20 @@ define i1 @vf1i1(ptr %this) readnone {
   ret i1 1
 }
 
-define i32 @vf1i32(ptr %this) readnone {
-  ret i32 1
+define i16 @vf1i16(ptr %this) readnone {
+  ret i16 3
 }
 
-define i32 @vf2i32(ptr %this) readnone {
-  ret i32 2
+define i16 @vf2i16(ptr %this) readnone {
+  ret i16 4
 }
 
-define i32 @vf3i32(ptr %this) readnone {
-  ret i32 3
+define i16 @vf3i16(ptr %this) readnone {
+  ret i16 5
 }
 
-define i32 @vf4i32(ptr %this) readnone {
-  ret i32 4
+define i16 @vf4i16(ptr %this) readnone {
+  ret i16 6
 }
 
 ; CHECK: define i1 @call1(
@@ -87,7 +113,7 @@ define i1 @call2(ptr %obj) {
   %vtable = load ptr, ptr %obj
   %p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid")
   call void @llvm.assume(i1 %p)
-  %fptrptr = getelementptr [3 x ptr], ptr %vtable, i32 0, i32 1
+  %fptrptr = getelementptr [3 x ptr], ptr %vtable, i16 0, i16 1
   %fptr = load ptr, ptr %fptrptr
   ; CHECK: [[VTGEP2:%[^ ]*]] = getelementptr i8, ptr %vtable, i32 -1
   ; CHECK: [[VTLOAD2:%[^ ]*]] = load i8, ptr [[VTGEP2]]
@@ -98,27 +124,68 @@ define i1 @call2(ptr %obj) {
   ret i1 %result
 }
 
-; CHECK: define i32 @call3(
-define i32 @call3(ptr %obj) {
+; CHECK: define i16 @call3(
+define i16 @call3(ptr %obj) {
   %vtable = load ptr, ptr %obj
   %p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid")
   call void @llvm.assume(i1 %p)
-  %fptrptr = getelementptr [3 x ptr], ptr %vtable, i32 0, i32 2
+  %fptrptr = getelementptr [3 x ptr], ptr %vtable, i16 0, i16 2
   %fptr = load ptr, ptr %fptrptr
-  ; CHECK: [[VTGEP3:%[^ ]*]] = getelementptr i8, ptr %vtable, i32 -5
-  ; CHECK: [[VTLOAD3:%[^ ]*]] = load i32, ptr [[VTGEP3]]
-  %result = call i32 %fptr(ptr %obj)
-  ; CHECK: ret i32 [[VTLOAD3]]
-  ret i32 %result
+  ; CHECK: [[VTGEP3:%[^ ]*]] = getelementptr i8, ptr %vtable, i32 -3
+  ; CHECK: [[VTLOAD3:%[^ ]*]] = load i16, ptr [[VTGEP3]]
+  %result = call i16 %fptr(ptr %obj)
+  ; CHECK: ret i16 [[VTLOAD3]]
+  ret i16 %result
+}
+
+; CHECK: define i1 @call1_rel(
+define i1 @call1_rel(ptr %obj) {
+  %vtable = load ptr, ptr %obj
+  %p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid3")
+  call void @llvm.assume(i1 %p)
+  %fptr = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
+  %result = call i1 %fptr(ptr %obj)
+  ret i1 %result
+  ; CHECK: [[RES:%[^ ]*]] = icmp eq ptr %vtable, @vt7_rel
+  ; CHECK: ret i1 [[RES]]
+}
+
+; CHECK: define i1 @call2_rel(
+define i1 @call2_rel(ptr %obj) {
+  %vtable = load ptr, ptr %obj
+  %p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid3")
+  call void @llvm.assume(i1 %p)
+  %fptr = call ptr @llvm.load.relative.i32(ptr %vtable, i32 4)
+  %result = call i1 %fptr(ptr %obj)
+  ret i1 %result
+  ; CHECK: [[RES:%[^ ]*]] = icmp eq ptr %vtable, @vt6_rel
+  ; CHECK: ret i1 [[RES]]
+}
+
+; CHECK: define i16 @call3_rel(
+define i16 @call3_rel(ptr %obj) {
+  %vtable = load ptr, ptr %obj
+  %p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid3")
+  call void @llvm.assume(i1 %p)
+  %fptr = call ptr @llvm.load.relative.i32(ptr %vtable, i32 8)
+  ; CHECK: [[VTGEP3:%[^ ]*]] = getelementptr i8, ptr %vtable, i32 -2
+  ; CHECK: [[VTLOAD3:%[^ ]*]] = load i16, ptr [[VTGEP3]]
+  %result = call i16 %fptr(ptr %obj)
+  ; CHECK: ret i16 [[VTLOAD3]]
+  ret i16 %result
 }
 
 declare i1 @llvm.type.test(ptr, metadata)
 declare void @llvm.assume(i1)
 declare void @__cxa_pure_virtual()
+declare ptr @llvm.load.relative.i32(ptr, i32)
 
 ; CHECK: [[T8]] = !{i32 8, !"typeid"}
-; CHECK: [[T5]] = !{i32 5, !"typeid"}
+; CHECK: [[T5]] = !{i32 4, !"typeid"}
 ; CHECK: [[T16]] = !{i32 16, !"typeid"}
 ; CHECK: [[T0]] = !{i32 0, !"typeid"}
+; CHECK: [[TREL]] = !{i32 4, !"typeid3"}
 
 !0 = !{i32 0, !"typeid"}
+!1 = !{i32 0, !"typeid2"}
+!2 = !{i32 0, !"typeid3"}
diff --git a/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll b/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll
index fb8b6de003fdd..40adabbe38400 100644
--- a/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll
+++ b/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-check.ll
@@ -7,13 +7,16 @@
 ; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility -pass-remarks=wholeprogramdevirt -wholeprogramdevirt-skip=vf0i1 %s 2>&1 | FileCheck %s --check-prefix=SKIP
 ; We have two set of call targets {vf0i1, vf1i1} and {vf1i32, vf2i32, vf3i32, vf4i32}.
 ; The command below prevents both of them from devirtualization.
-; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility -pass-remarks=wholeprogramdevirt -wholeprogramdevirt-skip=vf0i1,vf1i32 %s 2>&1 | FileCheck %s --check-prefix=SKIP-ALL
+; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility -pass-remarks=wholeprogramdevirt -wholeprogramdevirt-skip=vf0i1,vf1i32,vf3i32 %s 2>&1 | FileCheck %s --check-prefix=SKIP-ALL
 ; Check wildcard
 ; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility -pass-remarks=wholeprogramdevirt -wholeprogramdevirt-skip=vf?i1 %s 2>&1 | FileCheck %s --check-prefix=SKIP
 
 target datalayout = "e-p:64:64"
 target triple = "x86_64-unknown-linux-gnu"
 
+; CHECK: remark: <unknown>:0:0: unique-ret-val: devirtualized a call to vf0i1
+; CHECK: remark: <unknown>:0:0: unique-ret-val: devirtualized a call to vf1i1
+; CHECK: remark: <unknown>:0:0: virtual-const-prop: devirtualized a call to vf3i32
 ; CHECK: remark: <unknown>:0:0: virtual-const-prop-1-bit: devirtualized a call to vf0i1
 ; CHECK: remark: <unknown>:0:0: virtual-const-prop-1-bit: devirtualized a call to vf1i1
 ; CHECK: remark: <unknown>:0:0: virtual-const-prop: devirtualized a call to vf1i32
@@ -69,10 +72,35 @@ ptr @__cxa_pure_virtual,
 ptr @__cxa_pure_virtual
 ], !type !0
 
+;; Test relative vtables
+; CHECK:      [[VT6RELDATA:@[^ ]*]] = private constant { [4 x i8], [3 x i32], [0 x i8] } { [4 x i8] c"\03\00\00\00", [3 x i32] [
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf3i32 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32)
+; CHECK-SAME: ], [0 x i8] zeroinitializer }, !type [[TREL:![0-9]+]]
+@vt6_rel = constant [3 x i32] [
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf3i32 to i64), i64 ptrtoint (ptr @vt6_rel to i64)) to i32)
+], !type !1
+
+; CHECK:      [[VT7RELDATA:@[^ ]*]] = private constant { [4 x i8], [3 x i32], [0 x i8] } { [4 x i8] c"\04\00\00\00", [3 x i32] [
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf4i32 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32)
+; CHECK-SAME: ], [0 x i8] zeroinitializer }, !type [[TREL]]
+@vt7_rel = constant [3 x i32] [
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf4i32 to i64), i64 ptrtoint (ptr @vt7_rel to i64)) to i32)
+], !type !1
+
 ; CHECK: @vt1 = alias [3 x ptr], getelementptr inbounds ({ [8 x i8], [3 x ptr], [0 x i8] }, ptr [[VT1DATA]], i32 0, i32 1)
 ; CHECK: @vt2 = alias [3 x ptr], getelementptr inbounds ({ [8 x i8], [3 x ptr], [0 x i8] }, ptr [[VT2DATA]], i32 0, i32 1)
 ; CHECK: @vt3 = alias [3 x ptr], getelementptr inbounds ({ [8 x i8], [3 x ptr], [0 x i8] }, ptr [[VT3DATA]], i32 0, i32 1)
 ; CHECK: @vt4 = alias [3 x ptr], getelementptr inbounds ({ [8 x i8], [3 x ptr], [0 x i8] }, ptr [[VT4DATA]], i32 0, i32 1)
+; CHECK: @vt6_rel = alias [3 x i32], getelementptr inbounds ({ [4 x i8], [3 x i32], [0 x i8] }, ptr [[VT6RELDATA]], i32 0, i32 1)
+; CHECK: @vt7_rel = alias [3 x i32], getelementptr inbounds ({ [4 x i8], [3 x i32], [0 x i8] }, ptr [[VT7RELDATA]], i32 0, i32 1)
 
 define i1 @vf0i1(ptr %this) readnone {
   ret i1 0
@@ -144,15 +172,56 @@ define i32 @call3(ptr %obj) {
   ret i32 %result
 }
 
+; CHECK: define i1 @call1_rel(
+define i1 @call1_rel(ptr %obj) {
+  %vtable = load ptr, ptr %obj
+  %p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid2")
+  call void @llvm.assume(i1 %p)
+  %fptr = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
+  %result = call i1 %fptr(ptr %obj)
+  ret i1 %result
+  ; CHECK: [[RES:%[^ ]*]] = icmp eq ptr %vtable, @vt7_rel
+  ; CHECK: ret i1 [[RES]]
+}
+
+; CHECK: define i1 @call2_rel(
+define i1 @call2_rel(ptr %obj) {
+  %vtable = load ptr, ptr %obj
+  %p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid2")
+  call void @llvm.assume(i1 %p)
+  %fptr = call ptr @llvm.load.relative.i32(ptr %vtable, i32 4)
+  %result = call i1 %fptr(ptr %obj)
+  ret i1 %result
+  ; CHECK: [[RES:%[^ ]*]] = icmp eq ptr %vtable, @vt6_rel
+  ; CHECK: ret i1 [[RES]]
+}
+
+; CHECK: define i32 @call3_rel(
+define i32 @call3_rel(ptr %obj) {
+  %vtable = load ptr, ptr %obj
+  %p = call i1 @llvm.type.test(ptr %vtable, metadata !"typeid2")
+  call void @llvm.assume(i1 %p)
+  %fptr = call ptr @llvm.load.relative.i32(ptr %vtable, i32 8)
+  ; CHECK: [[VTGEP3:%[^ ]*]] = getelementptr i8, ptr %vtable, i32 -4
+  ; CHECK: [[VTLOAD3:%[^ ]*]] = load i32, ptr [[VTGEP3]]
+  %result = call i32 %fptr(ptr %obj)
+  ; CHECK: ret i32 [[VTLOAD3]]
+  ret i32 %result
+}
+
 declare {ptr, i1} @llvm.type.checked.load(ptr, i32, metadata)
 declare void @llvm.assume(i1)
 declare void @__cxa_pure_virtual()
+declare ptr @llvm.load.relative.i32(ptr, i32)
 
 ; CHECK: [[T8]] = !{i32 8, !"typeid"}
 ; CHECK: [[T0]] = !{i32 0, !"typeid"}
+; CHECK: [[TREL]] = !{i32 4, !"typeid2"}
 
 !0 = !{i32 0, !"typeid"}
+!1 = !{i32 0, !"typeid2"}
 
 ; CHECK: 6 wholeprogramdevirt - Number of whole program devirtualization targets
-; CHECK: 1 wholeprogramdevirt - Number of virtual constant propagations
+; CHECK: 2 wholeprogramdevirt - Number of unique return value optimizations
+; CHECK: 2 wholeprogramdevirt - Number of virtual constant propagations
 ; CHECK: 2 wholeprogramdevirt - Number of 1 bit virtual constant propagations
diff --git a/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-end.ll b/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-end.ll
index a28c27079f8de..e0f9b6dbe2ac5 100644
--- a/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-end.ll
+++ b/llvm/test/Transforms/WholeProgramDevirt/virtual-const-prop-end.ll
@@ -1,9 +1,8 @@
 ; RUN: opt -S -passes=wholeprogramdevirt -whole-program-visibility %s | FileCheck %s
 
 target datalayout = "e-p:64:64"
-target triple = "x86_64-unknown-linux-gnu"
 
-; CHECK: [[VT1DATA:@[^ ]*]] = private constant { [0 x i8], [4 x ptr], [5 x i8] } { [0 x i8] zeroinitializer, [4 x ptr] [ptr null, ptr @vf0i1, ptr @vf1i1, ptr @vf1i32], [5 x i8] c"\02\01\00\00\00" }, !type [[T8:![0-9]+]]
+; CHECK: [[VT1DATA:@[^ ]*]] = private constant { [0 x i8], [4 x ptr], [5 x i8] } { [0 x i8] zeroinitializer, [4 x ptr] [ptr null, ptr @vf0i1, ptr @vf1i1, ptr @vf1i32], [5 x i8] c"\02\03\00\00\00" }, !type [[T8:![0-9]+]]
 @vt1 = constant [4 x ptr] [
 ptr null,
 ptr @vf0i1,
@@ -11,14 +10,14 @@ ptr @vf1i1,
 ptr @vf1i32
 ], !type !1
 
-; CHECK: [[VT2DATA:@[^ ]*]] = private constant { [0 x i8], [3 x ptr], [5 x i8] } { [0 x i8] zeroinitializer, [3 x ptr] [ptr @vf1i1, ptr @vf0i1, ptr @vf2i32], [5 x i8] c"\01\02\00\00\00" }, !type [[T0:![0-9]+]]
+; CHECK: [[VT2DATA:@[^ ]*]] = private constant { [0 x i8], [3 x ptr], [5 x i8] } { [0 x i8] zeroinitializer, [3 x ptr] [ptr @vf1i1, ptr @vf0i1, ptr @vf2i32], [5 x i8] c"\01\04\00\00\00" }, !type [[T0:![0-9]+]]
 @vt2 = constant [3 x ptr] [
 ptr @vf1i1,
 ptr @vf0i1,
 ptr @vf2i32
 ], !type !0
 
-; CHECK: [[VT3DATA:@[^ ]*]] = private constant { [0 x i8], [4 x ptr], [5 x i8] } { [0 x i8] zeroinitializer, [4 x ptr] [ptr null, ptr @vf0i1, ptr @vf1i1, ptr @vf3i32], [5 x i8] c"\02\03\00\00\00" }, !type [[T8]]
+; CHECK: [[VT3DATA:@[^ ]*]] = private constant { [0 x i8], [4 x ptr], [5 x i8] } { [0 x i8] zeroinitializer, [4 x ptr] [ptr null, ptr @vf0i1, ptr @vf1i1, ptr @vf3i32], [5 x i8] c"\02\05\00\00\00" }, !type [[T8]]
 @vt3 = constant [4 x ptr] [
 ptr null,
 ptr @vf0i1,
@@ -26,13 +25,38 @@ ptr @vf1i1,
 ptr @vf3i32
 ], !type !1
 
-; CHECK: [[VT4DATA:@[^ ]*]] = private constant { [0 x i8], [3 x ptr], [5 x i8] } { [0 x i8] zeroinitializer, [3 x ptr] [ptr @vf1i1, ptr @vf0i1, ptr @vf4i32], [5 x i8] c"\01\04\00\00\00" }, !type [[T0]]
+; CHECK: [[VT4DATA:@[^ ]*]] = private constant { [0 x i8], [3 x ptr], [5 x i8] } { [0 x i8] zeroinitializer, [3 x ptr] [ptr @vf1i1, ptr @vf0i1, ptr @vf4i32], [5 x i8] c"\01\06\00\00\00" }, !type [[T0]]
 @vt4 = constant [3 x ptr] [
 ptr @vf1i1,
 ptr @vf0i1,
 ptr @vf4i32
 ], !type !0
 
+;; Test relative vtables
+; CHECK:      [[VT6RELDATA:@[^ ]*]] = private constant { [0 x i8], [4 x i32], [4 x i8] } { [0 x i8] zeroinitializer, [4 x i32] [
+; CHECK-SAME:     i32 0,
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt5_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt5_rel to i64)) to i32),
+; CHECK-SAME:     i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf2i32 to i64), i64 ptrtoint (ptr @vt5_rel to i64)) to i32)
+; CHECK-SAME: ], [4 x i8] c"\04\00\00\00" }, !type [[TREL:![0-9]+]]
+@vt5_rel = constant [4 x i32] [
+i32 zeroinitializer,
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf1i1 to i64), i64 ptrtoint (ptr @vt5_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf0i1 to i64), i64 ptrtoint (ptr @vt5_rel to i64)) to i32),
+i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @vf2i32 to i64), i64 ptrtoint (ptr @vt5_rel to i64)) to i32)
+], !type !3
+
+; CHECK:      [[VT7RELDATA:@[^ ...
[truncated]

Comment on lines 15 to 16
;; This represents a normal vtable using the default ABI alignments.
;; For this test, the pointers are 32-bit aligned.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What property are you testing? Will the test be brittle, due to changes in the textual IR format? Splitting the individual aspects you're checking w/ CHECK-SAME may make it easier to follow what's being tested.

Ditto for the other checks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The intent of this is to show that the current WPD patches allow for this to result in unaligned loads. The things to note in this patch are

  1. All the constants from the 3rd field propgating an i32 placed at an offset of -6 from the vtable. Since this vtable is 32-bit aligned according to the datalayout, this could result in an unaligned load.
  2. The call instruction in call3 is replaced with a GEP + load.

The significant deltas in the followup patch are (1) this constant is no longer propagated since its alignment is greater than at most the alignment of any of its vtables (namely the align 1 vtable) and (2) the GEP + load is not added and we still have a function call.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, those are fine properties to check, but the CHECK lines should reflect the property you're checking, right? There seems to be quite a bit of the IR here that you don't care about and that could change w/o impacting one of the properties you're trying to check here.

My point is these long lines that try to match the IR perfectly seem a bit brittle, and since you check everything in a single line it isn't clear what property would have broken.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Split them into separate lines to hopefully make it easier to read

@PiJoules PiJoules force-pushed the wpd-alignment-precommit-tests branch 2 times, most recently from 296dde2 to 618f8e8 Compare May 8, 2025 21:58
@PiJoules
Copy link
Contributor Author

PiJoules commented May 8, 2025

Split up some of the checks into separate lines

@PiJoules PiJoules requested a review from ilovepi May 9, 2025 17:20
@PiJoules PiJoules force-pushed the wpd-alignment-precommit-tests branch from 618f8e8 to 8386890 Compare May 9, 2025 21:13
…bles

This is a patch with precommitted tests to make
llvm#136630 easier to review. The
`virtual-const-prop-small-alignment-*` tests check the output when the
loaded int alignment is less than the vtable alignment.

This also changes some constants to make it easier to differentiate
between propagated values in vtables.
@PiJoules PiJoules force-pushed the wpd-alignment-precommit-tests branch from 8386890 to 7dfd54b Compare May 9, 2025 21:15
@PiJoules
Copy link
Contributor Author

PiJoules commented May 9, 2025

@ilovepi I trimmed some of the CHECKs to keep only the relevant bits for the new tests. How does this look to you?

Copy link
Contributor

@ilovepi ilovepi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, modulo the tests passing.

@PiJoules PiJoules merged commit 649b799 into llvm:main May 13, 2025
9 of 11 checks passed
@PiJoules PiJoules deleted the wpd-alignment-precommit-tests branch May 13, 2025 18:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants