[CIR] Add VTTAddrPointOp (#155048)

This adds the definition, verification, and lowering for CIR's
VTTAddrPointOp. This is a bit ahead of the current codegen
implementation, which doesn't yet have support for emitting VTT
definitions, but since this doesn't depend on any of the other work in
progress, it is being upstreamed in advance.
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index 8ab6020..2b7a709 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1907,6 +1907,70 @@
 }
 
 //===----------------------------------------------------------------------===//
+// VTTAddrPointOp
+//===----------------------------------------------------------------------===//
+
+def CIR_VTTAddrPointOp : CIR_Op<"vtt.address_point", [
+  Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>
+]> {
+  let summary = "Get the VTT address point";
+  let description = [{
+    The `vtt.address_point` operation retrieves an element from the virtual
+    table table (VTT), which is the address point of a C++ vtable. In virtual
+    inheritance, a set of internal `__vptr` members for an object are
+    initialized by this operation, which assigns an element from the VTT. The
+    initialization order is as follows:
+
+    The complete object constructors and destructors find the VTT,
+    via the mangled name of the VTT global variable. They pass the address of
+    the subobject's sub-VTT entry in the VTT as a second parameter
+    when calling the base object constructors and destructors.
+    The base object constructors and destructors use the address passed to
+    initialize the primary virtual pointer and virtual pointers that point to
+    the classes which either have virtual bases or override virtual functions
+    with a virtual step.
+
+    The first parameter is either the mangled name of VTT global variable
+    or the address of the subobject's sub-VTT entry in the VTT.
+    The second parameter `offset` provides a virtual step to adjust to
+    the actual address point of the vtable.
+
+    The return type is always a `!cir.ptr<!cir.ptr<void>>`.
+
+    Example:
+    ```mlir
+    cir.global linkonce_odr @_ZTV1B = ...
+    ...
+    %3 = cir.base_class_addr(%1 : !cir.ptr<!rec_D> nonnull) [0]
+             -> !cir.ptr<!rec_B>
+    %4 = cir.vtt.address_point @_ZTT1D, offset = 1
+             -> !cir.ptr<!cir.ptr<!void>>
+    cir.call @_ZN1BC2Ev(%3, %4)
+    ```
+    Or:
+    ```mlir
+    %7 = cir.vtt.address_point %3 : !cir.ptr<!cir.ptr<!void>>, offset = 1
+             -> !cir.ptr<!cir.ptr<!void>>
+    ```
+  }];
+
+  let arguments = (ins OptionalAttr<FlatSymbolRefAttr>:$name,
+                       Optional<CIR_AnyType>:$sym_addr,
+                       I32Attr:$offset);
+  let results = (outs CIR_PointerType:$addr);
+
+  let assemblyFormat = [{
+      ($name^)?
+      ($sym_addr^ `:` type($sym_addr))?
+      `,`
+      `offset` `=` $offset
+    `->` qualified(type($addr)) attr-dict
+  }];
+
+  let hasVerifier = 1;
+}
+
+//===----------------------------------------------------------------------===//
 // SetBitfieldOp
 //===----------------------------------------------------------------------===//
 
diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
index 8c53939..83fff09 100644
--- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
+++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp
@@ -1475,6 +1475,53 @@
 }
 
 //===----------------------------------------------------------------------===//
+// VTTAddrPointOp
+//===----------------------------------------------------------------------===//
+
+LogicalResult
+cir::VTTAddrPointOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
+  // VTT ptr is not coming from a symbol.
+  if (!getName())
+    return success();
+  StringRef name = *getName();
+
+  // Verify that the result type underlying pointer type matches the type of
+  // the referenced cir.global op.
+  auto op =
+      symbolTable.lookupNearestSymbolFrom<cir::GlobalOp>(*this, getNameAttr());
+  if (!op)
+    return emitOpError("'")
+           << name << "' does not reference a valid cir.global";
+  std::optional<mlir::Attribute> init = op.getInitialValue();
+  if (!init)
+    return success();
+  if (!isa<cir::ConstArrayAttr>(*init))
+    return emitOpError(
+               "Expected constant array in initializer for global VTT '")
+           << name << "'";
+  return success();
+}
+
+LogicalResult cir::VTTAddrPointOp::verify() {
+  // The operation uses either a symbol or a value to operate, but not both
+  if (getName() && getSymAddr())
+    return emitOpError("should use either a symbol or value, but not both");
+
+  // If not a symbol, stick with the concrete type used for getSymAddr.
+  if (getSymAddr())
+    return success();
+
+  mlir::Type resultType = getAddr().getType();
+  mlir::Type resTy = cir::PointerType::get(
+      cir::PointerType::get(cir::VoidType::get(getContext())));
+
+  if (resultType != resTy)
+    return emitOpError("result type must be ")
+           << resTy << ", but provided result type is " << resultType;
+  return success();
+}
+
+//===----------------------------------------------------------------------===//
 // FuncOp
 //===----------------------------------------------------------------------===//
 
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
index 9f0e4e6..03955dc 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
@@ -2451,7 +2451,8 @@
                CIRToLLVMVecTernaryOpLowering,
                CIRToLLVMVTableAddrPointOpLowering,
                CIRToLLVMVTableGetVPtrOpLowering,
