[Mlir-commits] [clang] [llvm] [mlir] [CIR][WIP] support relative vtable (PR #195025)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Tue May 12 07:56:35 PDT 2026


https://github.com/xiongzile updated https://github.com/llvm/llvm-project/pull/195025

>From bc30eea7f976d182909bea07e4d7a1de7079e7ef Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile at bytedance.com>
Date: Thu, 30 Apr 2026 15:14:45 +0800
Subject: [PATCH 1/5] [CIR] Relative vtable layout for virtual base offset

---
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 946739d4e1702..f56f291c70772 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -2057,8 +2057,10 @@ mlir::Value CIRGenItaniumCXXABI::getVirtualBaseClassOffset(
 
   mlir::Value vbaseOffset;
   if (cgm.getLangOpts().RelativeCXXABIVTables) {
-    assert(!cir::MissingFeatures::vtableRelativeLayout());
-    cgm.errorNYI(loc, "getVirtualBaseClassOffset: relative layout");
+    mlir::Value offsetPtr = builder.createBitcast(vbaseOffsetPtr, builder.getPointerTo(cgm.sInt32Ty));
+    vbaseOffset = cgf.getBuilder().createLoad(
+        loc, Address(offsetPtr, cgm.sInt32Ty,
+                     CharUnits::fromQuantity(4))); // vbase.offset
   } else {
     mlir::Value offsetPtr = builder.createBitcast(
         vbaseOffsetPtr, builder.getPointerTo(cgm.ptrDiffTy));

>From c22a4d7db8f00d90c4900c85402a5f24f602a611 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile99 at gmail.com>
Date: Thu, 30 Apr 2026 17:24:00 +0800
Subject: [PATCH 2/5] [CIR] add test for getVirtualBaseOffsetOffset:
 RelativeCXXABIVTables

---
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp |  3 +-
 .../CodeGenCXX/vtable-relative-baseoffset.cpp | 65 +++++++++++++++++++
 2 files changed, 67 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CIR/CodeGenCXX/vtable-relative-baseoffset.cpp

diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index f56f291c70772..b6e6bd4857c9b 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -2057,7 +2057,8 @@ mlir::Value CIRGenItaniumCXXABI::getVirtualBaseClassOffset(
 
   mlir::Value vbaseOffset;
   if (cgm.getLangOpts().RelativeCXXABIVTables) {
-    mlir::Value offsetPtr = builder.createBitcast(vbaseOffsetPtr, builder.getPointerTo(cgm.sInt32Ty));
+    mlir::Value offsetPtr = builder.createBitcast(
+        vbaseOffsetPtr, builder.getPointerTo(cgm.sInt32Ty));
     vbaseOffset = cgf.getBuilder().createLoad(
         loc, Address(offsetPtr, cgm.sInt32Ty,
                      CharUnits::fromQuantity(4))); // vbase.offset
diff --git a/clang/test/CIR/CodeGenCXX/vtable-relative-baseoffset.cpp b/clang/test/CIR/CodeGenCXX/vtable-relative-baseoffset.cpp
new file mode 100644
index 0000000000000..b58cf0622781c
--- /dev/null
+++ b/clang/test/CIR/CodeGenCXX/vtable-relative-baseoffset.cpp
@@ -0,0 +1,65 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexperimental-relative-c++-abi-vtables -fclangir -emit-cir %s -o - | FileCheck --check-prefix=CIR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexperimental-relative-c++-abi-vtables -fclangir -emit-llvm %s -o - | FileCheck --check-prefix=LLVM %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fexperimental-relative-c++-abi-vtables -emit-llvm %s -o - | FileCheck --check-prefix=OGCG %s
+
+// vbase-offset.cpp
+
+struct V {
+  int x;
+};
+
+struct A : virtual V {
+};
+
+struct B : A {
+};
+// CIR-LABEL: @_Z1fP1B(
+// CIR:         [[P:%.*]] = cir.load align(8) {{%.*}} : !cir.ptr<!cir.ptr<!rec_B>>, !cir.ptr<!rec_B>
+// CIR-NEXT:    [[VPTR_PTR:%.*]] = cir.vtable.get_vptr [[P]] : !cir.ptr<!rec_B> -> !cir.ptr<!cir.vptr>
+// CIR-NEXT:    [[VTABLE:%.*]] = cir.load align(8) [[VPTR_PTR]] : !cir.ptr<!cir.vptr>, !cir.vptr
+// CIR-NEXT:    [[VTABLE_BYTES:%.*]] = cir.cast bitcast [[VTABLE]] : !cir.vptr -> !cir.ptr<!u8i>
+// CIR-NEXT:    [[VBASE_OFFSET_SLOT_OFFSET:%.*]] = cir.const #cir.int<-12> : !s64i
+// CIR-NEXT:    [[VBASE_OFFSET_SLOT:%.*]] = cir.ptr_stride [[VTABLE_BYTES]], [[VBASE_OFFSET_SLOT_OFFSET]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
+// CIR-NEXT:    [[VBASE_OFFSET_PTR:%.*]] = cir.cast bitcast [[VBASE_OFFSET_SLOT]] : !cir.ptr<!u8i> -> !cir.ptr<!s32i>
+// CIR-NEXT:    [[VBASE_OFFSET:%.*]] = cir.load align(4) [[VBASE_OFFSET_PTR]] : !cir.ptr<!s32i>, !s32i
+// CIR-NEXT:    [[P_BYTES:%.*]] = cir.cast bitcast [[P]] : !cir.ptr<!rec_B> -> !cir.ptr<!u8i>
+// CIR-NEXT:    [[VBASE_PTR_BYTES:%.*]] = cir.ptr_stride [[P_BYTES]], [[VBASE_OFFSET]] : (!cir.ptr<!u8i>, !s32i) -> !cir.ptr<!u8i>
+// CIR-NEXT:    [[VBASE_PTR_B:%.*]] = cir.cast bitcast [[VBASE_PTR_BYTES]] : !cir.ptr<!u8i> -> !cir.ptr<!rec_B>
+// CIR-NEXT:    [[VBASE_PTR:%.*]] = cir.cast bitcast [[VBASE_PTR_B]] : !cir.ptr<!rec_B> -> !cir.ptr<!rec_V>
+// CIR-NEXT:    [[X_PTR:%.*]] = cir.get_member [[VBASE_PTR]][0] {name = "x"} : !cir.ptr<!rec_V> -> !cir.ptr<!s32i>
+// CIR-NEXT:    [[X:%.*]] = cir.load align(4) [[X_PTR]] : !cir.ptr<!s32i>, !s32i
+//
+// LLVM-LABEL: @_Z1fP1B(
+// LLVM:         [[P:%.*]] = load ptr, ptr {{.*}}, align 8
+// LLVM-NEXT:    [[VTABLE:%.*]] = load ptr, ptr [[P]], align 8
+// LLVM-NEXT:    [[VBASE_OFFSET_PTR:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 -12
+// LLVM-NEXT:    [[VBASE_OFFSET_I32:%.*]] = load i32, ptr [[VBASE_OFFSET_PTR]], align 4
+// LLVM-NEXT:    [[VBASE_OFFSET:%.*]] = sext i32 [[VBASE_OFFSET_I32]] to i64
+// LLVM-NEXT:    [[VBASE_PTR:%.*]] = getelementptr i8, ptr [[P]], i64 [[VBASE_OFFSET]]
+// LLVM-NEXT:    [[X_PTR:%.*]] = getelementptr inbounds nuw [[STRUCT_V:%.*]], ptr [[VBASE_PTR]], i32 0, i32 0
+// LLVM-NEXT:    [[X:%.*]] = load i32, ptr [[X_PTR]], align 4
+// LLVM:         ret i32
+//
+// OGCG-LABEL: define dso_local noundef i32 @_Z1fP1B(
+// OGCG-SAME: ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] {
+// OGCG-NEXT:  [[ENTRY:.*]]:
+// OGCG-NEXT:    [[P_ADDR:%.*]] = alloca ptr, align 8
+// OGCG-NEXT:    store ptr [[P]], ptr [[P_ADDR]], align 8
+// OGCG-NEXT:    [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8
+// OGCG-NEXT:    [[TMP1:%.*]] = icmp eq ptr [[TMP0]], null
+// OGCG-NEXT:    br i1 [[TMP1]], label %[[CAST_END:.*]], label %[[CAST_NOTNULL:.*]]
+// OGCG:       [[CAST_NOTNULL]]:
+// OGCG-NEXT:    [[VTABLE:%.*]] = load ptr, ptr [[TMP0]], align 8
+// OGCG-NEXT:    [[VBASE_OFFSET_PTR:%.*]] = getelementptr i8, ptr [[VTABLE]], i64 -12
+// OGCG-NEXT:    [[VBASE_OFFSET:%.*]] = load i32, ptr [[VBASE_OFFSET_PTR]], align 4
+// OGCG-NEXT:    [[ADD_PTR:%.*]] = getelementptr inbounds i8, ptr [[TMP0]], i32 [[VBASE_OFFSET]]
+// OGCG-NEXT:    br label %[[CAST_END]]
+// OGCG:       [[CAST_END]]:
+// OGCG-NEXT:    [[CAST_RESULT:%.*]] = phi ptr [ [[ADD_PTR]], %[[CAST_NOTNULL]] ], [ null, %[[ENTRY]] ]
+// OGCG-NEXT:    [[X:%.*]] = getelementptr inbounds nuw [[STRUCT_V:%.*]], ptr [[CAST_RESULT]], i32 0, i32 0
+// OGCG-NEXT:    [[TMP2:%.*]] = load i32, ptr [[X]], align 4
+// OGCG-NEXT:    ret i32 [[TMP2]]
+//
+int f(B *p) {
+  return static_cast<V *>(p)->x;
+}

>From d36b220dfda282d7669f395ca127d8fade6e66f3 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile99 at gmail.com>
Date: Thu, 7 May 2026 20:06:26 +0800
Subject: [PATCH 3/5] [CIR] add support for getVirtualFunctionPointer

---
 1.cpp                                         |  73 +++++
 cir-ll.ll                                     | 212 ++++++++++++++
 cir.ll                                        | 215 +++++++++++++++
 clang/include/clang/CIR/Dialect/IR/CIROps.td  |  54 ++++
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp |  18 +-
 clang/lib/CIR/CodeGen/CIRGenVTables.cpp       |  38 ++-
 clang/lib/CIR/Dialect/IR/CIRAttrs.cpp         |   8 +-
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |  22 ++
 ll.ll                                         | 250 +++++++++++++++++
 rel-ll.ll                                     | 259 ++++++++++++++++++
 10 files changed, 1140 insertions(+), 9 deletions(-)
 create mode 100644 1.cpp
 create mode 100644 cir-ll.ll
 create mode 100644 cir.ll
 create mode 100644 ll.ll
 create mode 100644 rel-ll.ll

diff --git a/1.cpp b/1.cpp
new file mode 100644
index 0000000000000..d736d6f48ab03
--- /dev/null
+++ b/1.cpp
@@ -0,0 +1,73 @@
+struct A {
+  virtual void f();
+  int a;
+};
+
+struct B {
+  virtual void g();
+  int b;
+};
+
+struct C : A, B {
+  void f() override;
+  void g() override;
+  int c;
+};
+
+void A::f() {}
+void B::g() {}
+void C::f() {}
+void C::g() {}
+
+// 1. 通过 A* 做虚调用。
+//    这里应该会查 A-subobject 的 vptr,然后从 vtable slot 里取 f。
+void call_through_A(A *pa) {
+  pa->f();
+}
+
+// 2. 通过 B* 做虚调用。
+//    这里应该会查 B-subobject 的 vptr,然后从 secondary vtable slot 里取 g。
+//    如果实际对象是 C,slot 里可能是 thunk。
+void call_through_B(B *pb) {
+  pb->g();
+}
+
+// 3. 已知静态类型是 C*,但调用 virtual 函数。
+//    C++ 语义上仍然是 virtual call,除非编译器能 devirtualize。
+//    在 -O0 / CIR 阶段通常更容易看到 vptr load。
+void call_through_C(C *pc) {
+  pc->f();
+  pc->g();
+}
+
+// 4. 非虚的限定调用。
+//    这里不会查 vptr,应该直接 call C::f / C::g。
+void direct_qualified_call(C *pc) {
+  pc->C::f();
+  pc->C::g();
+}
+
+// 5. 从 C* 转成 A* / B*。
+//    A 是 primary base,一般 offset 是 0。
+//    B 是 secondary base,一般需要 this + 16 之类的调整。
+void base_casts(C *pc) {
+  A *pa = pc;
+  B *pb = pc;
+
+  pa->f();
+  pb->g();
+}
+
+// 6. 栈上构造 C。
+//    这里适合观察 constructor / vptr 初始化。
+//    如果 CIR 暂时没有完整 ctor lowering,也至少能暴露相关路径。
+void construct_and_call() {
+  C obj;
+  obj.f();
+  obj.g();
+
+  A *pa = &obj;
+  B *pb = &obj;
+  pa->f();
+  pb->g();
+}
\ No newline at end of file
diff --git a/cir-ll.ll b/cir-ll.ll
new file mode 100644
index 0000000000000..67b701207dcb9
--- /dev/null
+++ b/cir-ll.ll
@@ -0,0 +1,212 @@
+; ModuleID = '/data00/home/xiongzile/workspace/llvm-worktree/llvm-project/1.cpp'
+source_filename = "/data00/home/xiongzile/workspace/llvm-worktree/llvm-project/1.cpp"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+%struct.C = type { %struct.A.base, [4 x i8], %struct.B.base, i32 }
+%struct.A.base = type <{ ptr, i32 }>
+%struct.B.base = type <{ ptr, i32 }>
+
+ at _ZTV1A = global { [3 x i32] } { [3 x i32] [i32 0, i32 ptrtoint (ptr @_ZTI1A to i32), i32 ptrtoint (ptr @_ZN1A1fEv to i32)] }, align 4
+ at _ZTVN10__cxxabiv117__class_type_infoE = external global ptr, align 8
+ at _ZTS1A = global [3 x i8] c"1A\00", align 1
+ at _ZTI1A = constant { i32, ptr } { i32 ptrtoint (ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 16) to i32), ptr @_ZTS1A }, align 8
+ at _ZTV1B = global { [3 x i32] } { [3 x i32] [i32 0, i32 ptrtoint (ptr @_ZTI1B to i32), i32 ptrtoint (ptr @_ZN1B1gEv to i32)] }, align 4
+ at _ZTS1B = global [3 x i8] c"1B\00", align 1
+ at _ZTI1B = constant { i32, ptr } { i32 ptrtoint (ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 16) to i32), ptr @_ZTS1B }, align 8
+ at _ZTV1C = global { [4 x i32], [3 x i32] } { [4 x i32] [i32 0, i32 ptrtoint (ptr @_ZTI1C to i32), i32 ptrtoint (ptr @_ZN1C1fEv to i32), i32 ptrtoint (ptr @_ZN1C1gEv to i32)], [3 x i32] [i32 -16, i32 ptrtoint (ptr @_ZTI1C to i32), i32 ptrtoint (ptr @_ZThn16_N1C1gEv to i32)] }, align 4
+ at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global ptr, align 8
+ at _ZTS1C = global [3 x i8] c"1C\00", align 1
+ at _ZTI1C = constant { i32, ptr, i32, i32, ptr, i64, ptr, i64 } { i32 ptrtoint (ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 16) to i32), ptr @_ZTS1C, i32 0, i32 2, ptr @_ZTI1A, i64 2, ptr @_ZTI1B, i64 4098 }, align 8
+
+; Function Attrs: noinline
+define dso_local void @_ZN1A1fEv(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_ZN1B1gEv(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_ZThn16_N1C1gEv(ptr noundef %0) #1 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  %4 = getelementptr i8, ptr %3, i64 -16
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %4)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z14call_through_AP1A(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  %4 = load ptr, ptr %3, align 8
+  %5 = call ptr @llvm.load.relative.i32(ptr %4, i32 0)
+  call void %5(ptr noundef nonnull align 8 dereferenceable(12) %3)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z14call_through_BP1B(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  %4 = load ptr, ptr %3, align 8
+  %5 = call ptr @llvm.load.relative.i32(ptr %4, i32 0)
+  call void %5(ptr noundef nonnull align 8 dereferenceable(12) %3)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z14call_through_CP1C(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  %4 = load ptr, ptr %3, align 8
+  %5 = call ptr @llvm.load.relative.i32(ptr %4, i32 0)
+  call void %5(ptr noundef nonnull align 8 dereferenceable(32) %3)
+  %6 = load ptr, ptr %2, align 8
+  %7 = load ptr, ptr %6, align 8
+  %8 = call ptr @llvm.load.relative.i32(ptr %7, i32 4)
+  call void %8(ptr noundef nonnull align 8 dereferenceable(32) %6)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z21direct_qualified_callP1C(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %3)
+  %4 = load ptr, ptr %2, align 8
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %4)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z10base_castsP1C(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  %3 = alloca ptr, i64 1, align 8
+  %4 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %5 = load ptr, ptr %2, align 8
+  store ptr %5, ptr %3, align 8
+  %6 = load ptr, ptr %2, align 8
+  %7 = icmp eq ptr %6, null
+  %8 = getelementptr i8, ptr %6, i32 16
+  %9 = select i1 %7, ptr %6, ptr %8
+  store ptr %9, ptr %4, align 8
+  %10 = load ptr, ptr %3, align 8
+  %11 = load ptr, ptr %10, align 8
+  %12 = call ptr @llvm.load.relative.i32(ptr %11, i32 0)
+  call void %12(ptr noundef nonnull align 8 dereferenceable(12) %10)
+  %13 = load ptr, ptr %4, align 8
+  %14 = load ptr, ptr %13, align 8
+  %15 = call ptr @llvm.load.relative.i32(ptr %14, i32 0)
+  call void %15(ptr noundef nonnull align 8 dereferenceable(12) %13)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z18construct_and_callv() #0 {
+  %1 = alloca %struct.C, i64 1, align 8
+  %2 = alloca ptr, i64 1, align 8
+  %3 = alloca ptr, i64 1, align 8
+  call void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %1) #3
+  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
+  store ptr %1, ptr %2, align 8
+  %4 = icmp eq ptr %1, null
+  %5 = getelementptr i8, ptr %1, i32 16
+  %6 = select i1 %4, ptr %1, ptr %5
+  store ptr %6, ptr %3, align 8
+  %7 = load ptr, ptr %2, align 8
+  %8 = load ptr, ptr %7, align 8
+  %9 = call ptr @llvm.load.relative.i32(ptr %8, i32 0)
+  call void %9(ptr noundef nonnull align 8 dereferenceable(12) %7)
+  %10 = load ptr, ptr %3, align 8
+  %11 = load ptr, ptr %10, align 8
+  %12 = call ptr @llvm.load.relative.i32(ptr %11, i32 0)
+  call void %12(ptr noundef nonnull align 8 dereferenceable(12) %10)
+  ret void
+}
+
+; Function Attrs: noinline
+define linkonce_odr void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1A, i64 8), ptr %3, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define linkonce_odr void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1B, i64 8), ptr %3, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define linkonce_odr void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %3) #3
+  %4 = getelementptr i8, ptr %3, i32 16
+  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %4) #3
+  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 8), ptr %3, align 8
+  %5 = getelementptr i8, ptr %3, i32 16
+  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 24), ptr %5, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define linkonce_odr void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  call void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %3) #3
+  ret void
+}
+
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
+declare ptr @llvm.load.relative.i32(ptr, i32) #2
+
+attributes #0 = { noinline "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+attributes #1 = { noinline }
+attributes #2 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) }
+attributes #3 = { nounwind }
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/cir.ll b/cir.ll
new file mode 100644
index 0000000000000..48f99824f98a0
--- /dev/null
+++ b/cir.ll
@@ -0,0 +1,215 @@
+; ModuleID = '/data00/home/xiongzile/workspace/llvm-worktree/llvm-project/1.cpp'
+source_filename = "/data00/home/xiongzile/workspace/llvm-worktree/llvm-project/1.cpp"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+%struct.C = type { %struct.A.base, [4 x i8], %struct.B.base, i32 }
+%struct.A.base = type <{ ptr, i32 }>
+%struct.B.base = type <{ ptr, i32 }>
+
+ at _ZTV1A = global { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1A, ptr @_ZN1A1fEv] }, align 8
+ at _ZTVN10__cxxabiv117__class_type_infoE = external global ptr, align 8
+ at _ZTS1A = global [3 x i8] c"1A\00", align 1
+ at _ZTI1A = constant { ptr, ptr } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 16), ptr @_ZTS1A }, align 8
+ at _ZTV1B = global { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1B1gEv] }, align 8
+ at _ZTS1B = global [3 x i8] c"1B\00", align 1
+ at _ZTI1B = constant { ptr, ptr } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 16), ptr @_ZTS1B }, align 8
+ at _ZTV1C = global { [4 x ptr], [3 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1C, ptr @_ZN1C1fEv, ptr @_ZN1C1gEv], [3 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr @_ZTI1C, ptr @_ZThn16_N1C1gEv] }, align 8
+ at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global ptr, align 8
+ at _ZTS1C = global [3 x i8] c"1C\00", align 1
+ at _ZTI1C = constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 16), ptr @_ZTS1C, i32 0, i32 2, ptr @_ZTI1A, i64 2, ptr @_ZTI1B, i64 4098 }, align 8
+
+; Function Attrs: noinline
+define dso_local void @_ZN1A1fEv(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_ZN1B1gEv(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_ZThn16_N1C1gEv(ptr noundef %0) #1 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  %4 = getelementptr i8, ptr %3, i64 -16
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %4)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z14call_through_AP1A(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  %4 = load ptr, ptr %3, align 8
+  %5 = getelementptr inbounds ptr, ptr %4, i32 0
+  %6 = load ptr, ptr %5, align 8
+  call void %6(ptr noundef nonnull align 8 dereferenceable(12) %3)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z14call_through_BP1B(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  %4 = load ptr, ptr %3, align 8
+  %5 = getelementptr inbounds ptr, ptr %4, i32 0
+  %6 = load ptr, ptr %5, align 8
+  call void %6(ptr noundef nonnull align 8 dereferenceable(12) %3)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z14call_through_CP1C(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  %4 = load ptr, ptr %3, align 8
+  %5 = getelementptr inbounds ptr, ptr %4, i32 0
+  %6 = load ptr, ptr %5, align 8
+  call void %6(ptr noundef nonnull align 8 dereferenceable(32) %3)
+  %7 = load ptr, ptr %2, align 8
+  %8 = load ptr, ptr %7, align 8
+  %9 = getelementptr inbounds ptr, ptr %8, i32 1
+  %10 = load ptr, ptr %9, align 8
+  call void %10(ptr noundef nonnull align 8 dereferenceable(32) %7)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z21direct_qualified_callP1C(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %3)
+  %4 = load ptr, ptr %2, align 8
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %4)
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z10base_castsP1C(ptr noundef %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  %3 = alloca ptr, i64 1, align 8
+  %4 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %5 = load ptr, ptr %2, align 8
+  store ptr %5, ptr %3, align 8
+  %6 = load ptr, ptr %2, align 8
+  %7 = icmp eq ptr %6, null
+  %8 = getelementptr i8, ptr %6, i32 16
+  %9 = select i1 %7, ptr %6, ptr %8
+  store ptr %9, ptr %4, align 8
+  %10 = load ptr, ptr %3, align 8
+  %11 = load ptr, ptr %10, align 8
+  %12 = getelementptr inbounds ptr, ptr %11, i32 0
+  %13 = load ptr, ptr %12, align 8
+  call void %13(ptr noundef nonnull align 8 dereferenceable(12) %10)
+  %14 = load ptr, ptr %4, align 8
+  %15 = load ptr, ptr %14, align 8
+  %16 = getelementptr inbounds ptr, ptr %15, i32 0
+  %17 = load ptr, ptr %16, align 8
+  call void %17(ptr noundef nonnull align 8 dereferenceable(12) %14)
+  ret void
+}
+
+; Function Attrs: noinline
+define linkonce_odr void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1A, i64 16), ptr %3, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define linkonce_odr void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1B, i64 16), ptr %3, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define linkonce_odr void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %3) #2
+  %4 = getelementptr i8, ptr %3, i32 16
+  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %4) #2
+  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 16), ptr %3, align 8
+  %5 = getelementptr i8, ptr %3, i32 16
+  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 48), ptr %5, align 8
+  ret void
+}
+
+; Function Attrs: noinline
+define linkonce_odr void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
+  %2 = alloca ptr, i64 1, align 8
+  store ptr %0, ptr %2, align 8
+  %3 = load ptr, ptr %2, align 8
+  call void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %3) #2
+  ret void
+}
+
+; Function Attrs: noinline
+define dso_local void @_Z18construct_and_callv() #0 {
+  %1 = alloca %struct.C, i64 1, align 8
+  %2 = alloca ptr, i64 1, align 8
+  %3 = alloca ptr, i64 1, align 8
+  call void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %1) #2
+  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
+  store ptr %1, ptr %2, align 8
+  %4 = icmp eq ptr %1, null
+  %5 = getelementptr i8, ptr %1, i32 16
+  %6 = select i1 %4, ptr %1, ptr %5
+  store ptr %6, ptr %3, align 8
+  %7 = load ptr, ptr %2, align 8
+  %8 = load ptr, ptr %7, align 8
+  %9 = getelementptr inbounds ptr, ptr %8, i32 0
+  %10 = load ptr, ptr %9, align 8
+  call void %10(ptr noundef nonnull align 8 dereferenceable(12) %7)
+  %11 = load ptr, ptr %3, align 8
+  %12 = load ptr, ptr %11, align 8
+  %13 = getelementptr inbounds ptr, ptr %12, i32 0
+  %14 = load ptr, ptr %13, align 8
+  call void %14(ptr noundef nonnull align 8 dereferenceable(12) %11)
+  ret void
+}
+
+attributes #0 = { noinline "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+attributes #1 = { noinline }
+attributes #2 = { nounwind }
+
+!llvm.module.flags = !{!0}
+
+!0 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index db3ac6340eccb..96625a36a0153 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -3109,6 +3109,60 @@ def CIR_VTableGetVirtualFnAddrOp : CIR_Op<"vtable.get_virtual_fn_addr", [
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// VTableGetRelativeVirtualFnAddrOp
+//===----------------------------------------------------------------------===//
+
+def CIR_VTableGetRelativeVirtualFnAddrOp
+    : CIR_Op<"vtable.get_relative_virtual_fn_addr", [Pure]> {
+  let summary = "Resolve a virtual function pointer from a relative vtable";
+
+  let description = [{
+    The `vtable.get_relative_virtual_fn_addr` operation resolves a virtual
+    function pointer from a relative C++ ABI vtable.
+
+    Relative vtables store 32-bit signed offsets in their entries instead of
+    storing function pointers directly. The `vptr` operand is the vtable address
+    point. The `index` attribute is the virtual function index, counted in
+    vtable entries.
+
+    This operation returns the resolved function pointer. It does not return
+    the address of the vtable slot.
+
+    During CIR-to-LLVM lowering, this operation should lower to:
+
+    ```
+    call ptr @llvm.load.relative.i32(ptr %vptr, i32 index * 4)
+    ```
+
+    Example:
+    ```
+    %vptr.addr = cir.vtable.get_vptr %obj : !cir.ptr<!rec_C>
+                 -> !cir.ptr<!cir.vptr>
+    %vptr = cir.load %vptr.addr : !cir.ptr<!cir.vptr>, !cir.vptr
+    %fn = cir.vtable.get_relative_virtual_fn_addr %vptr[1]
+          : !cir.vptr -> !cir.ptr<!cir.func<(!cir.ptr<!rec_C>) -> !s32i>>
+    %ret = cir.call %fn(%obj)
+           : (!cir.ptr<!cir.func<(!cir.ptr<!rec_C>) -> !s32i>>,
+              !cir.ptr<!rec_C>) -> !s32i
+    ```
+  }];
+
+  let arguments = (ins
+    CIR_VPtrType:$vptr,
+    I64Attr:$index
+  );
+
+  let results = (outs
+    CIR_PointerType:$result
+  );
+
+  let assemblyFormat = [{
+    $vptr `[` $index `]` attr-dict
+    `:` qualified(type($vptr)) `->` qualified(type($result))
+  }];
+}
+
 //===----------------------------------------------------------------------===//
 // VTableGetTypeInfoOp
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index b6e6bd4857c9b..b79fb507f9db8 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -1949,10 +1949,19 @@ CIRGenCallee CIRGenItaniumCXXABI::getVirtualFunctionPointer(
   } else {
     assert(!cir::MissingFeatures::emitTypeMetadataCodeForVCall());
 
-    mlir::Value vfuncLoad;
+    mlir::Value vfuncLoad{};
     if (cgm.getLangOpts().RelativeCXXABIVTables) {
-      assert(!cir::MissingFeatures::vtableRelativeLayout());
-      cgm.errorNYI(loc, "getVirtualFunctionPointer: isRelativeLayout");
+      // Relative vtables store 32-bit offsets in the vtable entries.
+      //
+      // Keep this as a CIR-level relative virtual call operation and let
+      // the CIR-to-LLVM lowering translate it to:
+      //
+      //   call ptr @llvm.load.relative.i32(ptr %vtable,
+      //                                   i32 (vtableIndex * 4))
+      //
+      // The result is the resolved virtual function pointer.
+      vfuncLoad = cir::VTableGetRelativeVirtualFnAddrOp::create(
+          builder, loc, tyPtr, vtable, vtableIndex);
     } else {
       auto vtableSlotPtr = cir::VTableGetVirtualFnAddrOp::create(
           builder, loc, builder.getPointerTo(tyPtr), vtable, vtableIndex);
@@ -1977,6 +1986,7 @@ CIRGenCallee CIRGenItaniumCXXABI::getVirtualFunctionPointer(
   return callee;
 }
 
+
 mlir::Value CIRGenItaniumCXXABI::getVTableAddressPointInStructorWithVTT(
     CIRGenFunction &cgf, const CXXRecordDecl *vtableClass, BaseSubobject base,
     const CXXRecordDecl *nearestVBase) {
@@ -2636,7 +2646,7 @@ static mlir::Value performTypeAdjustment(CIRGenFunction &cgf,
         cir::PtrStrideOp::create(builder, loc, i8PtrTy, vtablePtr,
                                  builder.getSInt64(virtualAdjustment, loc));
     if (cgf.cgm.getLangOpts().RelativeCXXABIVTables) {
-      assert(!cir::MissingFeatures::vtableRelativeLayout());
+      assert(!cir::MissingFeatures::vtableRelativeLayout()); // performTypeAdjustment
       cgf.cgm.errorNYI("virtual adjustment for relative layout vtables");
     } else {
       offset = builder.createAlignedLoad(loc, cgf.ptrDiffTy, offsetPtr,
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index 756ce62458290..8d9cf3e0a1ab7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -134,6 +134,40 @@ void CIRGenVTables::generateClassData(const CXXRecordDecl *rd) {
   cgm.getCXXABI().emitVTableDefinitions(*this, rd);
 }
 
+mlir::Attribute CIRGenVTables::getVTableIntegerOrNullComponent(
+    const VTableComponent &component) {
+  CIRGenBuilderTy &builder = cgm.getBuilder();
+  bool isRelative = cgm.getLangOpts().RelativeCXXABIVTables;
+
+  auto getOffsetAttr = [&](CharUnits offset) -> mlir::Attribute {
+    if (isRelative) {
+      return cir::IntAttr::get(builder.getSInt32Ty(), offset.getQuantity());
+    }
+    return builder.getConstPtrAttr(builder.getUInt8PtrTy(),
+                                   offset.getQuantity());
+  };
+
+  switch (component.getKind()) {
+  case VTableComponent::CK_UnusedFunctionPointer:
+    if (isRelative)
+      return cir::IntAttr::get(builder.getSInt32Ty(), 0);
+
+    return builder.getConstNullPtrAttr(builder.getUInt8PtrTy());
+
+  case VTableComponent::CK_VCallOffset:
+    return getOffsetAttr(component.getVCallOffset());
+
+  case VTableComponent::CK_VBaseOffset:
+    return getOffsetAttr(component.getVBaseOffset());
+
+  case VTableComponent::CK_OffsetToTop:
+    return getOffsetAttr(component.getOffsetToTop());
+
+  default:
+    llvm_unreachable("expected integer or null vtable component");
+  }
+}
+
 mlir::Attribute CIRGenVTables::getVTableComponent(
     const VTableLayout &layout, unsigned componentIndex, mlir::Attribute rtti,
     unsigned &nextVTableThunkIndex, unsigned vtableAddressPoint,
@@ -286,7 +320,7 @@ cir::GlobalOp CIRGenVTables::generateConstructionVTable(
                            base.getBase(), out);
   SmallString<256> name(outName);
 
-  assert(!cir::MissingFeatures::vtableRelativeLayout());
+  assert(!cir::MissingFeatures::vtableRelativeLayout()); // generateConstructionVTable
 
   cir::RecordType vtType = getVTableType(*vtLayout);
 
@@ -321,7 +355,7 @@ cir::GlobalOp CIRGenVTables::generateConstructionVTable(
   cgm.setGVProperties(vtable, rd);
 
   assert(!cir::MissingFeatures::vtableEmitMetadata());
-  assert(!cir::MissingFeatures::vtableRelativeLayout());
+  assert(!cir::MissingFeatures::vtableRelativeLayout()); // generateConstructionVTable
 
   return vtable;
 }
diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 270c55dfc4541..6cd2374673e69 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -708,11 +708,13 @@ LogicalResult cir::VTableAttr::verify(
     auto arrayElts = mlir::cast<ArrayAttr>(constArrayAttr.getElts());
     arrayElts.walkImmediateSubElements(
         [&](mlir::Attribute attr) {
-          if (mlir::isa<ConstPtrAttr, GlobalViewAttr>(attr))
+          if (mlir::isa<cir::ConstPtrAttr, cir::GlobalViewAttr,
+                        cir::IntAttr>(attr))
             return;
 
-          eltTypeCheck = emitError()
-                         << "expected GlobalViewAttr or ConstPtrAttr";
+          eltTypeCheck =
+              emitError()
+              << "expected GlobalViewAttr, ConstPtrAttr, or IntAttr";
         },
         [&](mlir::Type type) {});
     if (eltTypeCheck.failed())
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index e17c7a209db6b..2aaf2b9f946a3 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -4201,6 +4201,28 @@ mlir::LogicalResult CIRToLLVMVTableGetVirtualFnAddrOpLowering::matchAndRewrite(
   return mlir::success();
 }
 
+mlir::LogicalResult
+CIRToLLVMVTableGetRelativeVirtualFnAddrOpLowering::matchAndRewrite(
+    cir::VTableGetRelativeVirtualFnAddrOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  mlir::Location loc = op.getLoc();
+
+  mlir::Type targetType = getTypeConverter()->convertType(op.getType());
+
+  // llvm.load.relative.i32 takes a byte offset, not an entry index.
+  uint64_t byteOffset = op.getIndex() * 4;
+
+  mlir::Value offset = mlir::LLVM::ConstantOp::create(
+      rewriter, loc, rewriter.getI32Type(),
+      rewriter.getI32IntegerAttr(static_cast<int32_t>(byteOffset)));
+
+  replaceOpWithCallLLVMIntrinsicOp(rewriter, op.getOperation(),
+                                   "llvm.load.relative.i32", targetType,
+                                   mlir::ValueRange{adaptor.getVptr(), offset});
+
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMVTTAddrPointOpLowering::matchAndRewrite(
     cir::VTTAddrPointOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/ll.ll b/ll.ll
new file mode 100644
index 0000000000000..7cae76a7071d9
--- /dev/null
+++ b/ll.ll
@@ -0,0 +1,250 @@
+; ModuleID = '1.cpp'
+source_filename = "1.cpp"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+%struct.C = type { %struct.A.base, [4 x i8], %struct.B.base, i32 }
+%struct.A.base = type <{ ptr, i32 }>
+%struct.B.base = type <{ ptr, i32 }>
+
+$_ZN1CC1Ev = comdat any
+
+$_ZN1CC2Ev = comdat any
+
+$_ZN1AC2Ev = comdat any
+
+$_ZN1BC2Ev = comdat any
+
+ at _ZTV1A = unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1A, ptr @_ZN1A1fEv] }, align 8
+ at _ZTI1A = constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTS1A }, align 8
+ at _ZTVN10__cxxabiv117__class_type_infoE = external global [0 x ptr]
+ at _ZTS1A = constant [3 x i8] c"1A\00", align 1
+ at _ZTV1B = unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1B1gEv] }, align 8
+ at _ZTI1B = constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTS1B }, align 8
+ at _ZTS1B = constant [3 x i8] c"1B\00", align 1
+ at _ZTV1C = unnamed_addr constant { [4 x ptr], [3 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1C, ptr @_ZN1C1fEv, ptr @_ZN1C1gEv], [3 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr @_ZTI1C, ptr @_ZThn16_N1C1gEv] }, align 8
+ at _ZTI1C = constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), ptr @_ZTS1C, i32 0, i32 2, ptr @_ZTI1A, i64 2, ptr @_ZTI1B, i64 4098 }, align 8
+ at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global [0 x ptr]
+ at _ZTS1C = constant [3 x i8] c"1C\00", align 1
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_ZN1A1fEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_ZN1B1gEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  ret void
+}
+
+; Function Attrs: noinline nounwind optnone
+define dso_local void @_ZThn16_N1C1gEv(ptr noundef %this) unnamed_addr #1 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  %0 = getelementptr inbounds i8, ptr %this1, i64 -16
+  tail call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %0)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z14call_through_AP1A(ptr noundef %pa) #0 {
+entry:
+  %pa.addr = alloca ptr, align 8
+  store ptr %pa, ptr %pa.addr, align 8
+  %0 = load ptr, ptr %pa.addr, align 8
+  %vtable = load ptr, ptr %0, align 8
+  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
+  %1 = load ptr, ptr %vfn, align 8
+  call void %1(ptr noundef nonnull align 8 dereferenceable(12) %0)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z14call_through_BP1B(ptr noundef %pb) #0 {
+entry:
+  %pb.addr = alloca ptr, align 8
+  store ptr %pb, ptr %pb.addr, align 8
+  %0 = load ptr, ptr %pb.addr, align 8
+  %vtable = load ptr, ptr %0, align 8
+  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
+  %1 = load ptr, ptr %vfn, align 8
+  call void %1(ptr noundef nonnull align 8 dereferenceable(12) %0)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z14call_through_CP1C(ptr noundef %pc) #0 {
+entry:
+  %pc.addr = alloca ptr, align 8
+  store ptr %pc, ptr %pc.addr, align 8
+  %0 = load ptr, ptr %pc.addr, align 8
+  %vtable = load ptr, ptr %0, align 8
+  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
+  %1 = load ptr, ptr %vfn, align 8
+  call void %1(ptr noundef nonnull align 8 dereferenceable(32) %0)
+  %2 = load ptr, ptr %pc.addr, align 8
+  %vtable1 = load ptr, ptr %2, align 8
+  %vfn2 = getelementptr inbounds ptr, ptr %vtable1, i64 1
+  %3 = load ptr, ptr %vfn2, align 8
+  call void %3(ptr noundef nonnull align 8 dereferenceable(32) %2)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z21direct_qualified_callP1C(ptr noundef %pc) #0 {
+entry:
+  %pc.addr = alloca ptr, align 8
+  store ptr %pc, ptr %pc.addr, align 8
+  %0 = load ptr, ptr %pc.addr, align 8
+  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %0)
+  %1 = load ptr, ptr %pc.addr, align 8
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z10base_castsP1C(ptr noundef %pc) #0 {
+entry:
+  %pc.addr = alloca ptr, align 8
+  %pa = alloca ptr, align 8
+  %pb = alloca ptr, align 8
+  store ptr %pc, ptr %pc.addr, align 8
+  %0 = load ptr, ptr %pc.addr, align 8
+  store ptr %0, ptr %pa, align 8
+  %1 = load ptr, ptr %pc.addr, align 8
+  %2 = icmp eq ptr %1, null
+  br i1 %2, label %cast.end, label %cast.notnull
+
+cast.notnull:                                     ; preds = %entry
+  %add.ptr = getelementptr inbounds i8, ptr %1, i64 16
+  br label %cast.end
+
+cast.end:                                         ; preds = %cast.notnull, %entry
+  %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
+  store ptr %cast.result, ptr %pb, align 8
+  %3 = load ptr, ptr %pa, align 8
+  %vtable = load ptr, ptr %3, align 8
+  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
+  %4 = load ptr, ptr %vfn, align 8
+  call void %4(ptr noundef nonnull align 8 dereferenceable(12) %3)
+  %5 = load ptr, ptr %pb, align 8
+  %vtable1 = load ptr, ptr %5, align 8
+  %vfn2 = getelementptr inbounds ptr, ptr %vtable1, i64 0
+  %6 = load ptr, ptr %vfn2, align 8
+  call void %6(ptr noundef nonnull align 8 dereferenceable(12) %5)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z18construct_and_callv() #0 {
+entry:
+  %obj = alloca %struct.C, align 8
+  %pa = alloca ptr, align 8
+  %pb = alloca ptr, align 8
+  call void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %obj) #2
+  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %obj)
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %obj)
+  store ptr %obj, ptr %pa, align 8
+  %0 = icmp eq ptr %obj, null
+  br i1 %0, label %cast.end, label %cast.notnull
+
+cast.notnull:                                     ; preds = %entry
+  %add.ptr = getelementptr inbounds i8, ptr %obj, i64 16
+  br label %cast.end
+
+cast.end:                                         ; preds = %cast.notnull, %entry
+  %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
+  store ptr %cast.result, ptr %pb, align 8
+  %1 = load ptr, ptr %pa, align 8
+  %vtable = load ptr, ptr %1, align 8
+  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
+  %2 = load ptr, ptr %vfn, align 8
+  call void %2(ptr noundef nonnull align 8 dereferenceable(12) %1)
+  %3 = load ptr, ptr %pb, align 8
+  %vtable1 = load ptr, ptr %3, align 8
+  %vfn2 = getelementptr inbounds ptr, ptr %vtable1, i64 0
+  %4 = load ptr, ptr %vfn2, align 8
+  call void %4(ptr noundef nonnull align 8 dereferenceable(12) %3)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define linkonce_odr void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 comdat align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  call void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %this1) #2
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define linkonce_odr void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 comdat align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this1) #2
+  %0 = getelementptr inbounds i8, ptr %this1, i64 16
+  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #2
+  store ptr getelementptr inbounds inrange(-16, 16) ({ [4 x ptr], [3 x ptr] }, ptr @_ZTV1C, i32 0, i32 0, i32 2), ptr %this1, align 8
+  %add.ptr = getelementptr inbounds i8, ptr %this1, i64 16
+  store ptr getelementptr inbounds inrange(-16, 8) ({ [4 x ptr], [3 x ptr] }, ptr @_ZTV1C, i32 0, i32 1, i32 2), ptr %add.ptr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define linkonce_odr void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 comdat align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  store ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr] }, ptr @_ZTV1A, i32 0, i32 0, i32 2), ptr %this1, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define linkonce_odr void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 comdat align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  store ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr] }, ptr @_ZTV1B, i32 0, i32 0, i32 2), ptr %this1, align 8
+  ret void
+}
+
+attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+attributes #1 = { noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+attributes #2 = { nounwind }
+
+!llvm.ident = !{!0}
+
+!0 = !{!"clang version 23.0.0git (git at github.com:xiongzile/llvm-project.git c1ef54f3f3c46982dd7dc03c41f66f8e274ac2e6)"}
diff --git a/rel-ll.ll b/rel-ll.ll
new file mode 100644
index 0000000000000..46c7a87b63872
--- /dev/null
+++ b/rel-ll.ll
@@ -0,0 +1,259 @@
+; ModuleID = '1.cpp'
+source_filename = "1.cpp"
+target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-unknown-linux-gnu"
+
+%struct.C = type { %struct.A.base, [4 x i8], %struct.B.base, i32 }
+%struct.A.base = type <{ ptr, i32 }>
+%struct.B.base = type <{ ptr, i32 }>
+
+$_ZN1CC1Ev = comdat any
+
+$_ZN1CC2Ev = comdat any
+
+$_ZN1AC2Ev = comdat any
+
+$_ZN1BC2Ev = comdat any
+
+$_ZTI1A.rtti_proxy = comdat any
+
+$_ZTI1B.rtti_proxy = comdat any
+
+$_ZTI1C.rtti_proxy = comdat any
+
+ at _ZTV1A.local = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint (ptr @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1A1fEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4
+ at _ZTI1A = constant { ptr, ptr } { ptr getelementptr inbounds (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i32 8), ptr @_ZTS1A }, align 8
+ at _ZTVN10__cxxabiv117__class_type_infoE = external global [0 x ptr]
+ at _ZTS1A = constant [3 x i8] c"1A\00", align 1
+ at _ZTI1A.rtti_proxy = linkonce_odr hidden unnamed_addr constant ptr @_ZTI1A, comdat
+ at _ZTV1B.local = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint (ptr @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1B1gEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4
+ at _ZTI1B = constant { ptr, ptr } { ptr getelementptr inbounds (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i32 8), ptr @_ZTS1B }, align 8
+ at _ZTS1B = constant [3 x i8] c"1B\00", align 1
+ at _ZTI1B.rtti_proxy = linkonce_odr hidden unnamed_addr constant ptr @_ZTI1B, comdat
+ at _ZTV1C.local = internal unnamed_addr constant { [4 x i32], [3 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint (ptr @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1C1fEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1C1gEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32)], [3 x i32] [i32 -16, i32 trunc (i64 sub (i64 ptrtoint (ptr @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 1, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZThn16_N1C1gEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 1, i32 2) to i64)) to i32)] }, align 4
+ at _ZTI1C = constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr inbounds (i8, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i32 8), ptr @_ZTS1C, i32 0, i32 2, ptr @_ZTI1A, i64 2, ptr @_ZTI1B, i64 4098 }, align 8
+ at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global [0 x ptr]
+ at _ZTS1C = constant [3 x i8] c"1C\00", align 1
+ at _ZTI1C.rtti_proxy = linkonce_odr hidden unnamed_addr constant ptr @_ZTI1C, comdat
+
+ at _ZTV1A = unnamed_addr alias { [3 x i32] }, ptr @_ZTV1A.local
+ at _ZTV1B = unnamed_addr alias { [3 x i32] }, ptr @_ZTV1B.local
+ at _ZTV1C = unnamed_addr alias { [4 x i32], [3 x i32] }, ptr @_ZTV1C.local
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_ZN1A1fEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_ZN1B1gEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  ret void
+}
+
+; Function Attrs: noinline nounwind optnone
+define dso_local void @_ZThn16_N1C1gEv(ptr noundef %this) unnamed_addr #1 align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  %0 = getelementptr inbounds i8, ptr %this1, i64 -16
+  tail call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %0)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z14call_through_AP1A(ptr noundef %pa) #0 {
+entry:
+  %pa.addr = alloca ptr, align 8
+  store ptr %pa, ptr %pa.addr, align 8
+  %0 = load ptr, ptr %pa.addr, align 8
+  %vtable = load ptr, ptr %0, align 8
+  %1 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
+  call void %1(ptr noundef nonnull align 8 dereferenceable(12) %0)
+  ret void
+}
+
+; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
+declare ptr @llvm.load.relative.i32(ptr, i32) #2
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z14call_through_BP1B(ptr noundef %pb) #0 {
+entry:
+  %pb.addr = alloca ptr, align 8
+  store ptr %pb, ptr %pb.addr, align 8
+  %0 = load ptr, ptr %pb.addr, align 8
+  %vtable = load ptr, ptr %0, align 8
+  %1 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
+  call void %1(ptr noundef nonnull align 8 dereferenceable(12) %0)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z14call_through_CP1C(ptr noundef %pc) #0 {
+entry:
+  %pc.addr = alloca ptr, align 8
+  store ptr %pc, ptr %pc.addr, align 8
+  %0 = load ptr, ptr %pc.addr, align 8
+  %vtable = load ptr, ptr %0, align 8
+  %1 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
+  call void %1(ptr noundef nonnull align 8 dereferenceable(32) %0)
+  %2 = load ptr, ptr %pc.addr, align 8
+  %vtable1 = load ptr, ptr %2, align 8
+  %3 = call ptr @llvm.load.relative.i32(ptr %vtable1, i32 4)
+  call void %3(ptr noundef nonnull align 8 dereferenceable(32) %2)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z21direct_qualified_callP1C(ptr noundef %pc) #0 {
+entry:
+  %pc.addr = alloca ptr, align 8
+  store ptr %pc, ptr %pc.addr, align 8
+  %0 = load ptr, ptr %pc.addr, align 8
+  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %0)
+  %1 = load ptr, ptr %pc.addr, align 8
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z10base_castsP1C(ptr noundef %pc) #0 {
+entry:
+  %pc.addr = alloca ptr, align 8
+  %pa = alloca ptr, align 8
+  %pb = alloca ptr, align 8
+  store ptr %pc, ptr %pc.addr, align 8
+  %0 = load ptr, ptr %pc.addr, align 8
+  store ptr %0, ptr %pa, align 8
+  %1 = load ptr, ptr %pc.addr, align 8
+  %2 = icmp eq ptr %1, null
+  br i1 %2, label %cast.end, label %cast.notnull
+
+cast.notnull:                                     ; preds = %entry
+  %add.ptr = getelementptr inbounds i8, ptr %1, i32 16
+  br label %cast.end
+
+cast.end:                                         ; preds = %cast.notnull, %entry
+  %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
+  store ptr %cast.result, ptr %pb, align 8
+  %3 = load ptr, ptr %pa, align 8
+  %vtable = load ptr, ptr %3, align 8
+  %4 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
+  call void %4(ptr noundef nonnull align 8 dereferenceable(12) %3)
+  %5 = load ptr, ptr %pb, align 8
+  %vtable1 = load ptr, ptr %5, align 8
+  %6 = call ptr @llvm.load.relative.i32(ptr %vtable1, i32 0)
+  call void %6(ptr noundef nonnull align 8 dereferenceable(12) %5)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define dso_local void @_Z18construct_and_callv() #0 {
+entry:
+  %obj = alloca %struct.C, align 8
+  %pa = alloca ptr, align 8
+  %pb = alloca ptr, align 8
+  call void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %obj) #3
+  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %obj)
+  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %obj)
+  store ptr %obj, ptr %pa, align 8
+  %0 = icmp eq ptr %obj, null
+  br i1 %0, label %cast.end, label %cast.notnull
+
+cast.notnull:                                     ; preds = %entry
+  %add.ptr = getelementptr inbounds i8, ptr %obj, i32 16
+  br label %cast.end
+
+cast.end:                                         ; preds = %cast.notnull, %entry
+  %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
+  store ptr %cast.result, ptr %pb, align 8
+  %1 = load ptr, ptr %pa, align 8
+  %vtable = load ptr, ptr %1, align 8
+  %2 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
+  call void %2(ptr noundef nonnull align 8 dereferenceable(12) %1)
+  %3 = load ptr, ptr %pb, align 8
+  %vtable1 = load ptr, ptr %3, align 8
+  %4 = call ptr @llvm.load.relative.i32(ptr %vtable1, i32 0)
+  call void %4(ptr noundef nonnull align 8 dereferenceable(12) %3)
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define linkonce_odr void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 comdat align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  call void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %this1) #3
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define linkonce_odr void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 comdat align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this1) #3
+  %0 = getelementptr inbounds i8, ptr %this1, i64 16
+  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #3
+  store ptr getelementptr inbounds inrange(-8, 8) ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 0, i32 2), ptr %this1, align 8
+  %add.ptr = getelementptr inbounds i8, ptr %this1, i32 16
+  store ptr getelementptr inbounds inrange(-8, 4) ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 1, i32 2), ptr %add.ptr, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define linkonce_odr void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 comdat align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  store ptr getelementptr inbounds inrange(-8, 4) ({ [3 x i32] }, ptr @_ZTV1A.local, i32 0, i32 0, i32 2), ptr %this1, align 8
+  ret void
+}
+
+; Function Attrs: mustprogress noinline nounwind optnone
+define linkonce_odr void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 comdat align 2 {
+entry:
+  %this.addr = alloca ptr, align 8
+  store ptr %this, ptr %this.addr, align 8
+  %this1 = load ptr, ptr %this.addr, align 8
+  store ptr getelementptr inbounds inrange(-8, 4) ({ [3 x i32] }, ptr @_ZTV1B.local, i32 0, i32 0, i32 2), ptr %this1, align 8
+  ret void
+}
+
+attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+attributes #1 = { noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
+attributes #2 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) }
+attributes #3 = { nounwind }
+
+!llvm.ident = !{!0}
+
+!0 = !{!"clang version 23.0.0git (git at github.com:xiongzile/llvm-project.git c1ef54f3f3c46982dd7dc03c41f66f8e274ac2e6)"}

