Skip to content

[CIR] Add VTableAddrPointOp #148730

New issue

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

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

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
30 changes: 30 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
Original file line number Diff line number Diff line change
Expand Up @@ -515,5 +515,35 @@ def BitfieldInfoAttr : CIR_Attr<"BitfieldInfo", "bitfield_info"> {
];
}

//===----------------------------------------------------------------------===//
// AddressPointAttr
//===----------------------------------------------------------------------===//

def AddressPointAttr : CIR_Attr<"AddressPoint", "address_point"> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def AddressPointAttr : CIR_Attr<"AddressPoint", "address_point"> {
def CIR_AddressPointAttr : CIR_Attr<"AddressPoint", "address_point"> {

Copy link
Contributor

@xlauko xlauko Jul 15, 2025

Choose a reason for hiding this comment

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

Is there any reason to have this as a special attribute and not just two I32Attr in VTableAddrPointOp?

I scanned through incubator codebase and all uses I found it is always accessed directly to index or offset, e.g. op.getAddressPointAttr().getIndex(), op.getAddressPointAttr().getOffset().

Therefore, I believe we can remove the attribute entirely and use just op.getAddressPointIndex() and op.getAddressPointOffset(), or even simplify it to op.getIndex() and op.getOffset(), since it's clear from AddrPointOp that it deals with AddressPoint.

This can clean up address point op assembly as well to something like:

cir.vtable_address_point @_ZTV1B [index = 0, offset = 2] : !cir.ptr<!cir.ptr<() -> i32>>

^ This just my suggestion removing also some parenthesis and adding _ after vtable in op name.

Copy link
Member

Choose a reason for hiding this comment

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

There are other places that use address point in the incubator but are not using the attribute just yet (e.g. emitVTTDefinition). The idea was to unify those references so they are easy to understand (versus reading random integers in tables). It's possible we need more work to make them adequate to use though, so it's possible we don't need this until we can also cover relative layouts and understand all we really need.

let summary = "Address point attribute";

let description = [{
Attribute specifying the address point within a C++ virtual table (vtable).

The `index` (vtable index) parameter identifies which vtable to use within a
vtable group, while the `offset` (address point index) specifies the offset
within that vtable where the address begins.

Example:
```mlir
cir.global linkonce_odr @_ZTV1B = ...
...
%3 = cir.vtable.address_point(@_ZTV1B, address_point = <index = 0, offset = 2>)
: !cir.ptr<!cir.ptr<() -> i32>>
```
Comment on lines +526 to +538
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Attribute specifying the address point within a C++ virtual table (vtable).
The `index` (vtable index) parameter identifies which vtable to use within a
vtable group, while the `offset` (address point index) specifies the offset
within that vtable where the address begins.
Example:
```mlir
cir.global linkonce_odr @_ZTV1B = ...
...
%3 = cir.vtable.address_point(@_ZTV1B, address_point = <index = 0, offset = 2>)
: !cir.ptr<!cir.ptr<() -> i32>>
```
Attribute specifying the address point within a C++ virtual table (vtable).
The `index` (vtable index) parameter identifies which vtable to use within a
vtable group, while the `offset` (address point index) specifies the offset
within that vtable where the address begins.
Example:
```mlir
cir.global linkonce_odr @_ZTV1B = ...
...
%3 = cir.vtable.address_point(@_ZTV1B, address_point = <index = 0, offset = 2>)
: !cir.ptr<!cir.ptr<() -> i32>>
```

}];

let parameters = (ins "int32_t":$index,
"int32_t":$offset);

let assemblyFormat = [{
`<` struct($index, $offset) `>`
}];
}

#endif // LLVM_CLANG_CIR_DIALECT_IR_CIRATTRS_TD
45 changes: 45 additions & 0 deletions clang/include/clang/CIR/Dialect/IR/CIROps.td
Original file line number Diff line number Diff line change
Expand Up @@ -1669,6 +1669,51 @@ def GetGlobalOp : CIR_Op<"get_global",
}];
}

//===----------------------------------------------------------------------===//
// VTableAddrPointOp
//===----------------------------------------------------------------------===//

