Skip to content

Commit 7ac4d9b

Browse files
authored
[CIR] Add support for calling virtual functions (#153893)
This change adds support for calling virtual functions. This includes adding the cir.vtable.get_virtual_fn_addr operation to lookup the address of the function being called from an object's vtable.
1 parent 61a859b commit 7ac4d9b

File tree

14 files changed

+304
-21
lines changed

14 files changed

+304
-21
lines changed

clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,20 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
157157
return create<cir::ComplexImagOp>(loc, operandTy.getElementType(), operand);
158158
}
159159

160+
cir::LoadOp createLoad(mlir::Location loc, mlir::Value ptr,
161+
uint64_t alignment = 0) {
162+
mlir::IntegerAttr alignmentAttr = getAlignmentAttr(alignment);
163+
assert(!cir::MissingFeatures::opLoadStoreVolatile());
164+
assert(!cir::MissingFeatures::opLoadStoreMemOrder());
165+
return cir::LoadOp::create(*this, loc, ptr, /*isDeref=*/false,
166+
alignmentAttr);
167+
}
168+
169+
mlir::Value createAlignedLoad(mlir::Location loc, mlir::Value ptr,
170+
uint64_t alignment) {
171+
return createLoad(loc, ptr, alignment);
172+
}
173+
160174
mlir::Value createNot(mlir::Value value) {
161175
return create<cir::UnaryOp>(value.getLoc(), value.getType(),
162176
cir::UnaryOpKind::Not, value);

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

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1838,6 +1838,54 @@ def CIR_VTableGetVPtrOp : CIR_Op<"vtable.get_vptr", [Pure]> {
18381838
}];
18391839
}
18401840