-               CIRToLLVMVTableGetVirtualFnAddrOpLowering
+               CIRToLLVMVTableGetVirtualFnAddrOpLowering,
+               CIRToLLVMVTTAddrPointOpLowering
       // clang-format on
       >(converter, patterns.getContext());
 
@@ -2600,6 +2601,36 @@
   return mlir::success();
 }
 
+mlir::LogicalResult CIRToLLVMVTTAddrPointOpLowering::matchAndRewrite(
+    cir::VTTAddrPointOp op, OpAdaptor adaptor,
+    mlir::ConversionPatternRewriter &rewriter) const {
+  const mlir::Type resultType = getTypeConverter()->convertType(op.getType());
+  llvm::SmallVector<mlir::LLVM::GEPArg> offsets;
+  mlir::Type eltType;
+  mlir::Value llvmAddr = adaptor.getSymAddr();
+
+  if (op.getSymAddr()) {
+    if (op.getOffset() == 0) {
+      rewriter.replaceOp(op, {llvmAddr});
+      return mlir::success();
+    }
+
+    offsets.push_back(adaptor.getOffset());
+    eltType = mlir::IntegerType::get(resultType.getContext(), 8,
+                                     mlir::IntegerType::Signless);
+  } else {
+    llvmAddr = getValueForVTableSymbol(op, rewriter, getTypeConverter(),
+                                       op.getNameAttr(), eltType);
+    assert(eltType && "Shouldn't ever be missing an eltType here");
+    offsets.push_back(0);
+    offsets.push_back(adaptor.getOffset());
+  }
+  rewriter.replaceOpWithNewOp<mlir::LLVM::GEPOp>(
+      op, resultType, eltType, llvmAddr, offsets,
+      mlir::LLVM::GEPNoWrapFlags::inbounds);
+  return mlir::success();
+}
+
 mlir::LogicalResult CIRToLLVMStackSaveOpLowering::matchAndRewrite(
     cir::StackSaveOp op, OpAdaptor adaptor,
     mlir::ConversionPatternRewriter &rewriter) const {
diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
index 7b109c5..513ad378 100644
--- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
+++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
@@ -508,6 +508,16 @@
                   mlir::ConversionPatternRewriter &) const override;
 };
 