>From 9f64e4cf0fb16d1b378af243172364b10e261995 Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile99 at gmail.com>
Date: Wed, 6 May 2026 23:33:48 +0800
Subject: [PATCH 4/5] [CIR] add emit relative vtable support

---
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 40 ++++++++++-------
 clang/lib/CIR/CodeGen/CIRGenVTables.cpp       | 43 ++++++++++---------
 clang/lib/CIR/CodeGen/CIRGenVTables.h         |  3 ++
 clang/lib/CIR/Dialect/IR/CIRAttrs.cpp         |  4 +-
 4 files changed, 52 insertions(+), 38 deletions(-)

diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index b79fb507f9db8..70c9ff9492cf3 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -26,6 +26,7 @@
 #include "clang/AST/VTableBuilder.h"
 #include "clang/CIR/MissingFeatures.h"
 #include "llvm/Support/ErrorHandling.h"
+#include <cassert>
 
 using namespace clang;
 using namespace clang::CIRGen;
@@ -479,20 +480,27 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
   cir::GlobalOp vtable = getAddrOfVTable(rd, CharUnits());
   if (vtable.hasInitializer())
     return;
-
+  llvm::errs() << "vtable = ";
+  vtable->dump();
+  llvm::errs() << "";
   ItaniumVTableContext &vtContext = cgm.getItaniumVTableContext();
   const VTableLayout &vtLayout = vtContext.getVTableLayout(rd);
   cir::GlobalLinkageKind linkage = cgm.getVTableLinkage(rd);
   mlir::Attribute rtti =
       cgm.getAddrOfRTTIDescriptor(cgm.getLoc(rd->getBeginLoc()),
                                   cgm.getASTContext().getCanonicalTagType(rd));
