Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1906,6 +1906,70 @@ def CIR_VTableGetVirtualFnAddrOp : CIR_Op<"vtable.get_virtual_fn_addr", [
}];
}

//===----------------------------------------------------------------------===//
// 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
//===----------------------------------------------------------------------===//
Expand Down
47 changes: 47 additions & 0 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,53 @@ cir::VTableAddrPointOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
return success();
}

//===----------------------------------------------------------------------===//
// 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
//===----------------------------------------------------------------------===//
Expand Down
33 changes: 32 additions & 1 deletion clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2451,7 +2451,8 @@ void ConvertCIRToLLVMPass::runOnOperation() {
CIRToLLVMVecTernaryOpLowering,
CIRToLLVMVTableAddrPointOpLowering,
CIRToLLVMVTableGetVPtrOpLowering,
CIRToLLVMVTableGetVirtualFnAddrOpLowering
CIRToLLVMVTableGetVirtualFnAddrOpLowering,
CIRToLLVMVTTAddrPointOpLowering
// clang-format on
>(converter, patterns.getContext());

Expand Down Expand Up @@ -2600,6 +2601,36 @@ mlir::LogicalResult CIRToLLVMVTableGetVirtualFnAddrOpLowering::matchAndRewrite(
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 {
Expand Down
10 changes: 10 additions & 0 deletions clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,16 @@ class CIRToLLVMVTableGetVirtualFnAddrOpLowering
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:
Expand Down
54 changes: 52 additions & 2 deletions clang/test/CIR/IR/invalid-vtable.cir
Original file line number Diff line number Diff line change
@@ -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}}
Expand All @@ -13,7 +12,7 @@ cir.func @reference_unknown_vtable() {
!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
Expand Down Expand Up @@ -82,3 +81,54 @@ module {
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
}
55 changes: 55 additions & 0 deletions clang/test/CIR/IR/vtt-addrpoint.cir
Original file line number Diff line number Diff line change
@@ -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}
}
59 changes: 59 additions & 0 deletions clang/test/CIR/Lowering/vtt-addrpoint.cir
Original file line number Diff line number Diff line change
@@ -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}
}