+class CIRToLLVMVTTAddrPointOpLowering
+    : public mlir::OpConversionPattern<cir::VTTAddrPointOp> {
+public:
+  using mlir::OpConversionPattern<cir::VTTAddrPointOp>::OpConversionPattern;
+
+  mlir::LogicalResult
+  matchAndRewrite(cir::VTTAddrPointOp op, OpAdaptor,
+                  mlir::ConversionPatternRewriter &) const override;
+};
+
 class CIRToLLVMStackSaveOpLowering
     : public mlir::OpConversionPattern<cir::StackSaveOp> {
 public:
diff --git a/clang/test/CIR/IR/invalid-vtable.cir b/clang/test/CIR/IR/invalid-vtable.cir
index 41ddd4c..2e88016 100644
--- a/clang/test/CIR/IR/invalid-vtable.cir
+++ b/clang/test/CIR/IR/invalid-vtable.cir
@@ -1,6 +1,5 @@
 // RUN: cir-opt %s -verify-diagnostics -split-input-file
 
-!s8i = !cir.int<s, 8>
 !u32i = !cir.int<u, 32>
 cir.func @reference_unknown_vtable() {
   // expected-error @below {{'cir.vtable.address_point' op 'some_vtable' does not reference a valid cir.global}}
@@ -13,7 +12,7 @@
 !u8i = !cir.int<u, 8>
 !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_unknown_vtable() {
+cir.func @reference_non_vtable() {
   // expected-error @below {{Expected #cir.vtable in initializer for global '_ZTT1D'}}
   %0 = cir.vtable.address_point(@_ZTT1D, address_point = <index = 0, offset = 2>) : !cir.vptr
   cir.return
@@ -82,3 +81,54 @@
   cir.func private dso_local @_ZN1S6nonKeyEv(%arg0: !cir.ptr<!rec_S>)
   cir.func private dso_local @_ZN2S23keyEv(%arg0: !cir.ptr<!rec_S2>)
 }
+
+// -----
+
+!u32i = !cir.int<u, 32>
+!void = !cir.void
+cir.func @reference_unknown_vtt() {
+  // expected-error @below {{'cir.vtt.address_point' op 'some_vtt' does not reference a valid cir.global}}
+  %0 = cir.vtt.address_point @some_vtt, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+  cir.return
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+!u32i = !cir.int<u, 32>
+!void = !cir.void
+!rec_anon_struct = !cir.record<struct  {!cir.array<!cir.ptr<!u8i> x 4>}>
+cir.global external @_ZTV1S = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S3keyEv> : !cir.ptr<!u8i>, #cir.global_view<@_ZN1S6nonKeyEv> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 4>}> : !rec_anon_struct {alignment = 8 : i64}
+cir.func @reference_non_vtt() {
+  // expected-error @below {{'cir.vtt.address_point' op Expected constant array in initializer for global VTT '_ZTV1S'}}
+  %0 = cir.vtt.address_point @_ZTV1S, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+  cir.return
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+!u32i = !cir.int<u, 32>
+!void = !cir.void
+!rec_anon_struct = !cir.record<struct  {!cir.array<!cir.ptr<!u8i> x 4>}>
+!rec_C = !cir.record<class "C" {!cir.vptr}>
+cir.global linkonce_odr @_ZTT1C = #cir.const_array<[#cir.global_view<@_ZTV1C, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTC1C0_1B, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 2> {alignment = 8 : i64}
+cir.func @reference_name_and_value(%arg0: !cir.ptr<!rec_C>, %arg1: !cir.ptr<!cir.ptr<!void>>) {
+  // expected-error @below {{'cir.vtt.address_point' op should use either a symbol or value, but not both}}
+  %0 = cir.vtt.address_point @_ZTT1C %arg1 : !cir.ptr<!cir.ptr<!void>>, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+  cir.return
+}
+
+// -----
+
+!u8i = !cir.int<u, 8>
+!u32i = !cir.int<u, 32>
+!void = !cir.void
+!rec_anon_struct = !cir.record<struct  {!cir.array<!cir.ptr<!u8i> x 4>}>
+!rec_C = !cir.record<class "C" {!cir.vptr}>
+cir.global linkonce_odr @_ZTT1C = #cir.const_array<[#cir.global_view<@_ZTV1C, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTC1C0_1B, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 2> {alignment = 8 : i64}
+cir.func @bad_return_type_for_vtt_addrpoint() {
+  // expected-error @below {{result type must be '!cir.ptr<!cir.ptr<!cir.void>>', but provided result type is '!cir.ptr<!cir.int<u, 8>>'}}
+  %0 = cir.vtt.address_point @_ZTT1C, offset = 1 -> !cir.ptr<!u8i>
+  cir.return
+}
diff --git a/clang/test/CIR/IR/vtt-addrpoint.cir b/clang/test/CIR/IR/vtt-addrpoint.cir
new file mode 100644
index 0000000..f05bb78
--- /dev/null
+++ b/clang/test/CIR/IR/vtt-addrpoint.cir
@@ -0,0 +1,55 @@
+// RUN: cir-opt %s | FileCheck %s
+
+// Test the parsing and printing of the two forms of vtt.address_point op, as
+// they will appear in constructors.
+
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+!rec_A = !cir.record<struct "A" {!u8i}>
+!rec_B = !cir.record<struct "B" {!cir.vptr}>
+!rec_C = !cir.record<struct "C" {!rec_B}>
+!rec_anon_struct = !cir.record<struct  {!cir.array<!cir.ptr<!u8i> x 3>}>
+module {
+  cir.func private @_ZN1AC2Ev(!cir.ptr<!rec_A>)
+  cir.func private @_ZN1BC2Ev(!cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!void>>)
+  cir.func dso_local @_ZN1CC2Ev(%arg0: !cir.ptr<!rec_C>, %arg1: !cir.ptr<!cir.ptr<!void>>) {
+    %0 = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["this", init] {alignment = 8 : i64}
+    %1 = cir.alloca !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!cir.ptr<!cir.ptr<!void>>>, ["vtt", init] {alignment = 8 : i64}
+    cir.store %arg0, %0 : !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>
+    cir.store %arg1, %1 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!cir.ptr<!cir.ptr<!void>>>
+    %2 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!rec_C>
+    %3 = cir.load align(8) %1 : !cir.ptr<!cir.ptr<!cir.ptr<!void>>>, !cir.ptr<!cir.ptr<!void>>
+    %4 = cir.base_class_addr %2 : !cir.ptr<!rec_C> nonnull [0] -> !cir.ptr<!rec_B>
+
+    %5 = cir.vtt.address_point %3 : !cir.ptr<!cir.ptr<!void>>, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+    // CHECK: cir.vtt.address_point %{{.*}} : !cir.ptr<!cir.ptr<!void>>, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+
+    cir.call @_ZN1BC2Ev(%4, %5) : (!cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!void>>) -> ()
+    %6 = cir.vtt.address_point %3 : !cir.ptr<!cir.ptr<!void>>, offset = 0 -> !cir.ptr<!cir.ptr<!void>>
+    %7 = cir.cast(bitcast, %6 : !cir.ptr<!cir.ptr<!void>>), !cir.ptr<!cir.vptr>
+    %8 = cir.load align(8) %7 : !cir.ptr<!cir.vptr>, !cir.vptr
+    %9 = cir.vtable.get_vptr %2 : !cir.ptr<!rec_C> -> !cir.ptr<!cir.vptr>
+    cir.store align(8) %8, %9 : !cir.vptr, !cir.ptr<!cir.vptr>
+    cir.return
+  }
+  cir.global linkonce_odr dso_local @_ZTV1C = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 3>}> : !rec_anon_struct {alignment = 8 : i64}
+  cir.global linkonce_odr @_ZTT1C = #cir.const_array<[#cir.global_view<@_ZTV1C, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTC1C0_1B, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 2> {alignment = 8 : i64}
+  cir.func dso_local @_ZN1CC1Ev(%arg0: !cir.ptr<!rec_C>) {
+    %0 = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["this", init] {alignment = 8 : i64}
+    cir.store %arg0, %0 : !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>
+    %1 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!rec_C>
+    %2 = cir.base_class_addr %1 : !cir.ptr<!rec_C> nonnull [0] -> !cir.ptr<!rec_A>
+    cir.call @_ZN1AC2Ev(%2) : (!cir.ptr<!rec_A>) -> ()
+    %3 = cir.base_class_addr %1 : !cir.ptr<!rec_C> nonnull [0] -> !cir.ptr<!rec_B>
+
+    %4 = cir.vtt.address_point @_ZTT1C, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+    // CHECK: cir.vtt.address_point @_ZTT1C, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+
+    cir.call @_ZN1BC2Ev(%3, %4) : (!cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!void>>) -> ()
+    %5 = cir.vtable.address_point(@_ZTV1C, address_point = <index = 0, offset = 3>) : !cir.vptr
+    %6 = cir.vtable.get_vptr %1 : !cir.ptr<!rec_C> -> !cir.ptr<!cir.vptr>
+    cir.store align(8) %5, %6 : !cir.vptr, !cir.ptr<!cir.vptr>
+    cir.return
+  }
+  cir.global linkonce_odr dso_local @_ZTC1C0_1B = #cir.const_record<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 3>}> : !rec_anon_struct {alignment = 8 : i64}
+}
diff --git a/clang/test/CIR/Lowering/vtt-addrpoint.cir b/clang/test/CIR/Lowering/vtt-addrpoint.cir
new file mode 100644
index 0000000..a3e7271
--- /dev/null
+++ b/clang/test/CIR/Lowering/vtt-addrpoint.cir
@@ -0,0 +1,59 @@
+// RUN: cir-translate %s -cir-to-llvmir --target x86_64-unknown-linux-gnu -o %t.ll
+// RUN: FileCheck %s --input-file=%t.ll
+
+// Test the lowering of the two forms of vtt.address_point op, as they will
+// appear in constructors.
+
+!u8i = !cir.int<u, 8>
+!void = !cir.void
+!rec_A = !cir.record<struct "A" {!u8i}>
+!rec_B = !cir.record<struct "B" {!cir.vptr}>
+!rec_C = !cir.record<struct "C" {!rec_B}>
+!rec_anon_struct = !cir.record<struct  {!cir.array<!cir.ptr<!u8i> x 3>}>
+module {
+  cir.func private @_ZN1AC2Ev(!cir.ptr<!rec_A>)
+  cir.func private @_ZN1BC2Ev(!cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!void>>)
+  cir.func dso_local @_ZN1CC2Ev(%arg0: !cir.ptr<!rec_C>, %arg1: !cir.ptr<!cir.ptr<!void>>) {
+    %0 = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["this", init] {alignment = 8 : i64}
+    %1 = cir.alloca !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!cir.ptr<!cir.ptr<!void>>>, ["vtt", init] {alignment = 8 : i64}
+    cir.store %arg0, %0 : !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>
+    cir.store %arg1, %1 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!cir.ptr<!cir.ptr<!void>>>
+    %2 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!rec_C>
+    %3 = cir.load align(8) %1 : !cir.ptr<!cir.ptr<!cir.ptr<!void>>>, !cir.ptr<!cir.ptr<!void>>
+    %4 = cir.base_class_addr %2 : !cir.ptr<!rec_C> nonnull [0] -> !cir.ptr<!rec_B>
+    %5 = cir.vtt.address_point %3 : !cir.ptr<!cir.ptr<!void>>, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+    cir.call @_ZN1BC2Ev(%4, %5) : (!cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!void>>) -> ()
+    %6 = cir.vtt.address_point %3 : !cir.ptr<!cir.ptr<!void>>, offset = 0 -> !cir.ptr<!cir.ptr<!void>>
+    %7 = cir.cast(bitcast, %6 : !cir.ptr<!cir.ptr<!void>>), !cir.ptr<!cir.vptr>
+    %8 = cir.load align(8) %7 : !cir.ptr<!cir.vptr>, !cir.vptr
+    %9 = cir.vtable.get_vptr %2 : !cir.ptr<!rec_C> -> !cir.ptr<!cir.vptr>
+    cir.store align(8) %8, %9 : !cir.vptr, !cir.ptr<!cir.vptr>
+    cir.return
+  }
+
+// CHECK: define{{.*}} void @_ZN1CC2Ev
+// CHECK:   %[[VTT:.*]] = getelementptr inbounds i8, ptr %{{.*}}, i32 1
+// CHECK:   call void @_ZN1BC2Ev(ptr %{{.*}}, ptr %[[VTT]])
+
+  cir.global linkonce_odr dso_local @_ZTV1C = #cir.vtable<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 3>}> : !rec_anon_struct {alignment = 8 : i64}
+  cir.global linkonce_odr @_ZTT1C = #cir.const_array<[#cir.global_view<@_ZTV1C, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>, #cir.global_view<@_ZTC1C0_1B, [0 : i32, 3 : i32]> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 2> {alignment = 8 : i64}
+  cir.func dso_local @_ZN1CC1Ev(%arg0: !cir.ptr<!rec_C>) {
+    %0 = cir.alloca !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>, ["this", init] {alignment = 8 : i64}
+    cir.store %arg0, %0 : !cir.ptr<!rec_C>, !cir.ptr<!cir.ptr<!rec_C>>
+    %1 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!rec_C>
+    %2 = cir.base_class_addr %1 : !cir.ptr<!rec_C> nonnull [0] -> !cir.ptr<!rec_A>
+    cir.call @_ZN1AC2Ev(%2) : (!cir.ptr<!rec_A>) -> ()
+    %3 = cir.base_class_addr %1 : !cir.ptr<!rec_C> nonnull [0] -> !cir.ptr<!rec_B>
+    %4 = cir.vtt.address_point @_ZTT1C, offset = 1 -> !cir.ptr<!cir.ptr<!void>>
+    cir.call @_ZN1BC2Ev(%3, %4) : (!cir.ptr<!rec_B>, !cir.ptr<!cir.ptr<!void>>) -> ()
+    %5 = cir.vtable.address_point(@_ZTV1C, address_point = <index = 0, offset = 3>) : !cir.vptr
+    %6 = cir.vtable.get_vptr %1 : !cir.ptr<!rec_C> -> !cir.ptr<!cir.vptr>
+    cir.store align(8) %5, %6 : !cir.vptr, !cir.ptr<!cir.vptr>
+    cir.return
+  }
+
+// CHECK: define{{.*}} void @_ZN1CC1Ev
+// CHECK:   store ptr getelementptr inbounds nuw (i8, ptr @_ZTV1C, i64 24), ptr %{{.*}}
+
+  cir.global linkonce_odr dso_local @_ZTC1C0_1B = #cir.const_record<{#cir.const_array<[#cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>, #cir.ptr<null> : !cir.ptr<!u8i>]> : !cir.array<!cir.ptr<!u8i> x 3>}> : !rec_anon_struct {alignment = 8 : i64}
+}