-
+  llvm::errs() << "rtti = ";
+  rtti.dump();
+  llvm::errs() << "\n";
   // Classic codegen uses ConstantInitBuilder here, which is a very general
   // and feature-rich class to generate initializers for global values.
   // For now, this is using a simpler approach to create the initializer in CIR.
   cgvt.createVTableInitializer(vtable, vtLayout, rtti,
                                cir::isLocalLinkage(linkage));
-
+  llvm::errs() << "new vtable = ";
+  vtable->dump();
+  llvm::errs() << "\n";
+  llvm::errs() << "\n";
   // Set the correct linkage.
   vtable.setLinkage(linkage);
 
@@ -530,10 +538,11 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
                  "emitVTableDefinitions: WholeProgramVTables");
   }
 
-  assert(!cir::MissingFeatures::vtableRelativeLayout());
-  if (cgm.getLangOpts().RelativeCXXABIVTables) {
-    cgm.errorNYI(rd->getSourceRange(), "vtableRelativeLayout");
-  }
+  // TODO: by @Elio
+  // assert(!cir::MissingFeatures::vtableRelativeLayout());
+  // if (cgm.getLangOpts().RelativeCXXABIVTables) {
+  //   cgm.errorNYI(rd->getSourceRange(), "vtableRelativeLayout");
+  // }
 }
 
 mlir::Value CIRGenItaniumCXXABI::emitVirtualDestructorCall(
@@ -1223,11 +1232,6 @@ void CIRGenItaniumRTTIBuilder::buildVTablePointer(mlir::Location loc,
   const char *vTableName = vTableClassNameForType(cgm, ty);
 
   // Check if the alias exists. If it doesn't, then get or create the global.
-  if (cgm.getLangOpts().RelativeCXXABIVTables) {
-    cgm.errorNYI("buildVTablePointer: isRelativeLayout");
-    return;
-  }
-
   mlir::Type vtableGlobalTy = builder.getPointerTo(builder.getUInt8PtrTy());
   llvm::Align align = cgm.getDataLayout().getABITypeAlign(vtableGlobalTy);
   cir::GlobalOp vTable = cgm.createOrReplaceCXXRuntimeVariable(
@@ -1235,13 +1239,17 @@ void CIRGenItaniumRTTIBuilder::buildVTablePointer(mlir::Location loc,
       CharUnits::fromQuantity(align));
 
   // The vtable address point is 2.
+  SmallVector<mlir::Attribute, 4> offsets{
+      cgm.getBuilder().getI32IntegerAttr(2)};
+  auto indices = mlir::ArrayAttr::get(builder.getContext(), offsets);
   mlir::Attribute field{};
   if (cgm.getLangOpts().RelativeCXXABIVTables) {
-    cgm.errorNYI("buildVTablePointer: isRelativeLayout");
+    // TODO: by @Elio.
+    // For relative vtables, this needs special handling during lowering: the
+    // GlobalViewAttr target should be emitted as target - current slot.
+    auto symbol = mlir::FlatSymbolRefAttr::get(vTable.getSymNameAttr());
+    field = cir::GlobalViewAttr::get(builder.getSInt32Ty(), symbol, indices);
   } else {
-    SmallVector<mlir::Attribute, 4> offsets{
-        cgm.getBuilder().getI32IntegerAttr(2)};
-    auto indices = mlir::ArrayAttr::get(builder.getContext(), offsets);
     field = cgm.getBuilder().getGlobalViewAttr(cgm.getBuilder().getUInt8PtrTy(),
                                                vTable, indices);
   }
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index 8d9cf3e0a1ab7..a93cd23313c0e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -52,9 +52,12 @@ static void setThunkProperties(CIRGenModule &cgm, const ThunkInfo &thunk,
 }
 
 mlir::Type CIRGenModule::getVTableComponentType() {
-  mlir::Type ptrTy = builder.getUInt8PtrTy();
-  assert(!cir::MissingFeatures::vtableRelativeLayout());
-  return ptrTy;
+  mlir::Type ty = builder.getUInt8PtrTy();
+  // assert(!cir::MissingFeatures::vtableRelativeLayout());
+  if (getLangOpts().RelativeCXXABIVTables) {
+    ty = builder.getSInt32Ty();
+  }
+  return ty;
 }
 
 mlir::Type CIRGenVTables::getVTableComponentType() {
@@ -176,28 +179,26 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
 
   CIRGenBuilderTy builder = cgm.getBuilder();
 
-  assert(!cir::MissingFeatures::vtableRelativeLayout());
+  // assert(!cir::MissingFeatures::vtableRelativeLayout());
 
   switch (component.getKind()) {
   case VTableComponent::CK_UnusedFunctionPointer:
-    return builder.getConstNullPtrAttr(builder.getUInt8PtrTy());
-
   case VTableComponent::CK_VCallOffset:
-    return builder.getConstPtrAttr(builder.getUInt8PtrTy(),
-                                   component.getVCallOffset().getQuantity());
-
   case VTableComponent::CK_VBaseOffset:
-    return builder.getConstPtrAttr(builder.getUInt8PtrTy(),
-                                   component.getVBaseOffset().getQuantity());
-
   case VTableComponent::CK_OffsetToTop:
-    return builder.getConstPtrAttr(builder.getUInt8PtrTy(),
-                                   component.getOffsetToTop().getQuantity());
+    return getVTableIntegerOrNullComponent(component);
 
   case VTableComponent::CK_RTTI:
-    assert((mlir::isa<cir::GlobalViewAttr>(rtti) ||
-            mlir::isa<cir::ConstPtrAttr>(rtti)) &&
-           "expected GlobalViewAttr or ConstPtrAttr");
+    if (cgm.getLangOpts().RelativeCXXABIVTables) {
+      if (auto gv = mlir::dyn_cast<cir::GlobalViewAttr>(rtti)) {
+        rtti = cir::GlobalViewAttr::get(builder.getSInt32Ty(), gv.getSymbol(),
+                                        gv.getIndices());
+      } else {
+        // For null RTTI / special cases. Adjust if ConstPtrAttr has meaningful
+        // non-zero cases in your path.
+        rtti = builder.getI32IntegerAttr(0);
+      }
+    }
     return rtti;
 
   case VTableComponent::CK_FunctionPointer:
@@ -210,8 +211,6 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
     assert(!cir::MissingFeatures::cudaSupport());
 
     auto getSpecialVirtFn = [&](StringRef name) -> cir::FuncOp {
-      assert(!cir::MissingFeatures::vtableRelativeLayout());
-
       if (cgm.getLangOpts().OpenMP && cgm.getLangOpts().OpenMPIsTargetDevice &&
           cgm.getTriple().isNVPTX())
         cgm.errorNYI(gd.getDecl()->getSourceRange(),
@@ -251,7 +250,7 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
     }
 
     return cir::GlobalViewAttr::get(
-        builder.getUInt8PtrTy(),
+        getVTableComponentType(),
         mlir::FlatSymbolRefAttr::get(fnPtr.getSymNameAttr()));
   }
   }
@@ -278,10 +277,11 @@ void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp,
     size_t vtableEnd = vtableStart + layout.getVTableSize(vtableIndex);
     llvm::SmallVector<mlir::Attribute> components;
     components.reserve(vtableEnd - vtableStart);
-    for (size_t componentIndex : llvm::seq(vtableStart, vtableEnd))
+    for (size_t componentIndex : llvm::seq(vtableStart, vtableEnd)) {
       components.push_back(
           getVTableComponent(layout, componentIndex, rtti, nextVTableThunkIndex,
                              addressPoint, vtableHasLocalLinkage));
+    }
     // Create a ConstArrayAttr to hold the components.
     auto arr = cir::ConstArrayAttr::get(
         cir::ArrayType::get(componentType, components.size()),
@@ -297,6 +297,7 @@ void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp,
   auto vtableAttr = cir::VTableAttr::get(record.getType(), record.getMembers());
 
   // Add the vtable initializer to the vtable global op.
+  // CHECKME: by @Elio
   cgm.setInitializer(vtableOp, vtableAttr);
 }
 
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.h b/clang/lib/CIR/CodeGen/CIRGenVTables.h
index 1e4da7d8f945f..6011c1479fdb7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.h
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.h
@@ -56,6 +56,9 @@ class CIRGenVTables {
                      mlir::Attribute rtti, unsigned &nextVTableThunkIndex,
                      unsigned vtableAddressPoint, bool vtableHasLocalLinkage);
 
+  mlir::Attribute
+  getVTableIntegerOrNullComponent(const VTableComponent &component);
+
   mlir::Type getVTableComponentType();
 
 public:
diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 6cd2374673e69..0f38c76c6bbc4 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -700,7 +700,7 @@ LogicalResult cir::VTableAttr::verify(
     return failure();
 
   for (const auto &element : data.getAsRange<mlir::Attribute>()) {
-    const auto &constArrayAttr = mlir::dyn_cast<cir::ConstArrayAttr>(element);
+    auto constArrayAttr = mlir::dyn_cast<cir::ConstArrayAttr>(element);
     if (!constArrayAttr)
       return emitError() << "expected constant array subtype";
 
@@ -717,9 +717,11 @@ LogicalResult cir::VTableAttr::verify(
               << "expected GlobalViewAttr, ConstPtrAttr, or IntAttr";
         },
         [&](mlir::Type type) {});
+
     if (eltTypeCheck.failed())
       return eltTypeCheck;
   }
+
   return success();
 }
 

>From 41220f310b388c744e3c3f5522f3dc7ead65ffef Mon Sep 17 00:00:00 2001
From: Zile Xiong <xiongzile99 at gmail.com>
Date: Sat, 9 May 2026 17:32:47 +0800
Subject: [PATCH 5/5] [CIR][WIP] full support

---
 1.cpp                                         |  73 -----
 cir-ll.ll                                     | 212 --------------
 cir.ll                                        | 215 ---------------
 .../include/clang/CIR/Dialect/IR/CIRAttrs.td  | 149 ++++++++++
 clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp |  40 +--
 clang/lib/CIR/CodeGen/CIRGenVTables.cpp       |  75 +++--
 clang/lib/CIR/CodeGen/CIRGenVTables.h         |   5 +-
 clang/lib/CIR/Dialect/IR/CIRAttrs.cpp         |  61 ++++-
 clang/lib/CIR/Dialect/IR/CIRDialect.cpp       |  14 +-
 .../CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp |  57 +++-
 clang/test/CIR/IR/invalid-vtable.cir          |   2 +-
 ll.ll                                         | 250 -----------------
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp    |   3 +-
 rel-ll.ll                                     | 259 ------------------
 14 files changed, 344 insertions(+), 1071 deletions(-)
 delete mode 100644 1.cpp
 delete mode 100644 cir-ll.ll
 delete mode 100644 cir.ll
 delete mode 100644 ll.ll
 delete mode 100644 rel-ll.ll

diff --git a/1.cpp b/1.cpp
deleted file mode 100644
index d736d6f48ab03..0000000000000
--- a/1.cpp
+++ /dev/null
@@ -1,73 +0,0 @@
-struct A {
-  virtual void f();
-  int a;
-};
-
-struct B {
-  virtual void g();
-  int b;
-};
-
-struct C : A, B {
-  void f() override;
-  void g() override;
-  int c;
-};
-
-void A::f() {}
-void B::g() {}
-void C::f() {}
-void C::g() {}
-
-// 1. 通过 A* 做虚调用。
-//    这里应该会查 A-subobject 的 vptr,然后从 vtable slot 里取 f。
-void call_through_A(A *pa) {
-  pa->f();
-}
-
-// 2. 通过 B* 做虚调用。
-//    这里应该会查 B-subobject 的 vptr,然后从 secondary vtable slot 里取 g。
-//    如果实际对象是 C,slot 里可能是 thunk。
-void call_through_B(B *pb) {
-  pb->g();
-}
-
-// 3. 已知静态类型是 C*,但调用 virtual 函数。
-//    C++ 语义上仍然是 virtual call,除非编译器能 devirtualize。
-//    在 -O0 / CIR 阶段通常更容易看到 vptr load。
-void call_through_C(C *pc) {
-  pc->f();
-  pc->g();
-}
-
-// 4. 非虚的限定调用。
-//    这里不会查 vptr,应该直接 call C::f / C::g。
-void direct_qualified_call(C *pc) {
-  pc->C::f();
-  pc->C::g();
-}
-
-// 5. 从 C* 转成 A* / B*。
-//    A 是 primary base,一般 offset 是 0。
-//    B 是 secondary base,一般需要 this + 16 之类的调整。
-void base_casts(C *pc) {
-  A *pa = pc;
-  B *pb = pc;
-
-  pa->f();
-  pb->g();
-}
-
-// 6. 栈上构造 C。
-//    这里适合观察 constructor / vptr 初始化。
-//    如果 CIR 暂时没有完整 ctor lowering,也至少能暴露相关路径。
-void construct_and_call() {
-  C obj;
-  obj.f();
-  obj.g();
-
-  A *pa = &obj;
-  B *pb = &obj;
-  pa->f();
-  pb->g();
-}
\ No newline at end of file
diff --git a/cir-ll.ll b/cir-ll.ll
deleted file mode 100644
index 67b701207dcb9..0000000000000
--- a/cir-ll.ll
+++ /dev/null
@@ -1,212 +0,0 @@
-; ModuleID = '/data00/home/xiongzile/workspace/llvm-worktree/llvm-project/1.cpp'
-source_filename = "/data00/home/xiongzile/workspace/llvm-worktree/llvm-project/1.cpp"
-target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
-target triple = "x86_64-unknown-linux-gnu"
-
-%struct.C = type { %struct.A.base, [4 x i8], %struct.B.base, i32 }
-%struct.A.base = type <{ ptr, i32 }>
-%struct.B.base = type <{ ptr, i32 }>
-
- at _ZTV1A = global { [3 x i32] } { [3 x i32] [i32 0, i32 ptrtoint (ptr @_ZTI1A to i32), i32 ptrtoint (ptr @_ZN1A1fEv to i32)] }, align 4
- at _ZTVN10__cxxabiv117__class_type_infoE = external global ptr, align 8
- at _ZTS1A = global [3 x i8] c"1A\00", align 1
- at _ZTI1A = constant { i32, ptr } { i32 ptrtoint (ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 16) to i32), ptr @_ZTS1A }, align 8
- at _ZTV1B = global { [3 x i32] } { [3 x i32] [i32 0, i32 ptrtoint (ptr @_ZTI1B to i32), i32 ptrtoint (ptr @_ZN1B1gEv to i32)] }, align 4
- at _ZTS1B = global [3 x i8] c"1B\00", align 1
- at _ZTI1B = constant { i32, ptr } { i32 ptrtoint (ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 16) to i32), ptr @_ZTS1B }, align 8
- at _ZTV1C = global { [4 x i32], [3 x i32] } { [4 x i32] [i32 0, i32 ptrtoint (ptr @_ZTI1C to i32), i32 ptrtoint (ptr @_ZN1C1fEv to i32), i32 ptrtoint (ptr @_ZN1C1gEv to i32)], [3 x i32] [i32 -16, i32 ptrtoint (ptr @_ZTI1C to i32), i32 ptrtoint (ptr @_ZThn16_N1C1gEv to i32)] }, align 4
- at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global ptr, align 8
- at _ZTS1C = global [3 x i8] c"1C\00", align 1
- at _ZTI1C = constant { i32, ptr, i32, i32, ptr, i64, ptr, i64 } { i32 ptrtoint (ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 16) to i32), ptr @_ZTS1C, i32 0, i32 2, ptr @_ZTI1A, i64 2, ptr @_ZTI1B, i64 4098 }, align 8
-
-; Function Attrs: noinline
-define dso_local void @_ZN1A1fEv(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_ZN1B1gEv(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_ZThn16_N1C1gEv(ptr noundef %0) #1 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  %4 = getelementptr i8, ptr %3, i64 -16
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %4)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z14call_through_AP1A(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  %4 = load ptr, ptr %3, align 8
-  %5 = call ptr @llvm.load.relative.i32(ptr %4, i32 0)
-  call void %5(ptr noundef nonnull align 8 dereferenceable(12) %3)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z14call_through_BP1B(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  %4 = load ptr, ptr %3, align 8
-  %5 = call ptr @llvm.load.relative.i32(ptr %4, i32 0)
-  call void %5(ptr noundef nonnull align 8 dereferenceable(12) %3)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z14call_through_CP1C(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  %4 = load ptr, ptr %3, align 8
-  %5 = call ptr @llvm.load.relative.i32(ptr %4, i32 0)
-  call void %5(ptr noundef nonnull align 8 dereferenceable(32) %3)
-  %6 = load ptr, ptr %2, align 8
-  %7 = load ptr, ptr %6, align 8
-  %8 = call ptr @llvm.load.relative.i32(ptr %7, i32 4)
-  call void %8(ptr noundef nonnull align 8 dereferenceable(32) %6)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z21direct_qualified_callP1C(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %3)
-  %4 = load ptr, ptr %2, align 8
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %4)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z10base_castsP1C(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  %3 = alloca ptr, i64 1, align 8
-  %4 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %5 = load ptr, ptr %2, align 8
-  store ptr %5, ptr %3, align 8
-  %6 = load ptr, ptr %2, align 8
-  %7 = icmp eq ptr %6, null
-  %8 = getelementptr i8, ptr %6, i32 16
-  %9 = select i1 %7, ptr %6, ptr %8
-  store ptr %9, ptr %4, align 8
-  %10 = load ptr, ptr %3, align 8
-  %11 = load ptr, ptr %10, align 8
-  %12 = call ptr @llvm.load.relative.i32(ptr %11, i32 0)
-  call void %12(ptr noundef nonnull align 8 dereferenceable(12) %10)
-  %13 = load ptr, ptr %4, align 8
-  %14 = load ptr, ptr %13, align 8
-  %15 = call ptr @llvm.load.relative.i32(ptr %14, i32 0)
-  call void %15(ptr noundef nonnull align 8 dereferenceable(12) %13)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z18construct_and_callv() #0 {
-  %1 = alloca %struct.C, i64 1, align 8
-  %2 = alloca ptr, i64 1, align 8
-  %3 = alloca ptr, i64 1, align 8
-  call void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %1) #3
-  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
-  store ptr %1, ptr %2, align 8
-  %4 = icmp eq ptr %1, null
-  %5 = getelementptr i8, ptr %1, i32 16
-  %6 = select i1 %4, ptr %1, ptr %5
-  store ptr %6, ptr %3, align 8
-  %7 = load ptr, ptr %2, align 8
-  %8 = load ptr, ptr %7, align 8
-  %9 = call ptr @llvm.load.relative.i32(ptr %8, i32 0)
-  call void %9(ptr noundef nonnull align 8 dereferenceable(12) %7)
-  %10 = load ptr, ptr %3, align 8
-  %11 = load ptr, ptr %10, align 8
-  %12 = call ptr @llvm.load.relative.i32(ptr %11, i32 0)
-  call void %12(ptr noundef nonnull align 8 dereferenceable(12) %10)
-  ret void
-}
-
-; Function Attrs: noinline
-define linkonce_odr void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1A, i64 8), ptr %3, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define linkonce_odr void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1B, i64 8), ptr %3, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define linkonce_odr void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %3) #3
-  %4 = getelementptr i8, ptr %3, i32 16
-  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %4) #3
-  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 8), ptr %3, align 8
-  %5 = getelementptr i8, ptr %3, i32 16
-  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 24), ptr %5, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define linkonce_odr void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  call void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %3) #3
-  ret void
-}
-
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
-declare ptr @llvm.load.relative.i32(ptr, i32) #2
-
-attributes #0 = { noinline "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
-attributes #1 = { noinline }
-attributes #2 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) }
-attributes #3 = { nounwind }
-
-!llvm.module.flags = !{!0}
-
-!0 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/cir.ll b/cir.ll
deleted file mode 100644
index 48f99824f98a0..0000000000000
--- a/cir.ll
+++ /dev/null
@@ -1,215 +0,0 @@
-; ModuleID = '/data00/home/xiongzile/workspace/llvm-worktree/llvm-project/1.cpp'
-source_filename = "/data00/home/xiongzile/workspace/llvm-worktree/llvm-project/1.cpp"
-target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
-target triple = "x86_64-unknown-linux-gnu"
-
-%struct.C = type { %struct.A.base, [4 x i8], %struct.B.base, i32 }
-%struct.A.base = type <{ ptr, i32 }>
-%struct.B.base = type <{ ptr, i32 }>
-
- at _ZTV1A = global { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1A, ptr @_ZN1A1fEv] }, align 8
- at _ZTVN10__cxxabiv117__class_type_infoE = external global ptr, align 8
- at _ZTS1A = global [3 x i8] c"1A\00", align 1
- at _ZTI1A = constant { ptr, ptr } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 16), ptr @_ZTS1A }, align 8
- at _ZTV1B = global { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1B1gEv] }, align 8
- at _ZTS1B = global [3 x i8] c"1B\00", align 1
- at _ZTI1B = constant { ptr, ptr } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 16), ptr @_ZTS1B }, align 8
- at _ZTV1C = global { [4 x ptr], [3 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1C, ptr @_ZN1C1fEv, ptr @_ZN1C1gEv], [3 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr @_ZTI1C, ptr @_ZThn16_N1C1gEv] }, align 8
- at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global ptr, align 8
- at _ZTS1C = global [3 x i8] c"1C\00", align 1
- at _ZTI1C = constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr (i8, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 16), ptr @_ZTS1C, i32 0, i32 2, ptr @_ZTI1A, i64 2, ptr @_ZTI1B, i64 4098 }, align 8
-
-; Function Attrs: noinline
-define dso_local void @_ZN1A1fEv(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_ZN1B1gEv(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_ZThn16_N1C1gEv(ptr noundef %0) #1 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  %4 = getelementptr i8, ptr %3, i64 -16
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %4)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z14call_through_AP1A(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  %4 = load ptr, ptr %3, align 8
-  %5 = getelementptr inbounds ptr, ptr %4, i32 0
-  %6 = load ptr, ptr %5, align 8
-  call void %6(ptr noundef nonnull align 8 dereferenceable(12) %3)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z14call_through_BP1B(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  %4 = load ptr, ptr %3, align 8
-  %5 = getelementptr inbounds ptr, ptr %4, i32 0
-  %6 = load ptr, ptr %5, align 8
-  call void %6(ptr noundef nonnull align 8 dereferenceable(12) %3)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z14call_through_CP1C(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  %4 = load ptr, ptr %3, align 8
-  %5 = getelementptr inbounds ptr, ptr %4, i32 0
-  %6 = load ptr, ptr %5, align 8
-  call void %6(ptr noundef nonnull align 8 dereferenceable(32) %3)
-  %7 = load ptr, ptr %2, align 8
-  %8 = load ptr, ptr %7, align 8
-  %9 = getelementptr inbounds ptr, ptr %8, i32 1
-  %10 = load ptr, ptr %9, align 8
-  call void %10(ptr noundef nonnull align 8 dereferenceable(32) %7)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z21direct_qualified_callP1C(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %3)
-  %4 = load ptr, ptr %2, align 8
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %4)
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z10base_castsP1C(ptr noundef %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  %3 = alloca ptr, i64 1, align 8
-  %4 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %5 = load ptr, ptr %2, align 8
-  store ptr %5, ptr %3, align 8
-  %6 = load ptr, ptr %2, align 8
-  %7 = icmp eq ptr %6, null
-  %8 = getelementptr i8, ptr %6, i32 16
-  %9 = select i1 %7, ptr %6, ptr %8
-  store ptr %9, ptr %4, align 8
-  %10 = load ptr, ptr %3, align 8
-  %11 = load ptr, ptr %10, align 8
-  %12 = getelementptr inbounds ptr, ptr %11, i32 0
-  %13 = load ptr, ptr %12, align 8
-  call void %13(ptr noundef nonnull align 8 dereferenceable(12) %10)
-  %14 = load ptr, ptr %4, align 8
-  %15 = load ptr, ptr %14, align 8
-  %16 = getelementptr inbounds ptr, ptr %15, i32 0
-  %17 = load ptr, ptr %16, align 8
-  call void %17(ptr noundef nonnull align 8 dereferenceable(12) %14)
-  ret void
-}
-
-; Function Attrs: noinline
-define linkonce_odr void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1A, i64 16), ptr %3, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define linkonce_odr void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1B, i64 16), ptr %3, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define linkonce_odr void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %3) #2
-  %4 = getelementptr i8, ptr %3, i32 16
-  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %4) #2
-  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 16), ptr %3, align 8
-  %5 = getelementptr i8, ptr %3, i32 16
-  store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 48), ptr %5, align 8
-  ret void
-}
-
-; Function Attrs: noinline
-define linkonce_odr void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %0) #0 {
-  %2 = alloca ptr, i64 1, align 8
-  store ptr %0, ptr %2, align 8
-  %3 = load ptr, ptr %2, align 8
-  call void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %3) #2
-  ret void
-}
-
-; Function Attrs: noinline
-define dso_local void @_Z18construct_and_callv() #0 {
-  %1 = alloca %struct.C, i64 1, align 8
-  %2 = alloca ptr, i64 1, align 8
-  %3 = alloca ptr, i64 1, align 8
-  call void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %1) #2
-  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
-  store ptr %1, ptr %2, align 8
-  %4 = icmp eq ptr %1, null
-  %5 = getelementptr i8, ptr %1, i32 16
-  %6 = select i1 %4, ptr %1, ptr %5
-  store ptr %6, ptr %3, align 8
-  %7 = load ptr, ptr %2, align 8
-  %8 = load ptr, ptr %7, align 8
-  %9 = getelementptr inbounds ptr, ptr %8, i32 0
-  %10 = load ptr, ptr %9, align 8
-  call void %10(ptr noundef nonnull align 8 dereferenceable(12) %7)
-  %11 = load ptr, ptr %3, align 8
-  %12 = load ptr, ptr %11, align 8
-  %13 = getelementptr inbounds ptr, ptr %12, i32 0
-  %14 = load ptr, ptr %13, align 8
-  call void %14(ptr noundef nonnull align 8 dereferenceable(12) %11)
-  ret void
-}
-
-attributes #0 = { noinline "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
-attributes #1 = { noinline }
-attributes #2 = { nounwind }
-
-!llvm.module.flags = !{!0}
-
-!0 = !{i32 2, !"Debug Info Version", i32 3}
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index 1520999e3f85f..7c8de3d4a379e 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -893,6 +893,155 @@ def CIR_VTableAttr : CIR_ValueLikeAttr<"VTable", "vtable"> {
   }];
 }
 
