Skip to content

Commit 7512be0

Browse files
committed
[CIR][NFC] Add more examples for vtable-related ops
This adds a few C++ examples to the documentation for the cir.vtable operations.
1 parent 942008c commit 7512be0

File tree

1 file changed

+170
-16
lines changed

1 file changed

+170
-16
lines changed

clang/include/clang/CIR/Dialect/IR/CIROps.td

Lines changed: 170 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2607,14 +2607,57 @@ def CIR_VTableAddrPointOp : CIR_Op<"vtable.address_point", [
26072607
the vtable group (as specified by Itanium ABI), and `address_point.offset`
26082608
(address point index) the actual address point within that vtable.
26092609

2610+
The `name` argument to this operation must be the name of a C++ vtable
2611+
object. The return value is the address of the virtual function pointer
2612+
array within the vtable (the vptr). This value will be written to the
2613+
vptr member of a dynamic class by the constructor of the class. Derived
2614+
classes have their own vtable, which is used to obtain the vptr stored
2615+
in instances of the derived class.
2616+
26102617
The return type is always `!cir.vptr`.
26112618

2612-
Example:
2619+
Examples:
2620+
2621+
```C++
2622+
struct Base {
2623+
Base();
2624+
virtual void f();
2625+
};
2626+
struct Derived : public Base {
2627+
Derived();
2628+
}
2629+
```
2630+
26132631
```mlir
2614-
cir.global linkonce_odr @_ZTV1B = ...
2632+
!rec_Base = !cir.record<struct "Base" {!cir.vptr}
2633+
!rec_Derived = !cir.record<struct "Derived" {!rec_Base}
2634+
...
2635+
// VTable for Base
2636+
cir.global linkonce_odr @_ZTV1Base = ...
26152637
...
2616-
%3 = cir.vtable.address_point(@_ZTV1B,
2638+
// Constructor for Base
2639+
cir.func dso_local @_ZN4BaseC2Ev ...
2640+
...
2641+
%2 = cir.vtable.address_point(@_ZTV1Base,
26172642
address_point = <index = 0, offset = 2>) : !cir.vptr
2643+
// The vptr is at element zero.
2644+
%3 = cir.cast(bitcast, %1 : !cir.ptr<!rec_Base>), cir.ptr<!cir.vptr>>
2645+
cir.store align(8) %2, %3 : !cir.vptr, !cir.ptr<!cir.vptr>
2646+
...
2647+
// VTable for Derived
2648+
cir.global linkonce_odr @_ZTV7Derived = ...
2649+
...
2650+
// Constructor for Derived
2651+
cir.func dso_local @_ZN7DerivedC2Ev ...
2652+
// Get the address of Base within this Derived instance
2653+
%2 = cir.base_class_addr %1 : !cir.ptr<!rec_Derived> nonnull [0]
2654+
cir.call @_ZN4BaseC2Ev(%2)
2655+
%3 = cir.vtable.address_point(@_ZTV7Derived,
2656+
address_point = <index = 0, offset = 2>) : !cir.vptr
2657+
// The vptr is still at the start of the object in this case
2658+
%4 = cir.cast(bitcast, %1 : !cir.ptr<!rec_Derived>), !cir.ptr<!cir.vptr>
2659+
// This overwrites the vptr that was stored in the Base constructor call
2660+
cir.store align(8) %3, %4 : !cir.vptr, !cir.ptr<!cir.vptr>
26182661
```
26192662
}];
26202663