1841+
//===----------------------------------------------------------------------===//
1842+
// VTableGetVirtualFnAddrOp
1843+
//===----------------------------------------------------------------------===//
1844+
1845+
def CIR_VTableGetVirtualFnAddrOp : CIR_Op<"vtable.get_virtual_fn_addr", [
1846+
Pure
1847+
]> {
1848+
let summary = "Get a the address of a virtual function pointer";
1849+
let description = [{
1850+
The `vtable.get_virtual_fn_addr` operation retrieves the address of a
1851+
virtual function pointer from an object's vtable (__vptr).
1852+
This is an abstraction to perform the basic pointer arithmetic to get
1853+
the address of the virtual function pointer, which can then be loaded and
1854+
called.
1855+
1856+
The `vptr` operand must be a `!cir.ptr<!cir.vptr>` value, which would
1857+
have been returned by a previous call to `cir.vatble.get_vptr`. The
1858+
`index` operand is an index of the virtual function in the vtable.
1859+
1860+
The return type is a pointer-to-pointer to the function type.
1861+
1862+
Example:
1863+
```mlir
1864+
%2 = cir.load %0 : !cir.ptr<!cir.ptr<!rec_C>>, !cir.ptr<!rec_C>
1865+
%3 = cir.vtable.get_vptr %2 : !cir.ptr<!rec_C> -> !cir.ptr<!cir.vptr>
1866+
%4 = cir.load %3 : !cir.ptr<!cir.vptr>, !cir.vptr
1867+
%5 = cir.vtable.get_virtual_fn_addr %4[2] : !cir.vptr
1868+
-> !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_C>) -> !s32i>>>
1869+
%6 = cir.load align(8) %5 : !cir.ptr<!cir.ptr<!cir.func<(!cir.ptr<!rec_C>)
1870+
-> !s32i>>>,
1871+
!cir.ptr<!cir.func<(!cir.ptr<!rec_C>) -> !s32i>>
1872+
%7 = cir.call %6(%2) : (!cir.ptr<!cir.func<(!cir.ptr<!rec_C>) -> !s32i>>,
1873+
!cir.ptr<!rec_C>) -> !s32i
1874+
```
1875+
}];
1876+
1877+
let arguments = (ins
1878+
Arg<CIR_VPtrType, "vptr", [MemRead]>:$vptr,
1879+
I64Attr:$index);
1880+
1881+
let results = (outs CIR_PointerType:$result);
1882+
1883+
let assemblyFormat = [{
1884+
$vptr `[` $index `]` attr-dict
1885+
`:` qualified(type($vptr)) `->` qualified(type($result))
1886+
}];
1887+
}
1888+
18411889
//===----------------------------------------------------------------------===//
18421890
// SetBitfieldOp
18431891
//===----------------------------------------------------------------------===//

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ struct MissingFeatures {
9595
static bool opCallArgEvaluationOrder() { return false; }
9696
static bool opCallCallConv() { return false; }
9797
static bool opCallMustTail() { return false; }
98-
static bool opCallVirtual() { return false; }
9998
static bool opCallInAlloca() { return false; }
10099
static bool opCallAttrs() { return false; }
101100
static bool opCallSurroundingTry() { return false; }
@@ -204,6 +203,7 @@ struct MissingFeatures {
204203
static bool dataLayoutTypeAllocSize() { return false; }
205204
static bool dataLayoutTypeStoreSize() { return false; }
206205
static bool deferredCXXGlobalInit() { return false; }
206+
static bool devirtualizeMemberFunction() { return false; }
207207
static bool ehCleanupFlags() { return false; }
208208
static bool ehCleanupScope() { return false; }
209209
static bool ehCleanupScopeRequiresEHCleanup() { return false; }
@@ -215,6 +215,7 @@ struct MissingFeatures {
215215
static bool emitLValueAlignmentAssumption() { return false; }
216216
static bool emitNullabilityCheck() { return false; }
217217
static bool emitTypeCheck() { return false; }
218+
static bool emitTypeMetadataCodeForVCall() { return false; }
218219
static bool fastMathFlags() { return false; }
219220
static bool fpConstraints() { return false; }
220221
static bool generateDebugInfo() { return false; }

clang/lib/CIR/CodeGen/CIRGenCXXABI.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@ class CIRGenCXXABI {
6363
/// parameter.
6464
virtual bool needsVTTParameter(clang::GlobalDecl gd) { return false; }
6565

66+
/// Perform ABI-specific "this" argument adjustment required prior to
67+
/// a call of a virtual function.
68+
/// The "VirtualCall" argument is true iff the call itself is virtual.
69+
virtual Address adjustThisArgumentForVirtualFunctionCall(CIRGenFunction &cgf,
70+
clang::GlobalDecl gd,
71+
Address thisPtr,
72+
bool virtualCall) {
73+
return thisPtr;
74+
}
75+
6676
/// Build a parameter variable suitable for 'this'.
6777
void buildThisParam(CIRGenFunction &cgf, FunctionArgList &params);
6878

@@ -100,6 +110,13 @@ class CIRGenCXXABI {
100110
virtual cir::GlobalOp getAddrOfVTable(const CXXRecordDecl *rd,
101111
CharUnits vptrOffset) = 0;
102112

113+
/// Build a virtual function pointer in the ABI-specific way.
114+
virtual CIRGenCallee getVirtualFunctionPointer(CIRGenFunction &cgf,
115+
clang::GlobalDecl gd,
116+
Address thisAddr,
117+
mlir::Type ty,
118+
SourceLocation loc) = 0;
119+
103120
/// Get the address point of the vtable for the given base subobject.
104121
virtual mlir::Value
105122
getVTableAddressPoint(BaseSubobject base,

clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,10 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
7979
const Expr *base) {
8080
assert(isa<CXXMemberCallExpr>(ce) || isa<CXXOperatorCallExpr>(ce));
8181

82-
if (md->isVirtual()) {
83-
cgm.errorNYI(ce->getSourceRange(),
84-
"emitCXXMemberOrOperatorMemberCallExpr: virtual call");
85-
return RValue::get(nullptr);
86-
}
82+
// Compute the object pointer.
83+
bool canUseVirtualCall = md->isVirtual() && !hasQualifier;
84+
const CXXMethodDecl *devirtualizedMethod = nullptr;
85+
assert(!cir::MissingFeatures::devirtualizeMemberFunction());
8786

8887
// Note on trivial assignment
8988
// --------------------------
@@ -127,7 +126,8 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
127126
return RValue::get(nullptr);
128127

129128
// Compute the function type we're calling
130-
const CXXMethodDecl *calleeDecl = md;
129+
const CXXMethodDecl *calleeDecl =
130+
devirtualizedMethod ? devirtualizedMethod : md;
131131
const CIRGenFunctionInfo *fInfo = nullptr;
132132
if (isa<CXXDestructorDecl>(calleeDecl)) {
133133
cgm.errorNYI(ce->getSourceRange(),
@@ -137,25 +137,46 @@ RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
137137

138138
fInfo = &cgm.getTypes().arrangeCXXMethodDeclaration(calleeDecl);
139139

140-
mlir::Type ty = cgm.getTypes().getFunctionType(*fInfo);
140+
cir::FuncType ty = cgm.getTypes().getFunctionType(*fInfo);
141141

142142
assert(!cir::MissingFeatures::sanitizers());
143143
assert(!cir::MissingFeatures::emitTypeCheck());
144144

145+
// C++ [class.virtual]p12:
146+
// Explicit qualification with the scope operator (5.1) suppresses the
147+
// virtual call mechanism.
148+
//
149+
// We also don't emit a virtual call if the base expression has a record type
150+
// because then we know what the type is.
151+
bool useVirtualCall = canUseVirtualCall && !devirtualizedMethod;
152+
145153
if (isa<CXXDestructorDecl>(calleeDecl)) {
146154
cgm.errorNYI(ce->getSourceRange(),
147155
"emitCXXMemberOrOperatorMemberCallExpr: destructor call");
148156
return RValue::get(nullptr);
149157
}
150158

151-
assert(!cir::MissingFeatures::sanitizers());
152-
if (getLangOpts().AppleKext) {
153-
cgm.errorNYI(ce->getSourceRange(),
154-
"emitCXXMemberOrOperatorMemberCallExpr: AppleKext");
155-
return RValue::get(nullptr);
159+
CIRGenCallee callee;
160+
if (useVirtualCall) {
161+
callee = CIRGenCallee::forVirtual(ce, md, thisPtr.getAddress(), ty);
162+
} else {
163+
assert(!cir::MissingFeatures::sanitizers());
164+
if (getLangOpts().AppleKext) {
165+
cgm.errorNYI(ce->getSourceRange(),
166+
"emitCXXMemberOrOperatorMemberCallExpr: AppleKext");
167+
return RValue::get(nullptr);
168+
}
169+
170+
callee = CIRGenCallee::forDirect(cgm.getAddrOfFunction(calleeDecl, ty),
171+
GlobalDecl(calleeDecl));
172+
}
173+
174+
if (md->isVirtual()) {
175+
Address newThisAddr =
176+
cgm.getCXXABI().adjustThisArgumentForVirtualFunctionCall(
177+
*this, calleeDecl, thisPtr.getAddress(), useVirtualCall);
178+
thisPtr.setAddress(newThisAddr);
156179
}
157-
CIRGenCallee callee =
158-
CIRGenCallee::forDirect(cgm.getAddrOfFunction(md, ty), GlobalDecl(md));
159180

160181
return emitCXXMemberOrOperatorCall(
161182
calleeDecl, callee, returnValue, thisPtr.getPointer(),

clang/lib/CIR/CodeGen/CIRGenCall.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,12 @@ cir::FuncType CIRGenTypes::getFunctionType(const CIRGenFunctionInfo &info) {
5656
}
5757

5858
CIRGenCallee CIRGenCallee::prepareConcreteCallee(CIRGenFunction &cgf) const {
59-
assert(!cir::MissingFeatures::opCallVirtual());
59+
if (isVirtual()) {
60+
const CallExpr *ce = getVirtualCallExpr();
61+
return cgf.cgm.getCXXABI().getVirtualFunctionPointer(
62+
cgf, getVirtualMethodDecl(), getThisAddress(), getVirtualFunctionType(),
63+
ce ? ce->getBeginLoc() : SourceLocation());
64+
}
6065
return *this;
6166
}
6267

clang/lib/CIR/CodeGen/CIRGenCall.h

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ class CIRGenCallee {
4747
Invalid,
4848
Builtin,
4949
PseudoDestructor,
50+
Virtual,
5051

51-
Last = Builtin,
52+
Last = Virtual
5253
};
5354

5455
struct BuiltinInfoStorage {
@@ -58,13 +59,20 @@ class CIRGenCallee {
5859
struct PseudoDestructorInfoStorage {
5960
const clang::CXXPseudoDestructorExpr *expr;
6061
};
62+
struct VirtualInfoStorage {
63+
const clang::CallExpr *ce;
64+
clang::GlobalDecl md;
65+
Address addr;
66+
cir::FuncType fTy;
67+
};
6168

6269
SpecialKind kindOrFunctionPtr;
6370

6471
union {
6572
CIRGenCalleeInfo abstractInfo;
6673
BuiltinInfoStorage builtinInfo;
6774
PseudoDestructorInfoStorage pseudoDestructorInfo;
75+
VirtualInfoStorage virtualInfo;
6876
};
6977

7078
explicit CIRGenCallee(SpecialKind kind) : kindOrFunctionPtr(kind) {}
@@ -128,7 +136,8 @@ class CIRGenCallee {
128136
CIRGenCallee prepareConcreteCallee(CIRGenFunction &cgf) const;
129137

130138
CIRGenCalleeInfo getAbstractInfo() const {
131-
assert(!cir::MissingFeatures::opCallVirtual());
139+
if (isVirtual())
140+
return virtualInfo.md;
132141
assert(isOrdinary());
133142
return abstractInfo;
134143
}
@@ -138,6 +147,39 @@ class CIRGenCallee {
138147
return reinterpret_cast<mlir::Operation *>(kindOrFunctionPtr);
139148
}
140149

150+
bool isVirtual() const { return kindOrFunctionPtr == SpecialKind::Virtual; }
151+
152+
static CIRGenCallee forVirtual(const clang::CallExpr *ce,
153+
clang::GlobalDecl md, Address addr,
154+
cir::FuncType fTy) {
155+
CIRGenCallee result(SpecialKind::Virtual);
156+
result.virtualInfo.ce = ce;
157+
result.virtualInfo.md = md;
158+
result.virtualInfo.addr = addr;
159+
result.virtualInfo.fTy = fTy;
160+
return result;
161+
}
162+
163+
const clang::CallExpr *getVirtualCallExpr() const {
164+
assert(isVirtual());
165+
return virtualInfo.ce;
166+
}
167+
168+
clang::GlobalDecl getVirtualMethodDecl() const {
169+
assert(isVirtual());
170+
return virtualInfo.md;
171+
}
172+
173+
Address getThisAddress() const {
174+
assert(isVirtual());
175+
return virtualInfo.addr;
176+
}
177+
178+
cir::FuncType getVirtualFunctionType() const {
179+
assert(isVirtual());
180+
return virtualInfo.fTy;
181+
}
182+
141183
void setFunctionPointer(mlir::Operation *functionPtr) {
142184
assert(isOrdinary());
143185
kindOrFunctionPtr = SpecialKind(reinterpret_cast<uintptr_t>(functionPtr));

clang/lib/CIR/CodeGen/CIRGenClass.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,20 @@ Address CIRGenFunction::getAddressOfBaseClass(
657657
return value;
658658
}
659659

660+
// TODO(cir): this can be shared with LLVM codegen.
661+
bool CIRGenFunction::shouldEmitVTableTypeCheckedLoad(const CXXRecordDecl *rd) {
662+
assert(!cir::MissingFeatures::hiddenVisibility());
663+
if (!cgm.getCodeGenOpts().WholeProgramVTables)
664+
return false;
665+
666+
if (cgm.getCodeGenOpts().VirtualFunctionElimination)
667+
return true;
668+
669+
assert(!cir::MissingFeatures::sanitizers());
670+
671+
return false;
672+
}
673+
660674
mlir::Value CIRGenFunction::getVTablePtr(mlir::Location loc, Address thisAddr,
661675
const CXXRecordDecl *rd) {
662676
auto vtablePtr = cir::VTableGetVPtrOp::create(

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,11 @@ class CIRGenFunction : public CIRGenTypeCache {
552552
mlir::Value getVTablePtr(mlir::Location loc, Address thisAddr,
553553
const clang::CXXRecordDecl *vtableClass);
554554

555+
/// Returns whether we should perform a type checked load when loading a
556+
/// virtual function for virtual calls to members of RD. This is generally
557+
/// true when both vcall CFI and whole-program-vtables are enabled.
558+
bool shouldEmitVTableTypeCheckedLoad(const CXXRecordDecl *rd);
559+
555560
/// A scope within which we are constructing the fields of an object which
556561
/// might use a CXXDefaultInitExpr. This stashes away a 'this' value to use if
557562
/// we need to evaluate the CXXDefaultInitExpr within the evaluation.

0 commit comments

Comments
 (0)