+//===----------------------------------------------------------------------===//
+// RelativeVTableComponentAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_RelativeVTableComponentAttr
+    : CIR_ValueLikeAttr<"RelativeVTableComponent", "relative_vtable_component"> {
+  let summary = "Represents one C++ relative vtable component";
+
+  let description = [{
+    Represents one entry in a C++ relative vtable.
+
+    A relative vtable entry does not materialize the absolute address of the
+    referenced entity. Instead, it represents the 32-bit offset from the vtable
+    address point to the referenced symbol, optionally adjusted according to the
+    kind of vtable component being emitted.
+
+    The symbol identifies the referenced global, such as an RTTI proxy or a
+    function. The vtable address point identifies the address point used as the
+    base for computing the relative offset. The component kind records how the
+    entry should be interpreted during lowering, for example as an RTTI entry or
+    a function entry.
+
+    This attribute is intended for relative vtable initializers. Unlike
+    #cir.global_view, it does not produce a pointer-valued constant view of a
+    global. Its lowered value is a 32-bit integer relative-offset entry.
+
+    Example:
+    ```
+      cir.global linkonce_odr @_ZTV1A =
+        #cir.relative_vtable<{
+          #cir.const_array<[
+            #cir.int<0> : !s32i,
+            #cir.relative_vtable_component<@_ZTI1A.rtti_proxy, 0, 2, 0> : !s32i,
+            #cir.relative_vtable_component<@_ZN1A1fEv, 0, 2, 1> : !s32i
+          ]> : !cir.array<!s32i x 3>
+        }> : !rec_anon_struct
+    ```
+  }];
+
+  let parameters = (ins
+    AttributeSelfTypeParameter<"">:$type,
+    "mlir::FlatSymbolRefAttr":$symbol,
+    "unsigned":$vtableComponentIndex,
+    "unsigned":$vtableAddressPoint,
+    "uint32_t":$componentKind // 0 -> RTTI 1 -> Virtual function pointer(TODO: magic number)
+  );
+
+  let builders = [
+    AttrBuilderWithInferredContext<(ins
+      "mlir::Type":$type,
+      "mlir::FlatSymbolRefAttr":$symbol,
+      "unsigned":$vtableComponentIndex,
+      "unsigned":$vtableAddressPoint,
+      "uint32_t":$componentKind
+    ), [{
+      return $_get(type.getContext(), type, symbol, vtableComponentIndex, vtableAddressPoint,
+                   componentKind);
+    }]>
+  ];
+
+  let genVerifyDecl = 1;
+
+  let assemblyFormat = [{
+    `<`
+      $symbol `,`
+      $vtableComponentIndex `,`
+      $vtableAddressPoint `,`
+      $componentKind
+    `>`
+  }];
+}
+
+//===----------------------------------------------------------------------===//
+// RelativeVTableAttr
+//===----------------------------------------------------------------------===//
+
+def CIR_RelativeVTableAttr : CIR_ValueLikeAttr<"RelativeVTable", "relative_vtable"> {
+  let summary = "Represents a C++ relative vtable";
+  let description = [{
+    Represents the initializer for a C++ relative vtable global.
+
+    The attribute stores the constant elements of the relative vtable object.
+    The associated attribute type is the anonymous record type of the global.
+    The data parameter contains the record members, where each member is a
+    constant array corresponding to one vtable component.
+
+    Unlike a regular vtable, a relative vtable does not store absolute pointers
+    in its entries. Each vtable entry is represented as a 32-bit relative offset.
+    The offset is interpreted relative to the address point of vtable.
+
+    In the common single-inheritance case, the anonymous record type contains a
+    single array corresponding to the primary vtable for one class. In multiple
+    inheritance cases, the anonymous record may contain multiple arrays, each
+    representing a distinct vtable component within the same vtable object.
+
+    Example 1 (single vtable):
+    ```
+    cir.global linkonce_odr @_ZTV6Mother =
+      #cir.relative_vtable<{
+        #cir.const_array<[
+          #cir.int<0> : !s32i,
+          #cir.int<...> : !s32i,
+          #cir.int<...> : !s32i,
+          #cir.int<...> : !s32i
+        ]> : !cir.array<!s32i x 4>
+      }> : !rec_anon_struct1
+    ```
+
+    Example 2 (multiple vtables):
+    ```
+    cir.global linkonce_odr @_ZTV5Child =
+      #cir.relative_vtable<{
+        #cir.const_array<[
+          #cir.int<0> : !s32i,
+          #cir.int<...> : !s32i,
+          #cir.int<...> : !s32i,
+          #cir.int<...> : !s32i
+        ]> : !cir.array<!s32i x 4>,
+        #cir.const_array<[
+          #cir.int<-8> : !s32i,
+          #cir.int<...> : !s32i,
+          #cir.int<...> : !s32i
+        ]> : !cir.array<!s32i x 3>
+      }> : !rec_anon_struct2
+    ```
+  }];
+
+  // `type` is the anonymous record type of the relative vtable global.
+  // `data` contains the record members. Each member is expected to be a
+  // constant array of 32-bit relative-offset entries representing one vtable
+  // component.
+  let parameters = (ins
+    AttributeSelfTypeParameter<"">:$type,
+    "mlir::ArrayAttr":$data
+  );
+
+  let builders = [
+    AttrBuilderWithInferredContext<(ins "mlir::Type":$type,
+                                        "mlir::ArrayAttr":$data), [{
+      return $_get(type.getContext(), type, data);
+    }]>
+  ];
+
+  let genVerifyDecl = 1;
+  let assemblyFormat = [{
+    `<` custom<RecordMembers>($data) `>`
+  }];
+}
+
 //===----------------------------------------------------------------------===//
 // DynamicCastInfoAttr
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
index 70c9ff9492cf3..e4f14f44c1f0d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp
@@ -480,27 +480,18 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
   cir::GlobalOp vtable = getAddrOfVTable(rd, CharUnits());
   if (vtable.hasInitializer())
     return;