@@ -2649,9 +2692,41 @@ def CIR_VTableGetVPtrOp : CIR_Op<"vtable.get_vptr", [Pure]> {
26492692
The return type is always `!cir.ptr<!cir.vptr>`.
26502693

26512694
Example:
2695+
```C++
2696+
struct S {
2697+
virtual void f1();
2698+
virtual void f2();
2699+
};
2700+
void f3(S *s) {
2701+
s->f2();
2702+
}
2703+
```
2704+
26522705
```mlir
2653-
%2 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!rec_C>
2654-
%3 = cir.vtable.get_vptr %2 : !cir.ptr<!rec_C> -> !cir.ptr<!cir.vptr>
2706+
// VTable for S
2707+
cir.global external @_ZTV1S = #cir.vtable<{
2708+
#cir.const_array<[
2709+
// Offset to the base object
2710+
#cir.ptr<null> : !cir.ptr<!u8i>,
2711+
// Type info for S
2712+
#cir.global_view<@_ZTI1S> : !cir.ptr<!u8i>,
2713+
// Pointer to S::f1
2714+
#cir.global_view<@_ZN1S2f1Ev> : !cir.ptr<!u8i>,
2715+
// Pointer to S::f2
2716+
#cir.global_view<@_ZN1S2f2Ev> : !cir.ptr<!u8i>
2717+
]> : !cir.array<!cir.ptr<!u8i> x 4>}> ...
2718+
// f3()
2719+
cir.func dso_local @_Z2f3P1S(%s: !cir.ptr<!rec_S>) {
2720+
// Get the vptr -- This points to offset 2 in the vtable.
2721+
%1 = cir.vtable.get_vptr %s : !cir.ptr<!rec_S> -> !cir.ptr<!cir.vptr>
2722+
%2 = cir.load align(8) %2 : !cir.ptr<!cir.vptr>, !cir.vptr
2723+
// Get the address of b->f2() -- may be Base::f2() or Derived::f2()
2724+
%3 = cir.vtable.get_virtual_fn_addr %2[1] : !cir.vptr
2725+
-> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>
2726+
%4 = cir.load align(8) %3
2727+
: !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>,
2728+
!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>
2729+
cir.call %4(%b)
26552730
```
26562731
}];
26572732

