diff --git a/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp b/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp
index e5fda5a6bb15..b15b7f3aaf2e 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExprConst.cpp
@@ -736,12 +736,27 @@ bool ConstStructBuilder::Build(const APValue &Val, const RecordDecl *RD,
                                const CXXRecordDecl *VTableClass,
                                CharUnits Offset) {
   const ASTRecordLayout &Layout = CGM.getASTContext().getASTRecordLayout(RD);
-
   if (const CXXRecordDecl *CD = dyn_cast<CXXRecordDecl>(RD)) {
     // Add a vtable pointer, if we need one and it hasn't already been added.
-    if (Layout.hasOwnVFPtr())
-      llvm_unreachable("NYI");
-
+    if (Layout.hasOwnVFPtr()) {
+      CIRGenBuilderTy &builder = CGM.getBuilder();
+      cir::GlobalOp vtable =
+          CGM.getCXXABI().getAddrOfVTable(VTableClass, CharUnits());
+      clang::VTableLayout::AddressPointLocation addressPoint =
+          CGM.getItaniumVTableContext()
+              .getVTableLayout(VTableClass)
+              .getAddressPoint(BaseSubobject(CD, Offset));
+      assert(!cir::MissingFeatures::ptrAuth());
+      mlir::ArrayAttr indices = builder.getArrayAttr({
+          builder.getI32IntegerAttr(0),
+          builder.getI32IntegerAttr(addressPoint.VTableIndex),
+          builder.getI32IntegerAttr(addressPoint.AddressPointIndex),
+      });
+      cir::GlobalViewAttr vtableInit =
+          CGM.getBuilder().getGlobalViewAttr(vtable, indices);
+      if (!AppendBytes(Offset, vtableInit))
+        return false;
+    }
     // Accumulate and sort bases, in order to visit them in address order, which
     // may not be the same as declaration order.
     SmallVector<BaseInfo, 8> Bases;
diff --git a/clang/test/CIR/CodeGen/vtable-emission.cpp b/clang/test/CIR/CodeGen/vtable-emission.cpp
index f63a9fe3cd97..6691167488c5 100644
--- a/clang/test/CIR/CodeGen/vtable-emission.cpp
+++ b/clang/test/CIR/CodeGen/vtable-emission.cpp
@@ -1,15 +1,29 @@
 // RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
 // RUN: FileCheck --input-file=%t.cir %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir  -emit-llvm -o - %s \
+// RUN: | opt -S -passes=instcombine,mem2reg,simplifycfg -o %t.ll
+// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
 
 struct S {
   virtual void key();
   virtual void nonKey() {}
-};
+} sobj;
 
 void S::key() {}
 
+// CHECK-DAG: !ty_anon_struct1 = !cir.struct<struct  {!cir.array<!cir.ptr<!u8i> x 4>}>
+// CHECK-DAG: !ty_anon_struct2 = !cir.struct<struct  {!cir.ptr<!ty_anon_struct1>}>
+
 // The definition of the key function should result in the vtable being emitted.
 // CHECK: cir.global external @_ZTV1S = #cir.vtable
+// LLVM: @_ZTV1S = global { [4 x ptr] } { [4 x ptr]
+// LLVM-SAME: [ptr null, ptr @_ZTI1S, ptr @_ZN1S3keyEv, ptr @_ZN1S6nonKeyEv] }, align 8
+
+// CHECK: cir.global external @sobj = #cir.const_struct
+// CHECK-SAME: <{#cir.global_view<@_ZTV1S, [0 : i32, 0 : i32, 2 : i32]> :
+// CHECK-SAME: !cir.ptr<!ty_anon_struct1>}> : !ty_anon_struct2 {alignment = 8 : i64}
+// LLVM: @sobj = global { ptr } { ptr getelementptr inbounds
+// LLVM-SAME: ({ [4 x ptr] }, ptr @_ZTV1S, i32 0, i32 0, i32 2) }, align 8
 
 // The reference from the vtable should result in nonKey being emitted.
 // CHECK: cir.func linkonce_odr @_ZN1S6nonKeyEv({{.*}} {