-  llvm::errs() << "vtable = ";
-  vtable->dump();
-  llvm::errs() << "";
+
   ItaniumVTableContext &vtContext = cgm.getItaniumVTableContext();
   const VTableLayout &vtLayout = vtContext.getVTableLayout(rd);
   cir::GlobalLinkageKind linkage = cgm.getVTableLinkage(rd);
   mlir::Attribute rtti =
       cgm.getAddrOfRTTIDescriptor(cgm.getLoc(rd->getBeginLoc()),
                                   cgm.getASTContext().getCanonicalTagType(rd));
-  llvm::errs() << "rtti = ";
-  rtti.dump();
-  llvm::errs() << "\n";
   // Classic codegen uses ConstantInitBuilder here, which is a very general
   // and feature-rich class to generate initializers for global values.
   // For now, this is using a simpler approach to create the initializer in CIR.
   cgvt.createVTableInitializer(vtable, vtLayout, rtti,
                                cir::isLocalLinkage(linkage));
-  llvm::errs() << "new vtable = ";
-  vtable->dump();
-  llvm::errs() << "\n";
-  llvm::errs() << "\n";
   // Set the correct linkage.
   vtable.setLinkage(linkage);
 
@@ -538,9 +529,10 @@ void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &cgvt,
                  "emitVTableDefinitions: WholeProgramVTables");
   }
 