@@ -2688,19 +2763,98 @@ def CIR_VTableGetVirtualFnAddrOp : CIR_Op<"vtable.get_virtual_fn_addr", [
26882763

26892764
The return type is a pointer-to-pointer to the function type.
26902765

2691-
Example:
2766+
Example 1:
2767+
Suppose we have two classes, Base and Derived, where Derived overrides
2768+
virtual functions that were defined in Base. When a pointer to a Base
2769+
object is used to call one of these function, we may not know at compile
2770+
time whether it points to an instance of Base or an instance of Derived.
2771+
The compiler does not need to know. It will load the vptr from the object
2772+
and use that to get the address of the correct function to call. The
2773+
vptr will have been initialized in the object's constructor to point to
2774+
the correct vtable for the object being instantiated.
2775+
```C++
2776+
// In this example, when f3 is called, we don't know at compile-time
2777+
// whether
2778+
struct Base {
2779+
virtual void f1();
2780+
virtual void f2();
2781+
};
2782+
struct Derived : public Base {
2783+
void f1() override;
2784+
void f2() override;
2785+
};
2786+
void f3(Base *b) {
2787+
b->f2();
2788+
}
2789+
```
2790+
26922791
```mlir
2693-
%2 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!rec_C>
2694-
%3 = cir.vtable.get_vptr %2 : !cir.ptr<!rec_C> -> !cir.ptr<!cir.vptr>
2695-
%4 = cir.load %3 : !cir.ptr<!cir.vptr>, !cir.vptr
2696-
%5 = cir.vtable.get_virtual_fn_addr %4[2] : !cir.vptr
2697-
-> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_C>) -> !s32i>>>
2698-
%6 = cir.load align(8) %5 : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_C>)
2699-
-> !s32i>>>,
2700-
!cir.ptr<!cir.func<(!cir.ptr<!rec_C>) -> !s32i>>
2701-
%7 = cir.call %6(%2) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_C>) -> !s32i>>,
2702-
!cir.ptr<!rec_C>) -> !s32i
2792+
// VTable for Base
2793+
cir.global external @_ZTV4Base = #cir.vtable<{
2794+
#cir.const_array<[
2795+
#cir.ptr<null> : !cir.ptr<!u8i>,
2796+
#cir.global_view<@_ZTI4Base> : !cir.ptr<!u8i>,
2797+
#cir.global_view<@_ZN4Base2f1Ev> : !cir.ptr<!u8i>,
2798+
#cir.global_view<@_ZN4Base2f2Ev> : !cir.ptr<!u8i>
2799+
]> : !cir.array<!cir.ptr<!u8i> x 4>}> ...
2800+
// VTable for Derived
2801+
cir.global external @_ZTV7Derived = #cir.vtable<{
2802+
#cir.const_array<[
2803+
#cir.ptr<null> : !cir.ptr<!u8i>,
2804+
#cir.global_view<@_ZTI7Derived> : !cir.ptr<!u8i>,
2805+
#cir.global_view<@_ZN7Derived2f1Ev> : !cir.ptr<!u8i>,
2806+
#cir.global_view<@_ZN7Derived2f2Ev> : !cir.ptr<!u8i>
2807+
]> : !cir.array<!cir.ptr<!u8i> x 4>}> ...
2808+
// f3()
2809+
cir.func dso_local @_Z2f3P4Base(%b: !cir.ptr<!rec_Base>)
2810+
// Get the vptr
2811+
%1 = cir.vtable.get_vptr %b : !cir.ptr<!rec_Base> -> !cir.ptr<!cir.vptr>
2812+
%2 = cir.load align(8) %2 : !cir.ptr<!cir.vptr>, !cir.vptr
2813+
// Get the address of b->f2() -- may be Base::f2() or Derived::f2()
2814+
%3 = cir.vtable.get_virtual_fn_addr %2[1] : !cir.vptr
2815+
-> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>
2816+
%4 = cir.load align(8) %3
2817+
: !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>>,
2818+
!cir.ptr<!cir.func<(!cir.ptr<!rec_Base>)>>
2819+
cir.call %4(%b)
27032820
```
2821+
2822+
Example 2:
2823+
Consider the case of multiple inheritance, where Base1 and Base2 both
2824+
provide virtual functions and a third class, Derived, inherits from both
2825+
bases. When a pointer to a Derived is used to call a virtual function in
2826+
Base2, we must retrieve a pointer to the Base2 portion of the Derived object
2827+
and use that pointer to get the vptr for Base2 as a base class.
2828+
```C++
2829+
struct Base1 {
2830+
virtual void f1();
2831+
};
2832+
struct Base2 {
2833+
virtual void f2();
2834+
};
2835+
struct Derived : public Base1, Base2 { };
2836+
void f3(Derived *d) {
2837+
d->f2();
2838+
}
2839+
```
2840+
2841+
```mlir
2842+
!rec_Base1 = !cir.record<struct "Base1" {!cir.vptr}
2843+
!rec_Base2 = !cir.record<struct "Base2" {!cir.vptr}
2844+
!rec_Derived = !cir.record<struct "Derived" {!rec_Base1, !rec_Base2}
2845+
cir.func dso_local @_Z2f3P7Derived(%d: !cir.ptr<!rec_Derived>)
2846+
%2 = cir.base_class_addr %d : !cir.ptr<!rec_Derived> nonnull [8]
2847+
-> !cir.ptr<!rec_Base2>
2848+
%3 = cir.vtable.get_vptr %2 : !cir.ptr<!rec_Base2> -> !cir.ptr<!cir.vptr>
2849+
%4 = cir.load align(8) %3 : !cir.ptr<!cir.vptr>, !cir.vptr
2850+
%5 = cir.vtable.get_virtual_fn_addr %4[0] : !cir.vptr
2851+
-> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base2>)>>>
2852+
%6 = cir.load align(8) %5
2853+
: !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_Base2>)>>>,
2854+
!cir.ptr<!cir.func<(!cir.ptr<!rec_Base2>)>>
2855+
cir.call %6(%2) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_Base2>)>>,
2856+
!cir.ptr<!rec_Base2>) -> ()
2857+
```
27042858
}];
27052859

27062860
let arguments = (ins

0 commit comments

Comments
 (0)