def VTableAddrPointOp : CIR_Op<"vtable.address_point",
[Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
Comment on lines +1676 to +1677
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
def VTableAddrPointOp : CIR_Op<"vtable.address_point",
[Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>]> {
def CIR_VTableAddrPointOp : CIR_Op<"vtable.address_point", [
Pure, DeclareOpInterfaceMethods<SymbolUserOpInterface>
]> {

let summary = "Get the vtable (global variable) address point";
let description = [{
The `vtable.address_point` operation retrieves the effective address
(address point) of a C++ virtual table. An object internal `__vptr`
gets initializated on top of the value returned by this operation.

`address_point.index` (vtable index) provides the appropriate vtable within
the vtable group (as specified by Itanium ABI), and `address_point.offset`
(address point index) the actual address point within that vtable.

The return type is always a `!cir.ptr<!cir.ptr<() -> i32>>`.

Example:
```mlir
cir.global linkonce_odr @_ZTV1B = ...
...
%3 = cir.vtable.address_point(@_ZTV1B, address_point = <index = 0,
offset = 2>) : !cir.ptr<!cir.ptr<() -> i32>>
```
}];

let arguments = (ins OptionalAttr<FlatSymbolRefAttr>:$name,
Optional<CIR_AnyType>:$sym_addr,
Copy link
Contributor

Choose a reason for hiding this comment

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

I believe this should not be CIR_AnyType; should it be CIR_PtrTo<CIR_AnyFuncType> instead? However, neither the description nor the example clarifies what is expected for sym_addr. It would be helpful to include that in the documentation as well.

AddressPointAttr:$address_point);
Comment on lines +1699 to +1701
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
let arguments = (ins OptionalAttr<FlatSymbolRefAttr>:$name,
Optional<CIR_AnyType>:$sym_addr,
AddressPointAttr:$address_point);
let arguments = (ins
OptionalAttr<FlatSymbolRefAttr>:$name,
Optional<CIR_AnyType>:$sym_addr,
AddressPointAttr:$address_point
);

let results = (outs Res<CIR_PointerType, "", []>:$addr);

let assemblyFormat = [{
`(`
($name^)?
($sym_addr^ `:` type($sym_addr))?
`,`
`address_point` `=` $address_point
`)`
`:` qualified(type($addr)) attr-dict
}];

let hasVerifier = 1;
}

//===----------------------------------------------------------------------===//
// SetBitfieldOp
//===----------------------------------------------------------------------===//
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ struct MissingFeatures {
static bool thunks() { return false; }
static bool tryEmitAsConstant() { return false; }
static bool typeChecks() { return false; }
static bool vtableInitializer() { return false; }
static bool weakRefReference() { return false; }
static bool writebacks() { return false; }

Expand Down
46 changes: 46 additions & 0 deletions clang/lib/CIR/Dialect/IR/CIRDialect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1338,6 +1338,52 @@ cir::GetGlobalOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
return success();
}

//===----------------------------------------------------------------------===//
// VTableAddrPointOp
//===----------------------------------------------------------------------===//

LogicalResult
cir::VTableAddrPointOp::verifySymbolUses(SymbolTableCollection &symbolTable) {
// vtable 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 or cir.func op.
auto op = dyn_cast_or_null<GlobalOp>(
symbolTable.lookupNearestSymbolFrom(*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();
assert(!cir::MissingFeatures::vtableInitializer());
return success();
}

LogicalResult cir::VTableAddrPointOp::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();
auto intTy = cir::IntType::get(getContext(), 32, /*isSigned=*/false);
auto fnTy = cir::FuncType::get({}, intTy);

auto resTy = cir::PointerType::get(cir::PointerType::get(fnTy));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I can tell, this is completely arbitrary. In the incubator, CIRGenBuildTy::getVirtualFnPtrType returns this type, but it has a comment suggesting that we should create a special vtable pointer type that more explicitly identifies what the pointer is. I'm working on an incubator patch to do just that.


if (resultType != resTy)
return emitOpError("result type must be ")
<< resTy << ", but provided result type is " << resultType << "";
return success();
}

//===----------------------------------------------------------------------===//
// FuncOp
//===----------------------------------------------------------------------===//
Expand Down
22 changes: 22 additions & 0 deletions clang/test/CIR/IR/invalid-vtable.cir
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// 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}}
%0 = cir.vtable.address_point(@some_vtable, address_point = <index = 0, offset = 2>) : !cir.ptr<!cir.ptr<!cir.func<() -> !u32i>>>
cir.return
}

// -----

!s8i = !cir.int<s, 8>
!u8i = !cir.int<u, 8>
!u64i = !cir.int<u, 64>
!rec_anon_struct = !cir.record<struct {!cir.array<!cir.ptr<!u8i> x 4>}>
cir.global "private" external @_ZTV1S : !rec_anon_struct {alignment = 8 : i64}
cir.func @incorrect_type() {
// expected-error @below {{'cir.vtable.address_point' op result type must be '!cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 32>>>>', but provided result type is '!cir.ptr<!cir.ptr<!cir.func<() -> !cir.int<u, 64>>>>'}}
%0 = cir.vtable.address_point(@_ZTV1S, address_point = <index = 0, offset = 2>) : !cir.ptr<!cir.ptr<!cir.func<() -> !u64i>>>
cir.return
}
23 changes: 23 additions & 0 deletions clang/test/CIR/IR/vtable-addrpt.cir
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// RUN: cir-opt %s | FileCheck %s

// Test the parsing and printing of a constructor that uses a vtable addess_point op.

!u32i = !cir.int<u, 32>
!u8i = !cir.int<u, 8>
!rec_anon_struct = !cir.record<struct {!cir.array<!cir.ptr<!u8i> x 4>}>
!rec_S = !cir.record<struct "S" {!cir.ptr<!cir.ptr<!cir.func<() -> !u32i>>>}>

module {
cir.global "private" external @_ZTV1S : !rec_anon_struct {alignment = 8 : i64}
cir.func @_ZN1SC2Ev(%arg0: !cir.ptr<!rec_S>) {
%0 = cir.alloca !cir.ptr<!rec_S>, !cir.ptr<!cir.ptr<!rec_S>>, ["this", init] {alignment = 8 : i64}
cir.store %arg0, %0 : !cir.ptr<!rec_S>, !cir.ptr<!cir.ptr<!rec_S>>
%1 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_S>>, !cir.ptr<!rec_S>
%2 = cir.vtable.address_point(@_ZTV1S, address_point = <index = 0, offset = 2>) : !cir.ptr<!cir.ptr<!cir.func<() -> !u32i>>>
%3 = cir.cast(bitcast, %1 : !cir.ptr<!rec_S>), !cir.ptr<!cir.ptr<!cir.ptr<!cir.func<() -> !u32i>>>>
cir.store align(8) %2, %3 : !cir.ptr<!cir.ptr<!cir.func<() -> !u32i>>>, !cir.ptr<!cir.ptr<!cir.ptr<!cir.func<() -> !u32i>>>>
cir.return
}
}

// CHECK: cir.vtable.address_point(@_ZTV1S, address_point = <index = 0, offset = 2>) : !cir.ptr<!cir.ptr<!cir.func<() -> !u32i>>>
Loading