-  // TODO: by @Elio
-  // assert(!cir::MissingFeatures::vtableRelativeLayout());
-  // if (cgm.getLangOpts().RelativeCXXABIVTables) {
+  // TODO: we don't try to generate alias here like:
+  // clang/lib/CodeGen/ItaniumCXXABI.cpp Instead, we create alias variable on
+  // LowerToLLVM assert(!cir::MissingFeatures::vtableRelativeLayout()); if
+  // (cgm.getLangOpts().RelativeCXXABIVTables) {
   //   cgm.errorNYI(rd->getSourceRange(), "vtableRelativeLayout");
   // }
 }
@@ -1243,16 +1235,14 @@ void CIRGenItaniumRTTIBuilder::buildVTablePointer(mlir::Location loc,
       cgm.getBuilder().getI32IntegerAttr(2)};
   auto indices = mlir::ArrayAttr::get(builder.getContext(), offsets);
   mlir::Attribute field{};
-  if (cgm.getLangOpts().RelativeCXXABIVTables) {
-    // TODO: by @Elio.
-    // For relative vtables, this needs special handling during lowering: the
-    // GlobalViewAttr target should be emitted as target - current slot.
-    auto symbol = mlir::FlatSymbolRefAttr::get(vTable.getSymNameAttr());
-    field = cir::GlobalViewAttr::get(builder.getSInt32Ty(), symbol, indices);
-  } else {
-    field = cgm.getBuilder().getGlobalViewAttr(cgm.getBuilder().getUInt8PtrTy(),
-                                               vTable, indices);
-  }
+
+  // We don't have to change this when support relative vtable.
+  // This is something like: @_ZTI1A = constant { ptr, ptr } { ptr getelementptr
+  // inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr
+  // @_ZTS1A }, align 8
+  // don't use relative offset here.
+  field = cgm.getBuilder().getGlobalViewAttr(cgm.getBuilder().getUInt8PtrTy(),
+                                             vTable, indices);
 
   assert(field && "expected attribute");
   fields.push_back(field);
@@ -1994,7 +1984,6 @@ CIRGenCallee CIRGenItaniumCXXABI::getVirtualFunctionPointer(
   return callee;
 }
 
-
 mlir::Value CIRGenItaniumCXXABI::getVTableAddressPointInStructorWithVTT(
     CIRGenFunction &cgf, const CXXRecordDecl *vtableClass, BaseSubobject base,
     const CXXRecordDecl *nearestVBase) {
@@ -2654,7 +2643,8 @@ static mlir::Value performTypeAdjustment(CIRGenFunction &cgf,
         cir::PtrStrideOp::create(builder, loc, i8PtrTy, vtablePtr,
                                  builder.getSInt64(virtualAdjustment, loc));
     if (cgf.cgm.getLangOpts().RelativeCXXABIVTables) {
-      assert(!cir::MissingFeatures::vtableRelativeLayout()); // performTypeAdjustment
+      assert(!cir::MissingFeatures::
+                 vtableRelativeLayout()); // performTypeAdjustment
       cgf.cgm.errorNYI("virtual adjustment for relative layout vtables");
     } else {
       offset = builder.createAlignedLoad(loc, cgf.ptrDiffTy, offsetPtr,
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
index a93cd23313c0e..aa6493ba83592 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp
@@ -53,7 +53,6 @@ static void setThunkProperties(CIRGenModule &cgm, const ThunkInfo &thunk,
 
 mlir::Type CIRGenModule::getVTableComponentType() {
   mlir::Type ty = builder.getUInt8PtrTy();
-  // assert(!cir::MissingFeatures::vtableRelativeLayout());
   if (getLangOpts().RelativeCXXABIVTables) {
     ty = builder.getSInt32Ty();
   }
@@ -144,7 +143,7 @@ mlir::Attribute CIRGenVTables::getVTableIntegerOrNullComponent(
 
   auto getOffsetAttr = [&](CharUnits offset) -> mlir::Attribute {
     if (isRelative) {
-      return cir::IntAttr::get(builder.getSInt32Ty(), offset.getQuantity());
+      return cir::IntAttr::get(getVTableComponentType(), offset.getQuantity());
     }
     return builder.getConstPtrAttr(builder.getUInt8PtrTy(),
                                    offset.getQuantity());
@@ -153,7 +152,7 @@ mlir::Attribute CIRGenVTables::getVTableIntegerOrNullComponent(
   switch (component.getKind()) {
   case VTableComponent::CK_UnusedFunctionPointer:
     if (isRelative)
-      return cir::IntAttr::get(builder.getSInt32Ty(), 0);
+      return cir::IntAttr::get(getVTableComponentType(), 0);
 
     return builder.getConstNullPtrAttr(builder.getUInt8PtrTy());
 
@@ -171,16 +170,33 @@ mlir::Attribute CIRGenVTables::getVTableIntegerOrNullComponent(
   }
 }
 
+cir::GlobalOp
+CIRGenVTables::getRTTIProxy(cir::GlobalOp &vtable, /* to calculate loc*/
+                            mlir::Attribute rtti) {
+  assert(dyn_cast<cir::GlobalViewAttr>(rtti));
+  cir::GlobalOp proxy;
+  if (auto r = dyn_cast<cir::GlobalViewAttr>(rtti)) {
+    llvm::SmallString<16> rttiProxyName(r.getSymbol().getValue());
+    rttiProxyName.append(".rtti_proxy");
+    proxy = cgm.createOrReplaceCXXRuntimeVariable(
+        vtable->getLoc(), rttiProxyName, r.getType(),
+        cir::GlobalLinkageKind::PrivateLinkage,
+        CharUnits::fromQuantity(
+            cgm.getDataLayout().getABITypeAlign(r.getType())));
+  }
+  cgm.setInitializer(proxy, rtti);
+  return proxy;
+}
+
 mlir::Attribute CIRGenVTables::getVTableComponent(
-    const VTableLayout &layout, unsigned componentIndex, mlir::Attribute rtti,
+    const VTableLayout &layout, cir::GlobalOp &vtableOp,
+    unsigned componentIndex, unsigned vtableIndex, mlir::Attribute rtti,
     unsigned &nextVTableThunkIndex, unsigned vtableAddressPoint,
     bool vtableHasLocalLinkage) {
   const VTableComponent &component = layout.vtable_components()[componentIndex];
 
   CIRGenBuilderTy builder = cgm.getBuilder();
 
-  // assert(!cir::MissingFeatures::vtableRelativeLayout());
-
   switch (component.getKind()) {
   case VTableComponent::CK_UnusedFunctionPointer:
   case VTableComponent::CK_VCallOffset:
@@ -190,14 +206,11 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
 
   case VTableComponent::CK_RTTI:
     if (cgm.getLangOpts().RelativeCXXABIVTables) {
-      if (auto gv = mlir::dyn_cast<cir::GlobalViewAttr>(rtti)) {
-        rtti = cir::GlobalViewAttr::get(builder.getSInt32Ty(), gv.getSymbol(),
-                                        gv.getIndices());
-      } else {
-        // For null RTTI / special cases. Adjust if ConstPtrAttr has meaningful
-        // non-zero cases in your path.
-        rtti = builder.getI32IntegerAttr(0);
-      }
+      cir::GlobalOp rttiProxy = getRTTIProxy(vtableOp, rtti);
+      return cir::RelativeVTableComponentAttr::get(
+          getVTableComponentType(),
+          mlir::FlatSymbolRefAttr::get(rttiProxy.getSymNameAttr()), vtableIndex,
+          vtableAddressPoint, /* RTTI */ 0);
     }
     return rtti;
 
@@ -249,6 +262,12 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
       fnPtr = cgm.getAddrOfFunction(gd, fnTy, /*ForVTable=*/true);
     }
 
+    if (cgm.getLangOpts().RelativeCXXABIVTables) {
+      return cir::RelativeVTableComponentAttr::get(
+          cgm.getBuilder().getSInt32Ty(),
+          mlir::FlatSymbolRefAttr::get(fnPtr.getSymNameAttr()), vtableIndex,
+          vtableAddressPoint, /* function pointer */ 1);
+    }
     return cir::GlobalViewAttr::get(
         getVTableComponentType(),
         mlir::FlatSymbolRefAttr::get(fnPtr.getSymNameAttr()));
@@ -278,9 +297,9 @@ void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp,
     llvm::SmallVector<mlir::Attribute> components;
     components.reserve(vtableEnd - vtableStart);
     for (size_t componentIndex : llvm::seq(vtableStart, vtableEnd)) {
-      components.push_back(
-          getVTableComponent(layout, componentIndex, rtti, nextVTableThunkIndex,
-                             addressPoint, vtableHasLocalLinkage));
+      components.push_back(getVTableComponent(
+          layout, vtableOp, componentIndex, vtableIndex, rtti,
+          nextVTableThunkIndex, addressPoint, vtableHasLocalLinkage));
     }
     // Create a ConstArrayAttr to hold the components.
     auto arr = cir::ConstArrayAttr::get(
@@ -293,12 +312,18 @@ void CIRGenVTables::createVTableInitializer(cir::GlobalOp &vtableOp,
   const auto members = mlir::ArrayAttr::get(mlirContext, vtables);
   cir::ConstRecordAttr record = cgm.getBuilder().getAnonConstRecord(members);
 
-  // Create a VTableAttr
-  auto vtableAttr = cir::VTableAttr::get(record.getType(), record.getMembers());
+  if (cgm.getLangOpts().RelativeCXXABIVTables) {
+    auto relativeVtableAttr =
+        cir::RelativeVTableAttr::get(record.getType(), record.getMembers());
+    cgm.setInitializer(vtableOp, relativeVtableAttr);
+  } else {
+    // Create a VTableAttr
+    auto vtableAttr =
+        cir::VTableAttr::get(record.getType(), record.getMembers());
 
-  // Add the vtable initializer to the vtable global op.
-  // CHECKME: by @Elio
-  cgm.setInitializer(vtableOp, vtableAttr);
+    // Add the vtable initializer to the vtable global op.
+    cgm.setInitializer(vtableOp, vtableAttr);
+  }
 }
 
 cir::GlobalOp CIRGenVTables::generateConstructionVTable(
@@ -321,7 +346,8 @@ cir::GlobalOp CIRGenVTables::generateConstructionVTable(
                            base.getBase(), out);
   SmallString<256> name(outName);
 
-  assert(!cir::MissingFeatures::vtableRelativeLayout()); // generateConstructionVTable
+  assert(!cir::MissingFeatures::
+             vtableRelativeLayout()); // generateConstructionVTable
 
   cir::RecordType vtType = getVTableType(*vtLayout);
 
@@ -356,7 +382,8 @@ cir::GlobalOp CIRGenVTables::generateConstructionVTable(
   cgm.setGVProperties(vtable, rd);
 
   assert(!cir::MissingFeatures::vtableEmitMetadata());
-  assert(!cir::MissingFeatures::vtableRelativeLayout()); // generateConstructionVTable
+  assert(!cir::MissingFeatures::
+             vtableRelativeLayout()); // generateConstructionVTable
 
   return vtable;
 }
diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.h b/clang/lib/CIR/CodeGen/CIRGenVTables.h
index 6011c1479fdb7..4eb4004c3a104 100644
--- a/clang/lib/CIR/CodeGen/CIRGenVTables.h
+++ b/clang/lib/CIR/CodeGen/CIRGenVTables.h
@@ -52,7 +52,8 @@ class CIRGenVTables {
   cir::FuncOp deletedVirtualFn = nullptr;
 
   mlir::Attribute
-  getVTableComponent(const VTableLayout &layout, unsigned componentIndex,
+  getVTableComponent(const VTableLayout &layout, cir::GlobalOp &vtableOp,
+                     unsigned componentIndex, unsigned vtableIndex,
                      mlir::Attribute rtti, unsigned &nextVTableThunkIndex,
                      unsigned vtableAddressPoint, bool vtableHasLocalLinkage);
 
@@ -119,6 +120,8 @@ class CIRGenVTables {
   /// arrays of pointers, with one struct element for each vtable in the vtable
   /// group.
   cir::RecordType getVTableType(const clang::VTableLayout &layout);
+
+  cir::GlobalOp getRTTIProxy(cir::GlobalOp &vtable, mlir::Attribute rtti);
 };
 
 } // namespace clang::CIRGen
diff --git a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
index 0f38c76c6bbc4..91b37004c8ff2 100644
--- a/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRAttrs.cpp
@@ -700,7 +700,7 @@ LogicalResult cir::VTableAttr::verify(
     return failure();
 
   for (const auto &element : data.getAsRange<mlir::Attribute>()) {
-    auto constArrayAttr = mlir::dyn_cast<cir::ConstArrayAttr>(element);
+    const auto &constArrayAttr = mlir::dyn_cast<cir::ConstArrayAttr>(element);
     if (!constArrayAttr)
       return emitError() << "expected constant array subtype";
 
@@ -708,13 +708,51 @@ LogicalResult cir::VTableAttr::verify(
     auto arrayElts = mlir::cast<ArrayAttr>(constArrayAttr.getElts());
     arrayElts.walkImmediateSubElements(
         [&](mlir::Attribute attr) {
-          if (mlir::isa<cir::ConstPtrAttr, cir::GlobalViewAttr,
-                        cir::IntAttr>(attr))
+          if (mlir::isa<ConstPtrAttr, GlobalViewAttr>(attr))
             return;
 
-          eltTypeCheck =
-              emitError()
-              << "expected GlobalViewAttr, ConstPtrAttr, or IntAttr";
+          eltTypeCheck = emitError()
+                         << "expected GlobalViewAttr or ConstPtrAttr";
+        },
+        [&](mlir::Type type) {});
+    if (eltTypeCheck.failed())
+      return eltTypeCheck;
+  }
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
+// CIR RelativeVTableAttr
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::RelativeVTableAttr::verify(
+    llvm::function_ref<mlir::InFlightDiagnostic()> emitError, mlir::Type type,
+    mlir::ArrayAttr data) {
+  auto sTy = mlir::dyn_cast_if_present<cir::RecordType>(type);
+  if (!sTy)
+    return emitError() << "expected !cir.record type result";
+
+  if (sTy.getMembers().empty() || data.empty())
+    return emitError() << "expected record type with one or more subtype";
+
+  if (cir::ConstRecordAttr::verify(emitError, type, data).failed())
+    return failure();
+
+  for (const auto &element : data.getAsRange<mlir::Attribute>()) {
+    const auto &constArrayAttr = mlir::dyn_cast<cir::ConstArrayAttr>(element);
+    if (!constArrayAttr)
+      return emitError() << "expected constant array subtype";
+
+    LogicalResult eltTypeCheck = success();
+    auto arrayElts = mlir::cast<mlir::ArrayAttr>(constArrayAttr.getElts());
+
+    arrayElts.walkImmediateSubElements(
+        [&](mlir::Attribute attr) {
+          if (mlir::isa<cir::IntAttr, cir::RelativeVTableComponentAttr>(attr))
+            return;
+
+          eltTypeCheck = emitError()
+                         << "expected IntAttr or RelativeVTableComponentAttr";
         },
         [&](mlir::Type type) {});
 
@@ -725,6 +763,17 @@ LogicalResult cir::VTableAttr::verify(
   return success();
 }
 
+//===----------------------------------------------------------------------===//
+// CIR RelativeVTableComponentAttr
+//===----------------------------------------------------------------------===//
+
+LogicalResult cir::RelativeVTableComponentAttr::verify(
+    llvm::function_ref<mlir::InFlightDiagnostic()>, mlir::Type,
+    mlir::FlatSymbolRefAttr, unsigned int, unsigned, unsigned int) {
+  // TODO by @Elio
+  return success();
+}
+
 //===----------------------------------------------------------------------===//
 // DynamicCastInfoAtttr definitions
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 47ccb291ea745..5401fefd50c98 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -497,6 +497,15 @@ static LogicalResult checkConstantTypes(mlir::Operation *op, mlir::Type opType,
                 cir::VTableAttr>(attrType))
     return success();
 
+  if (mlir::isa<cir::RelativeVTableAttr>(attrType)) {
+    auto at = mlir::cast<mlir::TypedAttr>(attrType);
+    if (at.getType() != opType)
+      return op->emitOpError("result type (")
+             << opType << ") does not match value type (" << at.getType()
+             << ")";
+    return success();
+  }
+
   assert(isa<TypedAttr>(attrType) && "What else could we be looking at here?");
   return op->emitOpError("global with type ")
          << cast<TypedAttr>(attrType).getType() << " not yet supported";
@@ -2129,8 +2138,9 @@ cir::VTableAddrPointOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   std::optional<mlir::Attribute> init = op.getInitialValue();
   if (!init)
     return success();
-  if (!isa<cir::VTableAttr>(*init))
-    return emitOpError("Expected #cir.vtable in initializer for global '")
+  if (!(isa<cir::VTableAttr>(*init) || isa<cir::RelativeVTableAttr>(*init)))
+    return emitOpError("Expected #cir.vtable or #cir.relative_vtable in "
+                       "initializer for global '")
            << name << "'";
   return success();
 }
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 2aaf2b9f946a3..75f44eaf185a8 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -691,6 +691,58 @@ mlir::Value CIRAttrToValue::visitCirAttr(cir::VTableAttr vtableArr) {
   return result;
 }
 
+// RelativeVTableAttr visitor.
+mlir::Value CIRAttrToValue::visitCirAttr(cir::RelativeVTableAttr rVtableArr) {
+  mlir::Type llvmTy = converter->convertType(rVtableArr.getType());
+  mlir::Location loc = parentOp->getLoc();
+  mlir::Value result = mlir::LLVM::UndefOp::create(rewriter, loc, llvmTy);
+
+  for (auto [idx, elt] : llvm::enumerate(rVtableArr.getData())) {
+    mlir::Value init = visit(elt);
+    result =
+        mlir::LLVM::InsertValueOp::create(rewriter, loc, result, init, idx);
+  }
+
+  return result;
+}
+
+// RelativeVTableAttr visitor.
+mlir::Value
+CIRAttrToValue::visitCirAttr(cir::RelativeVTableComponentAttr attr) {
+  mlir::Location loc = parentOp->getLoc();
+
+  auto i32Ty = mlir::IntegerType::get(rewriter.getContext(), 32);
+  auto i64Ty = mlir::IntegerType::get(rewriter.getContext(), 64);
+  auto ptrTy = mlir::LLVM::LLVMPointerType::get(rewriter.getContext());
+
+  auto globalOp = mlir::cast<cir::GlobalOp>(parentOp);
+
+  auto target = mlir::LLVM::AddressOfOp::create(rewriter, loc, ptrTy,
+                                                attr.getSymbol().getValue());
+
+  auto vtable = mlir::LLVM::AddressOfOp::create(rewriter, loc, ptrTy,
+                                                globalOp.getSymName());
+
+  auto vtableObjectTy = converter->convertType(globalOp.getSymType());
+
+  auto addressPoint = mlir::LLVM::GEPOp::create(
+      rewriter, loc, ptrTy, vtableObjectTy, vtable,
+      llvm::ArrayRef<mlir::LLVM::GEPArg>{0, attr.getVtableComponentIndex(),
+                                         attr.getVtableAddressPoint()},
+      mlir::LLVM::GEPNoWrapFlags::inbounds);
+  addressPoint->dump();
+
+  auto targetInt = mlir::LLVM::PtrToIntOp::create(rewriter, loc, i64Ty, target);
+
+  auto addressPointInt =
+      mlir::LLVM::PtrToIntOp::create(rewriter, loc, i64Ty, addressPoint);
+
+  auto diff = mlir::LLVM::SubOp::create(rewriter, loc, i64Ty, targetInt,
+                                        addressPointInt);
+
+  return mlir::LLVM::TruncOp::create(rewriter, loc, i32Ty, diff);
+}
+
 /// ZeroAttr visitor.
 mlir::Value CIRAttrToValue::visitCirAttr(cir::ZeroAttr attr) {
   mlir::Location loc = parentOp->getLoc();
@@ -2427,7 +2479,7 @@ CIRToLLVMGlobalOpLowering::matchAndRewriteRegionInitializedGlobal(
   assert((isa<cir::ConstArrayAttr, cir::ConstRecordAttr, cir::ConstVectorAttr,
               cir::ConstPtrAttr, cir::ConstComplexAttr, cir::GlobalViewAttr,
               cir::TypeInfoAttr, cir::UndefAttr, cir::PoisonAttr,
-              cir::VTableAttr, cir::ZeroAttr>(init)));
+              cir::VTableAttr, cir::ZeroAttr, cir::RelativeVTableAttr>(init)));
 
   // TODO(cir): once LLVM's dialect has proper equivalent attributes this
   // should be updated. For now, we use a custom op to initialize globals
@@ -2489,7 +2541,8 @@ mlir::LogicalResult CIRToLLVMGlobalOpLowering::matchAndRewrite(
                          cir::ConstRecordAttr, cir::ConstPtrAttr,
                          cir::ConstComplexAttr, cir::GlobalViewAttr,
                          cir::TypeInfoAttr, cir::UndefAttr, cir::PoisonAttr,
-                         cir::VTableAttr, cir::ZeroAttr>(init.value())) {
+                         cir::VTableAttr, cir::ZeroAttr,
+                         cir::RelativeVTableAttr>(init.value())) {
       // TODO(cir): once LLVM's dialect has proper equivalent attributes this
       // should be updated. For now, we use a custom op to initialize globals
       // to the appropriate value.
diff --git a/clang/test/CIR/IR/invalid-vtable.cir b/clang/test/CIR/IR/invalid-vtable.cir
index 2e880169abbfa..9fda20e8bbbb4 100644
--- a/clang/test/CIR/IR/invalid-vtable.cir
+++ b/clang/test/CIR/IR/invalid-vtable.cir
@@ -13,7 +13,7 @@ cir.func @reference_unknown_vtable() {
 !u32i = !cir.int<u, 32>
 cir.global linkonce_odr @_ZTT1D = #cir.const_array<[#cir.global_view<@_ZTV1D, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTC1D0_1B, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 2>
 cir.func @reference_non_vtable() {
-  // expected-error @below {{Expected #cir.vtable in initializer for global '_ZTT1D'}}
+  // expected-error @below {{Expected #cir.vtable or #cir.relative_vtable in initializer for global '_ZTT1D'}}
   %0 = cir.vtable.address_point(@_ZTT1D, address_point = <index = 0, offset = 2>) : !cir.vptr
   cir.return
 }
diff --git a/ll.ll b/ll.ll
deleted file mode 100644
index 7cae76a7071d9..0000000000000
--- a/ll.ll
+++ /dev/null
@@ -1,250 +0,0 @@
-; ModuleID = '1.cpp'
-source_filename = "1.cpp"
-target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
-target triple = "x86_64-unknown-linux-gnu"
-
-%struct.C = type { %struct.A.base, [4 x i8], %struct.B.base, i32 }
-%struct.A.base = type <{ ptr, i32 }>
-%struct.B.base = type <{ ptr, i32 }>
-
-$_ZN1CC1Ev = comdat any
-
-$_ZN1CC2Ev = comdat any
-
-$_ZN1AC2Ev = comdat any
-
-$_ZN1BC2Ev = comdat any
-
- at _ZTV1A = unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1A, ptr @_ZN1A1fEv] }, align 8
- at _ZTI1A = constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTS1A }, align 8
- at _ZTVN10__cxxabiv117__class_type_infoE = external global [0 x ptr]
- at _ZTS1A = constant [3 x i8] c"1A\00", align 1
- at _ZTV1B = unnamed_addr constant { [3 x ptr] } { [3 x ptr] [ptr null, ptr @_ZTI1B, ptr @_ZN1B1gEv] }, align 8
- at _ZTI1B = constant { ptr, ptr } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i64 2), ptr @_ZTS1B }, align 8
- at _ZTS1B = constant [3 x i8] c"1B\00", align 1
- at _ZTV1C = unnamed_addr constant { [4 x ptr], [3 x ptr] } { [4 x ptr] [ptr null, ptr @_ZTI1C, ptr @_ZN1C1fEv, ptr @_ZN1C1gEv], [3 x ptr] [ptr inttoptr (i64 -16 to ptr), ptr @_ZTI1C, ptr @_ZThn16_N1C1gEv] }, align 8
- at _ZTI1C = constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr inbounds (ptr, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i64 2), ptr @_ZTS1C, i32 0, i32 2, ptr @_ZTI1A, i64 2, ptr @_ZTI1B, i64 4098 }, align 8
- at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global [0 x ptr]
- at _ZTS1C = constant [3 x i8] c"1C\00", align 1
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_ZN1A1fEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_ZN1B1gEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  ret void
-}
-
-; Function Attrs: noinline nounwind optnone
-define dso_local void @_ZThn16_N1C1gEv(ptr noundef %this) unnamed_addr #1 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  %0 = getelementptr inbounds i8, ptr %this1, i64 -16
-  tail call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %0)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z14call_through_AP1A(ptr noundef %pa) #0 {
-entry:
-  %pa.addr = alloca ptr, align 8
-  store ptr %pa, ptr %pa.addr, align 8
-  %0 = load ptr, ptr %pa.addr, align 8
-  %vtable = load ptr, ptr %0, align 8
-  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
-  %1 = load ptr, ptr %vfn, align 8
-  call void %1(ptr noundef nonnull align 8 dereferenceable(12) %0)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z14call_through_BP1B(ptr noundef %pb) #0 {
-entry:
-  %pb.addr = alloca ptr, align 8
-  store ptr %pb, ptr %pb.addr, align 8
-  %0 = load ptr, ptr %pb.addr, align 8
-  %vtable = load ptr, ptr %0, align 8
-  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
-  %1 = load ptr, ptr %vfn, align 8
-  call void %1(ptr noundef nonnull align 8 dereferenceable(12) %0)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z14call_through_CP1C(ptr noundef %pc) #0 {
-entry:
-  %pc.addr = alloca ptr, align 8
-  store ptr %pc, ptr %pc.addr, align 8
-  %0 = load ptr, ptr %pc.addr, align 8
-  %vtable = load ptr, ptr %0, align 8
-  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
-  %1 = load ptr, ptr %vfn, align 8
-  call void %1(ptr noundef nonnull align 8 dereferenceable(32) %0)
-  %2 = load ptr, ptr %pc.addr, align 8
-  %vtable1 = load ptr, ptr %2, align 8
-  %vfn2 = getelementptr inbounds ptr, ptr %vtable1, i64 1
-  %3 = load ptr, ptr %vfn2, align 8
-  call void %3(ptr noundef nonnull align 8 dereferenceable(32) %2)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z21direct_qualified_callP1C(ptr noundef %pc) #0 {
-entry:
-  %pc.addr = alloca ptr, align 8
-  store ptr %pc, ptr %pc.addr, align 8
-  %0 = load ptr, ptr %pc.addr, align 8
-  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %0)
-  %1 = load ptr, ptr %pc.addr, align 8
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z10base_castsP1C(ptr noundef %pc) #0 {
-entry:
-  %pc.addr = alloca ptr, align 8
-  %pa = alloca ptr, align 8
-  %pb = alloca ptr, align 8
-  store ptr %pc, ptr %pc.addr, align 8
-  %0 = load ptr, ptr %pc.addr, align 8
-  store ptr %0, ptr %pa, align 8
-  %1 = load ptr, ptr %pc.addr, align 8
-  %2 = icmp eq ptr %1, null
-  br i1 %2, label %cast.end, label %cast.notnull
-
-cast.notnull:                                     ; preds = %entry
-  %add.ptr = getelementptr inbounds i8, ptr %1, i64 16
-  br label %cast.end
-
-cast.end:                                         ; preds = %cast.notnull, %entry
-  %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
-  store ptr %cast.result, ptr %pb, align 8
-  %3 = load ptr, ptr %pa, align 8
-  %vtable = load ptr, ptr %3, align 8
-  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
-  %4 = load ptr, ptr %vfn, align 8
-  call void %4(ptr noundef nonnull align 8 dereferenceable(12) %3)
-  %5 = load ptr, ptr %pb, align 8
-  %vtable1 = load ptr, ptr %5, align 8
-  %vfn2 = getelementptr inbounds ptr, ptr %vtable1, i64 0
-  %6 = load ptr, ptr %vfn2, align 8
-  call void %6(ptr noundef nonnull align 8 dereferenceable(12) %5)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z18construct_and_callv() #0 {
-entry:
-  %obj = alloca %struct.C, align 8
-  %pa = alloca ptr, align 8
-  %pb = alloca ptr, align 8
-  call void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %obj) #2
-  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %obj)
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %obj)
-  store ptr %obj, ptr %pa, align 8
-  %0 = icmp eq ptr %obj, null
-  br i1 %0, label %cast.end, label %cast.notnull
-
-cast.notnull:                                     ; preds = %entry
-  %add.ptr = getelementptr inbounds i8, ptr %obj, i64 16
-  br label %cast.end
-
-cast.end:                                         ; preds = %cast.notnull, %entry
-  %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
-  store ptr %cast.result, ptr %pb, align 8
-  %1 = load ptr, ptr %pa, align 8
-  %vtable = load ptr, ptr %1, align 8
-  %vfn = getelementptr inbounds ptr, ptr %vtable, i64 0
-  %2 = load ptr, ptr %vfn, align 8
-  call void %2(ptr noundef nonnull align 8 dereferenceable(12) %1)
-  %3 = load ptr, ptr %pb, align 8
-  %vtable1 = load ptr, ptr %3, align 8
-  %vfn2 = getelementptr inbounds ptr, ptr %vtable1, i64 0
-  %4 = load ptr, ptr %vfn2, align 8
-  call void %4(ptr noundef nonnull align 8 dereferenceable(12) %3)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define linkonce_odr void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 comdat align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  call void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %this1) #2
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define linkonce_odr void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 comdat align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this1) #2
-  %0 = getelementptr inbounds i8, ptr %this1, i64 16
-  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #2
-  store ptr getelementptr inbounds inrange(-16, 16) ({ [4 x ptr], [3 x ptr] }, ptr @_ZTV1C, i32 0, i32 0, i32 2), ptr %this1, align 8
-  %add.ptr = getelementptr inbounds i8, ptr %this1, i64 16
-  store ptr getelementptr inbounds inrange(-16, 8) ({ [4 x ptr], [3 x ptr] }, ptr @_ZTV1C, i32 0, i32 1, i32 2), ptr %add.ptr, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define linkonce_odr void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 comdat align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  store ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr] }, ptr @_ZTV1A, i32 0, i32 0, i32 2), ptr %this1, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define linkonce_odr void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 comdat align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  store ptr getelementptr inbounds inrange(-16, 8) ({ [3 x ptr] }, ptr @_ZTV1B, i32 0, i32 0, i32 2), ptr %this1, align 8
-  ret void
-}
-
-attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
-attributes #1 = { noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
-attributes #2 = { nounwind }
-
-!llvm.ident = !{!0}
-
-!0 = !{!"clang version 23.0.0git (git at github.com:xiongzile/llvm-project.git c1ef54f3f3c46982dd7dc03c41f66f8e274ac2e6)"}
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 2b603efadb476..8117d083aba71 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -2267,9 +2267,10 @@ AddressOfOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
   auto alias = dyn_cast_or_null<AliasOp>(symbol);
   auto ifunc = dyn_cast_or_null<IFuncOp>(symbol);
 
-  if (!global && !function && !alias && !ifunc)
+  if (!global && !function && !alias && !ifunc) {
     return emitOpError("must reference a global defined by 'llvm.mlir.global', "
                        "'llvm.mlir.alias' or 'llvm.func' or 'llvm.mlir.ifunc'");
+  }
 
   LLVMPointerType type = getType();
   if ((global && global.getAddrSpace() != type.getAddressSpace()) ||
diff --git a/rel-ll.ll b/rel-ll.ll
deleted file mode 100644
index 46c7a87b63872..0000000000000
--- a/rel-ll.ll
+++ /dev/null
@@ -1,259 +0,0 @@
-; ModuleID = '1.cpp'
-source_filename = "1.cpp"
-target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
-target triple = "x86_64-unknown-linux-gnu"
-
-%struct.C = type { %struct.A.base, [4 x i8], %struct.B.base, i32 }
-%struct.A.base = type <{ ptr, i32 }>
-%struct.B.base = type <{ ptr, i32 }>
-
-$_ZN1CC1Ev = comdat any
-
-$_ZN1CC2Ev = comdat any
-
-$_ZN1AC2Ev = comdat any
-
-$_ZN1BC2Ev = comdat any
-
-$_ZTI1A.rtti_proxy = comdat any
-
-$_ZTI1B.rtti_proxy = comdat any
-
-$_ZTI1C.rtti_proxy = comdat any
-
- at _ZTV1A.local = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint (ptr @_ZTI1A.rtti_proxy to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1A1fEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1A.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4
- at _ZTI1A = constant { ptr, ptr } { ptr getelementptr inbounds (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i32 8), ptr @_ZTS1A }, align 8
- at _ZTVN10__cxxabiv117__class_type_infoE = external global [0 x ptr]
- at _ZTS1A = constant [3 x i8] c"1A\00", align 1
- at _ZTI1A.rtti_proxy = linkonce_odr hidden unnamed_addr constant ptr @_ZTI1A, comdat
- at _ZTV1B.local = internal unnamed_addr constant { [3 x i32] } { [3 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint (ptr @_ZTI1B.rtti_proxy to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1B1gEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [3 x i32] }, ptr @_ZTV1B.local, i32 0, i32 0, i32 2) to i64)) to i32)] }, align 4
- at _ZTI1B = constant { ptr, ptr } { ptr getelementptr inbounds (i8, ptr @_ZTVN10__cxxabiv117__class_type_infoE, i32 8), ptr @_ZTS1B }, align 8
- at _ZTS1B = constant [3 x i8] c"1B\00", align 1
- at _ZTI1B.rtti_proxy = linkonce_odr hidden unnamed_addr constant ptr @_ZTI1B, comdat
- at _ZTV1C.local = internal unnamed_addr constant { [4 x i32], [3 x i32] } { [4 x i32] [i32 0, i32 trunc (i64 sub (i64 ptrtoint (ptr @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1C1fEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZN1C1gEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 0, i32 2) to i64)) to i32)], [3 x i32] [i32 -16, i32 trunc (i64 sub (i64 ptrtoint (ptr @_ZTI1C.rtti_proxy to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 1, i32 2) to i64)) to i32), i32 trunc (i64 sub (i64 ptrtoint (ptr dso_local_equivalent @_ZThn16_N1C1gEv to i64), i64 ptrtoint (ptr getelementptr inbounds ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 1, i32 2) to i64)) to i32)] }, align 4
- at _ZTI1C = constant { ptr, ptr, i32, i32, ptr, i64, ptr, i64 } { ptr getelementptr inbounds (i8, ptr @_ZTVN10__cxxabiv121__vmi_class_type_infoE, i32 8), ptr @_ZTS1C, i32 0, i32 2, ptr @_ZTI1A, i64 2, ptr @_ZTI1B, i64 4098 }, align 8
- at _ZTVN10__cxxabiv121__vmi_class_type_infoE = external global [0 x ptr]
- at _ZTS1C = constant [3 x i8] c"1C\00", align 1
- at _ZTI1C.rtti_proxy = linkonce_odr hidden unnamed_addr constant ptr @_ZTI1C, comdat
-
- at _ZTV1A = unnamed_addr alias { [3 x i32] }, ptr @_ZTV1A.local
- at _ZTV1B = unnamed_addr alias { [3 x i32] }, ptr @_ZTV1B.local
- at _ZTV1C = unnamed_addr alias { [4 x i32], [3 x i32] }, ptr @_ZTV1C.local
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_ZN1A1fEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_ZN1B1gEv(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  ret void
-}
-
-; Function Attrs: noinline nounwind optnone
-define dso_local void @_ZThn16_N1C1gEv(ptr noundef %this) unnamed_addr #1 align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  %0 = getelementptr inbounds i8, ptr %this1, i64 -16
-  tail call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %0)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z14call_through_AP1A(ptr noundef %pa) #0 {
-entry:
-  %pa.addr = alloca ptr, align 8
-  store ptr %pa, ptr %pa.addr, align 8
-  %0 = load ptr, ptr %pa.addr, align 8
-  %vtable = load ptr, ptr %0, align 8
-  %1 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
-  call void %1(ptr noundef nonnull align 8 dereferenceable(12) %0)
-  ret void
-}
-
-; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: read)
-declare ptr @llvm.load.relative.i32(ptr, i32) #2
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z14call_through_BP1B(ptr noundef %pb) #0 {
-entry:
-  %pb.addr = alloca ptr, align 8
-  store ptr %pb, ptr %pb.addr, align 8
-  %0 = load ptr, ptr %pb.addr, align 8
-  %vtable = load ptr, ptr %0, align 8
-  %1 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
-  call void %1(ptr noundef nonnull align 8 dereferenceable(12) %0)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z14call_through_CP1C(ptr noundef %pc) #0 {
-entry:
-  %pc.addr = alloca ptr, align 8
-  store ptr %pc, ptr %pc.addr, align 8
-  %0 = load ptr, ptr %pc.addr, align 8
-  %vtable = load ptr, ptr %0, align 8
-  %1 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
-  call void %1(ptr noundef nonnull align 8 dereferenceable(32) %0)
-  %2 = load ptr, ptr %pc.addr, align 8
-  %vtable1 = load ptr, ptr %2, align 8
-  %3 = call ptr @llvm.load.relative.i32(ptr %vtable1, i32 4)
-  call void %3(ptr noundef nonnull align 8 dereferenceable(32) %2)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z21direct_qualified_callP1C(ptr noundef %pc) #0 {
-entry:
-  %pc.addr = alloca ptr, align 8
-  store ptr %pc, ptr %pc.addr, align 8
-  %0 = load ptr, ptr %pc.addr, align 8
-  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %0)
-  %1 = load ptr, ptr %pc.addr, align 8
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %1)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z10base_castsP1C(ptr noundef %pc) #0 {
-entry:
-  %pc.addr = alloca ptr, align 8
-  %pa = alloca ptr, align 8
-  %pb = alloca ptr, align 8
-  store ptr %pc, ptr %pc.addr, align 8
-  %0 = load ptr, ptr %pc.addr, align 8
-  store ptr %0, ptr %pa, align 8
-  %1 = load ptr, ptr %pc.addr, align 8
-  %2 = icmp eq ptr %1, null
-  br i1 %2, label %cast.end, label %cast.notnull
-
-cast.notnull:                                     ; preds = %entry
-  %add.ptr = getelementptr inbounds i8, ptr %1, i32 16
-  br label %cast.end
-
-cast.end:                                         ; preds = %cast.notnull, %entry
-  %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
-  store ptr %cast.result, ptr %pb, align 8
-  %3 = load ptr, ptr %pa, align 8
-  %vtable = load ptr, ptr %3, align 8
-  %4 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
-  call void %4(ptr noundef nonnull align 8 dereferenceable(12) %3)
-  %5 = load ptr, ptr %pb, align 8
-  %vtable1 = load ptr, ptr %5, align 8
-  %6 = call ptr @llvm.load.relative.i32(ptr %vtable1, i32 0)
-  call void %6(ptr noundef nonnull align 8 dereferenceable(12) %5)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define dso_local void @_Z18construct_and_callv() #0 {
-entry:
-  %obj = alloca %struct.C, align 8
-  %pa = alloca ptr, align 8
-  %pb = alloca ptr, align 8
-  call void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %obj) #3
-  call void @_ZN1C1fEv(ptr noundef nonnull align 8 dereferenceable(32) %obj)
-  call void @_ZN1C1gEv(ptr noundef nonnull align 8 dereferenceable(32) %obj)
-  store ptr %obj, ptr %pa, align 8
-  %0 = icmp eq ptr %obj, null
-  br i1 %0, label %cast.end, label %cast.notnull
-
-cast.notnull:                                     ; preds = %entry
-  %add.ptr = getelementptr inbounds i8, ptr %obj, i32 16
-  br label %cast.end
-
-cast.end:                                         ; preds = %cast.notnull, %entry
-  %cast.result = phi ptr [ %add.ptr, %cast.notnull ], [ null, %entry ]
-  store ptr %cast.result, ptr %pb, align 8
-  %1 = load ptr, ptr %pa, align 8
-  %vtable = load ptr, ptr %1, align 8
-  %2 = call ptr @llvm.load.relative.i32(ptr %vtable, i32 0)
-  call void %2(ptr noundef nonnull align 8 dereferenceable(12) %1)
-  %3 = load ptr, ptr %pb, align 8
-  %vtable1 = load ptr, ptr %3, align 8
-  %4 = call ptr @llvm.load.relative.i32(ptr %vtable1, i32 0)
-  call void %4(ptr noundef nonnull align 8 dereferenceable(12) %3)
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define linkonce_odr void @_ZN1CC1Ev(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 comdat align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  call void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %this1) #3
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define linkonce_odr void @_ZN1CC2Ev(ptr noundef nonnull align 8 dereferenceable(32) %this) unnamed_addr #0 comdat align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  call void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this1) #3
-  %0 = getelementptr inbounds i8, ptr %this1, i64 16
-  call void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %0) #3
-  store ptr getelementptr inbounds inrange(-8, 8) ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 0, i32 2), ptr %this1, align 8
-  %add.ptr = getelementptr inbounds i8, ptr %this1, i32 16
-  store ptr getelementptr inbounds inrange(-8, 4) ({ [4 x i32], [3 x i32] }, ptr @_ZTV1C.local, i32 0, i32 1, i32 2), ptr %add.ptr, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define linkonce_odr void @_ZN1AC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 comdat align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  store ptr getelementptr inbounds inrange(-8, 4) ({ [3 x i32] }, ptr @_ZTV1A.local, i32 0, i32 0, i32 2), ptr %this1, align 8
-  ret void
-}
-
-; Function Attrs: mustprogress noinline nounwind optnone
-define linkonce_odr void @_ZN1BC2Ev(ptr noundef nonnull align 8 dereferenceable(12) %this) unnamed_addr #0 comdat align 2 {
-entry:
-  %this.addr = alloca ptr, align 8
-  store ptr %this, ptr %this.addr, align 8
-  %this1 = load ptr, ptr %this.addr, align 8
-  store ptr getelementptr inbounds inrange(-8, 4) ({ [3 x i32] }, ptr @_ZTV1B.local, i32 0, i32 0, i32 2), ptr %this1, align 8
-  ret void
-}
-
-attributes #0 = { mustprogress noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
-attributes #1 = { noinline nounwind optnone "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+cx8,+mmx,+sse,+sse2,+x87" }
-attributes #2 = { nocallback nofree nosync nounwind willreturn memory(argmem: read) }
-attributes #3 = { nounwind }
-
-!llvm.ident = !{!0}
-
-!0 = !{!"clang version 23.0.0git (git at github.com:xiongzile/llvm-project.git c1ef54f3f3c46982dd7dc03c41f66f8e274ac2e6)"}



More information about the Mlir-commits mailing list