[CIR] Upstream vtable thunk handling (#183629)

This implements vtable thunk handling in CIR based on the incubator
code, but also compared against the latest Clang LLVM IR codegen.

Eventually, we'll want to create CIR abstractions for all of this and
move the CXXABI-specific details into the CXXABI lowering pass. For now,
we just implement it directly in codegen.
This commit is contained in:
Andy Kaylor 2026-03-02 14:15:52 -08:00 committed by GitHub
parent 49c3cd15e8
commit a14d8b2e36
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 763 additions and 10 deletions

View File

@ -329,6 +329,7 @@ struct MissingFeatures {
static bool pointerOverflowSanitizer() { return false; }
static bool preservedAccessIndexRegion() { return false; }
static bool requiresCleanups() { return false; }
static bool returnValueSlotFeatures() { return false; }
static bool runCleanupsScope() { return false; }
static bool sanitizers() { return false; }
static bool setDLLStorageClass() { return false; }

View File

@ -96,3 +96,11 @@ bool CIRGenCXXABI::requiresArrayCookie(const CXXNewExpr *e) {
return e->getAllocatedType().isDestructedType();
}
void CIRGenCXXABI::emitReturnFromThunk(CIRGenFunction &cgf, RValue rv,
QualType resultType) {
assert(!cgf.hasAggregateEvaluationKind(resultType) &&
"cannot handle aggregates");
mlir::Location loc = cgf.getBuilder().getUnknownLoc();
cgf.emitReturnOfRValue(loc, rv, resultType);
}

View File

@ -227,6 +227,36 @@ public:
/// this emits virtual table tables.
virtual void emitVirtualInheritanceTables(const CXXRecordDecl *rd) = 0;
/// Returns true if the thunk should be exported.
virtual bool exportThunk() = 0;
/// Set the linkage and visibility of a thunk function.
virtual void setThunkLinkage(cir::FuncOp thunk, bool forVTable, GlobalDecl gd,
bool returnAdjustment) = 0;
/// Perform adjustment on the 'this' pointer for a thunk.
/// Returns the adjusted 'this' pointer value.
virtual mlir::Value
performThisAdjustment(CIRGenFunction &cgf, Address thisAddr,
const CXXRecordDecl *unadjustedClass,
const ThunkInfo &ti) = 0;
/// Perform adjustment on a return pointer for a thunk (covariant returns).
/// Returns the adjusted return pointer value.
virtual mlir::Value
performReturnAdjustment(CIRGenFunction &cgf, Address ret,
const CXXRecordDecl *unadjustedClass,
const ReturnAdjustment &ra) = 0;
/// Adjust call arguments for a destructor thunk.
virtual void adjustCallArgsForDestructorThunk(CIRGenFunction &cgf,
GlobalDecl globalDecl,
CallArgList &callArgs) {}
/// Emit a return from a thunk.
virtual void emitReturnFromThunk(CIRGenFunction &cgf, RValue rv,
QualType resultType);
/// Returns true if the given destructor type should be emitted as a linkonce
/// delegating thunk, regardless of whether the dtor is defined in this TU or
/// not.

View File

@ -529,7 +529,7 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType,
didCallStackSave = false;
curCodeDecl = d;
const auto *fd = dyn_cast_or_null<FunctionDecl>(d);
curFuncDecl = d->getNonClosureContext();
curFuncDecl = (d ? d->getNonClosureContext() : nullptr);
prologueCleanupDepth = ehStack.stable_begin();

View File

@ -122,6 +122,7 @@ public:
const clang::Decl *curFuncDecl = nullptr;
/// This is the inner-most code context, which includes blocks.
const clang::Decl *curCodeDecl = nullptr;
const CIRGenFunctionInfo *curFnInfo = nullptr;
/// The current function or global initializer that is generated code for.
/// This is usually a cir::FuncOp, but it can also be a cir::GlobalOp for
@ -186,6 +187,10 @@ public:
/// this fuction. These can potentially set the return value.
bool sawAsmBlock = false;
/// In C++, whether we are code generating a thunk. This controls whether we
/// should emit cleanups.
bool curFuncIsThunk = false;
mlir::Type convertTypeForMem(QualType t);
mlir::Type convertType(clang::QualType t);
@ -1234,6 +1239,18 @@ public:
Destroyer *getDestroyer(clang::QualType::DestructionKind kind);
/// Start generating a thunk function.
void startThunk(cir::FuncOp fn, GlobalDecl gd,
const CIRGenFunctionInfo &fnInfo, bool isUnprototyped);
/// Finish generating a thunk function.
void finishThunk();
/// Generate code for a thunk function.
void generateThunk(cir::FuncOp fn, const CIRGenFunctionInfo &fnInfo,
GlobalDecl gd, const ThunkInfo &thunk,
bool isUnprototyped);
/// ----------------------
/// CIR emit functions
/// ----------------------
@ -1425,6 +1442,11 @@ public:
RValue emitCall(clang::QualType calleeTy, const CIRGenCallee &callee,
const clang::CallExpr *e, ReturnValueSlot returnValue);
/// Emit the call and return for a thunk function.
void emitCallAndReturnForThunk(cir::FuncOp callee, const ThunkInfo *thunk,
bool isUnprototyped);
void emitCallArg(CallArgList &args, const clang::Expr *e,
clang::QualType argType);
void emitCallArgs(
@ -1770,6 +1792,10 @@ public:
LValue emitMemberExpr(const MemberExpr *e);
/// Emit a musttail call for a thunk with a potentially different ABI.
void emitMustTailThunk(GlobalDecl gd, mlir::Value adjustedThisPtr,
cir::FuncOp callee);
/// Emit a call to an AMDGPU builtin function.
std::optional<mlir::Value> emitAMDGPUBuiltinExpr(unsigned builtinID,
const CallExpr *expr);

View File

@ -119,6 +119,24 @@ public:
const CXXRecordDecl *rd) override;
void emitVirtualInheritanceTables(const CXXRecordDecl *rd) override;
void setThunkLinkage(cir::FuncOp thunk, bool forVTable, GlobalDecl gd,
bool returnAdjustment) override {
if (forVTable && !thunk.hasLocalLinkage())
thunk.setLinkage(cir::GlobalLinkageKind::AvailableExternallyLinkage);
const auto *nd = cast<NamedDecl>(gd.getDecl());
cgm.setGVProperties(thunk, nd);
}
bool exportThunk() override { return true; }
mlir::Value performThisAdjustment(CIRGenFunction &cgf, Address thisAddr,
const CXXRecordDecl *unadjustedClass,
const ThunkInfo &ti) override;
mlir::Value performReturnAdjustment(CIRGenFunction &cgf, Address ret,
const CXXRecordDecl *unadjustedClass,
const ReturnAdjustment &ra) override;
mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc,
QualType ty) override;
CatchTypeInfo
@ -2487,3 +2505,61 @@ void CIRGenItaniumCXXABI::emitBeginCatch(CIRGenFunction &cgf,
catchStmt->getBeginLoc());
cgf.emitAutoVarCleanups(var);
}
static mlir::Value performTypeAdjustment(CIRGenFunction &cgf,
Address initialPtr,
const CXXRecordDecl *unadjustedClass,
int64_t nonVirtualAdjustment,
int64_t virtualAdjustment,
bool isReturnAdjustment) {
if (!nonVirtualAdjustment && !virtualAdjustment)
return initialPtr.getPointer();
CIRGenBuilderTy &builder = cgf.getBuilder();
mlir::Location loc = builder.getUnknownLoc();
cir::PointerType i8PtrTy = builder.getUInt8PtrTy();
mlir::Value v = builder.createBitcast(initialPtr.getPointer(), i8PtrTy);
// In a base-to-derived cast, the non-virtual adjustment is applied first.
if (nonVirtualAdjustment && !isReturnAdjustment) {
cir::ConstantOp offsetConst = builder.getSInt64(nonVirtualAdjustment, loc);
v = cir::PtrStrideOp::create(builder, loc, i8PtrTy, v, offsetConst);
}
// Perform the virtual adjustment if we have one.
mlir::Value resultPtr;
if (virtualAdjustment) {
cgf.cgm.errorNYI("virtual adjustment in thunk");
resultPtr = v;
} else {
resultPtr = v;
}
// In a derived-to-base conversion, the non-virtual adjustment is
// applied second.
if (nonVirtualAdjustment && isReturnAdjustment) {
cir::ConstantOp offsetConst = builder.getSInt64(nonVirtualAdjustment, loc);
resultPtr =
cir::PtrStrideOp::create(builder, loc, i8PtrTy, resultPtr, offsetConst);
}
// Cast back to original pointer type.
return builder.createBitcast(resultPtr, initialPtr.getType());
}
mlir::Value CIRGenItaniumCXXABI::performThisAdjustment(
CIRGenFunction &cgf, Address thisAddr, const CXXRecordDecl *unadjustedClass,
const ThunkInfo &ti) {
return performTypeAdjustment(cgf, thisAddr, unadjustedClass,
ti.This.NonVirtual,
ti.This.Virtual.Itanium.VCallOffsetOffset,
/*isReturnAdjustment=*/false);
}
mlir::Value CIRGenItaniumCXXABI::performReturnAdjustment(
CIRGenFunction &cgf, Address ret, const CXXRecordDecl *unadjustedClass,
const ReturnAdjustment &ra) {
return performTypeAdjustment(cgf, ret, unadjustedClass, ra.NonVirtual,
ra.Virtual.Itanium.VBaseOffsetOffset,
/*isReturnAdjustment=*/true);
}

View File

@ -2372,12 +2372,6 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction(
mlir::ArrayAttr extraAttrs) {
const Decl *d = gd.getDecl();
if (isThunk)
errorNYI(d->getSourceRange(), "getOrCreateCIRFunction: thunk");
// In what follows, we continue past 'errorNYI' as if nothing happened because
// the rest of the implementation is better than doing nothing.
if (const auto *fd = cast_or_null<FunctionDecl>(d)) {
// For the device mark the function as one that should be emitted.
if (getLangOpts().OpenMPIsTargetDevice && fd->isDefined() && !dontDefer &&

View File

@ -273,6 +273,9 @@ public:
getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty = {},
ForDefinition_t isForDefinition = NotForDefinition);
/// Get or create a thunk function with the given name and type.
cir::FuncOp getAddrOfThunk(StringRef name, mlir::Type fnTy, GlobalDecl gd);
/// Return the mlir::GlobalViewAttr for the address of the given global.
cir::GlobalViewAttr getAddrOfGlobalVarAttr(const VarDecl *d);

View File

@ -26,6 +26,31 @@ using namespace clang::CIRGen;
CIRGenVTables::CIRGenVTables(CIRGenModule &cgm)
: cgm(cgm), vtContext(cgm.getASTContext().getVTableContext()) {}
cir::FuncOp CIRGenModule::getAddrOfThunk(StringRef name, mlir::Type fnTy,
GlobalDecl gd) {
return getOrCreateCIRFunction(name, fnTy, gd, /*forVTable=*/true,
/*dontDefer=*/true, /*isThunk=*/true);
}
static void setThunkProperties(CIRGenModule &cgm, const ThunkInfo &thunk,
cir::FuncOp thunkFn, bool forVTable,
GlobalDecl gd) {
cgm.setFunctionLinkage(gd, thunkFn);
cgm.getCXXABI().setThunkLinkage(thunkFn, forVTable, gd,
!thunk.Return.isEmpty());
// Set the right visibility.
cgm.setGVProperties(thunkFn, cast<NamedDecl>(gd.getDecl()));
if (!cgm.getCXXABI().exportThunk()) {
assert(!cir::MissingFeatures::setDLLStorageClass());
cgm.setDSOLocal(static_cast<mlir::Operation *>(thunkFn));
}
if (cgm.supportsCOMDAT() && thunkFn.isWeakForLinker())
thunkFn.setComdat(true);
}
mlir::Type CIRGenModule::getVTableComponentType() {
mlir::Type ptrTy = builder.getUInt8PtrTy();
assert(!cir::MissingFeatures::vtableRelativeLayout());
@ -161,8 +186,11 @@ mlir::Attribute CIRGenVTables::getVTableComponent(
} else if (nextVTableThunkIndex < layout.vtable_thunks().size() &&
layout.vtable_thunks()[nextVTableThunkIndex].first ==
componentIndex) {
cgm.errorNYI("getVTableComponent: CK_FunctionPointer: thunk");
return mlir::Attribute();
const ThunkInfo &thunkInfo =
layout.vtable_thunks()[nextVTableThunkIndex].second;
nextVTableThunkIndex++;
fnPtr = maybeEmitThunk(gd, thunkInfo, /*forVTable=*/true);
assert(!cir::MissingFeatures::pointerAuthentication());
} else {
// Otherwise we can use the method definition directly.
cir::FuncType fnTy = cgm.getTypes().getFunctionTypeForVTable(gd);
@ -510,6 +538,371 @@ uint64_t CIRGenVTables::getSecondaryVirtualPointerIndex(const CXXRecordDecl *rd,
return it->second;
}
static RValue performReturnAdjustment(CIRGenFunction &cgf, QualType resultType,
RValue rv, const ThunkInfo &thunk) {
// Emit the return adjustment.
bool nullCheckValue = !resultType->isReferenceType();
mlir::Value returnValue = rv.getValue();
if (nullCheckValue)
cgf.cgm.errorNYI(
"return adjustment with null check for non-reference types");
const CXXRecordDecl *classDecl =
resultType->getPointeeType()->getAsCXXRecordDecl();
CharUnits classAlign = cgf.cgm.getClassPointerAlignment(classDecl);
mlir::Type pointeeType = cgf.convertTypeForMem(resultType->getPointeeType());
returnValue = cgf.cgm.getCXXABI().performReturnAdjustment(
cgf, Address(returnValue, pointeeType, classAlign), classDecl,
thunk.Return);
if (nullCheckValue)
cgf.cgm.errorNYI(
"return adjustment with null check for non-reference types");
return RValue::get(returnValue);
}
void CIRGenFunction::startThunk(cir::FuncOp fn, GlobalDecl gd,
const CIRGenFunctionInfo &fnInfo,
bool isUnprototyped) {
assert(!curGD.getDecl() && "curGD was already set!");
curGD = gd;
curFuncIsThunk = true;
// Build FunctionArgs.
const CXXMethodDecl *md = cast<CXXMethodDecl>(gd.getDecl());
QualType thisType = md->getThisType();
QualType resultType;
if (isUnprototyped)
resultType = cgm.getASTContext().VoidTy;
else if (cgm.getCXXABI().hasThisReturn(gd))
resultType = thisType;
else if (cgm.getCXXABI().hasMostDerivedReturn(gd))
resultType = cgm.getASTContext().VoidPtrTy;
else
resultType = md->getType()->castAs<FunctionProtoType>()->getReturnType();
FunctionArgList functionArgs;
// Create the implicit 'this' parameter declaration.
cgm.getCXXABI().buildThisParam(*this, functionArgs);
// Add the rest of the parameters, if we have a prototype to work with.
if (!isUnprototyped) {
functionArgs.append(md->param_begin(), md->param_end());
if (isa<CXXDestructorDecl>(md))
cgm.getCXXABI().addImplicitStructorParams(*this, resultType,
functionArgs);
}
assert(!cir::MissingFeatures::generateDebugInfo());
// Start defining the function.
cir::FuncType funcType = cgm.getTypes().getFunctionType(fnInfo);
startFunction(GlobalDecl(), resultType, fn, funcType, functionArgs,
md->getLocation(), md->getLocation());
// TODO(cir): Move this into startFunction.
curFnInfo = &fnInfo;
assert(!cir::MissingFeatures::generateDebugInfo());
// Since we didn't pass a GlobalDecl to startFunction, do this ourselves.
cgm.getCXXABI().emitInstanceFunctionProlog(md->getLocation(), *this);
cxxThisValue = cxxabiThisValue;
curCodeDecl = md;
curFuncDecl = md;
}
void CIRGenFunction::finishThunk() {
// Clear these to restore the invariants expected by
// startFunction/finishFunction.
curCodeDecl = nullptr;
curFuncDecl = nullptr;
finishFunction(SourceLocation());
}
void CIRGenFunction::emitCallAndReturnForThunk(cir::FuncOp callee,
const ThunkInfo *thunk,
bool isUnprototyped) {
assert(isa<CXXMethodDecl>(curGD.getDecl()) &&
"Please use a new CGF for this thunk");
const CXXMethodDecl *md = cast<CXXMethodDecl>(curGD.getDecl());
// Determine the this pointer class (may differ from md's class for thunks).
const CXXRecordDecl *thisValueClass =
md->getThisType()->getPointeeCXXRecordDecl();
if (thunk)
thisValueClass = thunk->ThisType->getPointeeCXXRecordDecl();
mlir::Value adjustedThisPtr =
thunk ? cgm.getCXXABI().performThisAdjustment(*this, loadCXXThisAddress(),
thisValueClass, *thunk)
: loadCXXThis();
// If perfect forwarding is required a variadic method, a method using
// inalloca, or an unprototyped thunk, use musttail. Emit an error if this
// thunk requires a return adjustment, since that is impossible with musttail.
assert(!cir::MissingFeatures::opCallInAlloca());
if ((curFnInfo && curFnInfo->isVariadic()) || isUnprototyped) {
// Error if return adjustment is needed (can't do with musttail).
if (thunk && !thunk->Return.isEmpty()) {
if (isUnprototyped)
cgm.errorUnsupported(
md, "return-adjusting thunk with incomplete parameter type");
else if (curFnInfo && curFnInfo->isVariadic())
llvm_unreachable("shouldn't try to emit musttail return-adjusting "
"thunks for variadic functions");
else
cgm.errorUnsupported(
md, "non-trivial argument copy for return-adjusting thunk");
}
emitMustTailThunk(curGD, adjustedThisPtr, callee);
return;
}
// Build the call argument list.
CallArgList callArgs;
QualType thisType = md->getThisType();
callArgs.add(RValue::get(adjustedThisPtr), thisType);
if (isa<CXXDestructorDecl>(md))
cgm.getCXXABI().adjustCallArgsForDestructorThunk(*this, curGD, callArgs);
#ifndef NDEBUG
unsigned prefixArgs = callArgs.size() - 1;
#endif
// Add the rest of the method parameters.
for (const ParmVarDecl *pd : md->parameters())
emitDelegateCallArg(callArgs, pd, SourceLocation());
const FunctionProtoType *fpt = md->getType()->castAs<FunctionProtoType>();
#ifndef NDEBUG
const CIRGenFunctionInfo &callFnInfo = cgm.getTypes().arrangeCXXMethodCall(
callArgs, fpt, RequiredArgs::getFromProtoWithExtraSlots(fpt, 1),
prefixArgs);
assert(callFnInfo.argTypeSize() == curFnInfo->argTypeSize());
#endif
// Determine whether we have a return value slot to use.
QualType resultType = cgm.getCXXABI().hasThisReturn(curGD) ? thisType
: cgm.getCXXABI().hasMostDerivedReturn(curGD)
? cgm.getASTContext().VoidPtrTy
: fpt->getReturnType();
ReturnValueSlot slot;
// This should also be tracking volatile, unused, and externally destructed.
assert(!cir::MissingFeatures::returnValueSlotFeatures());
if (!resultType->isVoidType() && hasAggregateEvaluationKind(resultType))
slot = ReturnValueSlot(returnValue);
// Now emit our call.
CIRGenCallee cirCallee = CIRGenCallee::forDirect(callee, curGD);
mlir::Location loc = builder.getUnknownLoc();
RValue rv = emitCall(*curFnInfo, cirCallee, slot, callArgs,
/*callOrTryCall=*/nullptr, loc);
// Consider return adjustment if we have ThunkInfo.
if (thunk && !thunk->Return.isEmpty())
rv = performReturnAdjustment(*this, resultType, rv, *thunk);
else
assert(!cir::MissingFeatures::opCallMustTail());
// Emit return.
if (!resultType->isVoidType() && slot.isNull())
cgm.getCXXABI().emitReturnFromThunk(*this, rv, resultType);
// Disable final ARC autorelease.
assert(!cir::MissingFeatures::objCLifetime());
finishThunk();
}
void CIRGenFunction::emitMustTailThunk(GlobalDecl gd,
mlir::Value adjustedThisPtr,
cir::FuncOp callee) {
assert(!cir::MissingFeatures::opCallMustTail());
cgm.errorNYI("musttail thunk");
}
void CIRGenFunction::generateThunk(cir::FuncOp fn,
const CIRGenFunctionInfo &fnInfo,
GlobalDecl gd, const ThunkInfo &thunk,
bool isUnprototyped) {
// Create entry block and set up the builder's insertion point.
// This must be done before calling startThunk() which calls startFunction().
assert(fn.isDeclaration() && "Function already has body?");
mlir::Block *entryBb = fn.addEntryBlock();
builder.setInsertionPointToStart(entryBb);
// Create a scope in the symbol table to hold variable declarations.
// This is required before startFunction processes parameters, as it will
// insert them into the symbolTable (ScopedHashTable) which requires an
// active scope.
SymTableScopeTy varScope(symbolTable);
// Create lexical scope - must stay alive for entire thunk generation.
// startFunction() requires currLexScope to be set.
mlir::Location unknownLoc = builder.getUnknownLoc();
LexicalScope lexScope{*this, unknownLoc, entryBb};
startThunk(fn, gd, fnInfo, isUnprototyped);
assert(!cir::MissingFeatures::generateDebugInfo());
// Get our callee. Use a placeholder type if this method is unprototyped so
// that CIRGenModule doesn't try to set attributes.
mlir::Type ty;
if (isUnprototyped)
cgm.errorNYI("unprototyped thunk placeholder type");
else
ty = cgm.getTypes().getFunctionType(fnInfo);
cir::FuncOp calleeOp = cgm.getAddrOfFunction(gd, ty, /*forVTable=*/true);
// Make the call and return the result.
emitCallAndReturnForThunk(calleeOp, &thunk, isUnprototyped);
}
static bool shouldEmitVTableThunk(CIRGenModule &cgm, const CXXMethodDecl *md,
bool isUnprototyped, bool forVTable) {
// Always emit thunks in the MS C++ ABI. We cannot rely on other TUs to
// provide thunks for us.
if (cgm.getTarget().getCXXABI().isMicrosoft())
return true;
// In the Itanium C++ ABI, vtable thunks are provided by TUs that provide
// definitions of the main method. Therefore, emitting thunks with the vtable
// is purely an optimization. Emit the thunk if optimizations are enabled and
// all of the parameter types are complete.
if (forVTable)
return cgm.getCodeGenOpts().OptimizationLevel && !isUnprototyped;
// Always emit thunks along with the method definition.
return true;
}
cir::FuncOp CIRGenVTables::maybeEmitThunk(GlobalDecl gd,
const ThunkInfo &thunkAdjustments,
bool forVTable) {
const CXXMethodDecl *md = cast<CXXMethodDecl>(gd.getDecl());
SmallString<256> name;
MangleContext &mCtx = cgm.getCXXABI().getMangleContext();
llvm::raw_svector_ostream out(name);
if (const CXXDestructorDecl *dd = dyn_cast<CXXDestructorDecl>(md)) {
mCtx.mangleCXXDtorThunk(dd, gd.getDtorType(), thunkAdjustments,
/*elideOverrideInfo=*/false, out);
} else {
mCtx.mangleThunk(md, thunkAdjustments, /*elideOverrideInfo=*/false, out);
}
if (cgm.getASTContext().useAbbreviatedThunkName(gd, name.str())) {
name = "";
if (const CXXDestructorDecl *dd = dyn_cast<CXXDestructorDecl>(md))
mCtx.mangleCXXDtorThunk(dd, gd.getDtorType(), thunkAdjustments,
/*elideOverrideInfo=*/true, out);
else
mCtx.mangleThunk(md, thunkAdjustments, /*elideOverrideInfo=*/true, out);
}
cir::FuncType thunkVTableTy = cgm.getTypes().getFunctionTypeForVTable(gd);
cir::FuncOp thunk = cgm.getAddrOfThunk(name, thunkVTableTy, gd);
// If we don't need to emit a definition, return this declaration as is.
bool isUnprototyped = !cgm.getTypes().isFuncTypeConvertible(
md->getType()->castAs<FunctionType>());
if (!shouldEmitVTableThunk(cgm, md, isUnprototyped, forVTable))
return thunk;
// Arrange a function prototype appropriate for a function definition. In some
// cases in the MS ABI, we may need to build an unprototyped musttail thunk.
const CIRGenFunctionInfo &fnInfo =
isUnprototyped ? (cgm.errorNYI("unprototyped must-tail thunk"),
cgm.getTypes().arrangeGlobalDeclaration(gd))
: cgm.getTypes().arrangeGlobalDeclaration(gd);
cir::FuncType thunkFnTy = cgm.getTypes().getFunctionType(fnInfo);
// This is to replace OG's casting to a function, keeping it here to
// streamline the 1-to-1 mapping from OG starting below.
cir::FuncOp thunkFn = thunk;
if (thunk.getFunctionType() != thunkFnTy) {
cir::FuncOp oldThunkFn = thunkFn;
assert(oldThunkFn.isDeclaration() && "Shouldn't replace non-declaration");
// Remove the name from the old thunk function and get a new thunk.
oldThunkFn.setName(StringRef());
thunkFn =
cir::FuncOp::create(cgm.getBuilder(), thunk->getLoc(), name.str(),
thunkFnTy, cir::GlobalLinkageKind::ExternalLinkage);
cgm.setCIRFunctionAttributes(md, fnInfo, thunkFn, /*isThunk=*/false);
if (!oldThunkFn->use_empty())
oldThunkFn->replaceAllUsesWith(thunkFn);
// Remove the old thunk.
oldThunkFn->erase();
}
bool abiHasKeyFunctions = cgm.getTarget().getCXXABI().hasKeyFunctions();
bool useAvailableExternallyLinkage = forVTable && abiHasKeyFunctions;
// If the type of the underlying GlobalValue is wrong, we'll have to replace
// it. It should be a declaration.
if (!thunkFn.isDeclaration()) {
if (!abiHasKeyFunctions || useAvailableExternallyLinkage) {
// There is already a thunk emitted for this function, do nothing.
return thunkFn;
}
setThunkProperties(cgm, thunkAdjustments, thunkFn, forVTable, gd);
return thunkFn;
}
// TODO(cir): Add "thunk" attribute if unprototyped.
cgm.setCIRFunctionAttributesForDefinition(cast<FunctionDecl>(gd.getDecl()),
thunkFn);
// Thunks for variadic methods are special because in general variadic
// arguments cannot be perfectly forwarded. In the general case, clang
// implements such thunks by cloning the original function body. However, for
// thunks with no return adjustment on targets that support musttail, we can
// use musttail to perfectly forward the variadic arguments.
bool shouldCloneVarArgs = false;
if (!isUnprototyped && thunkFn.getFunctionType().isVarArg()) {
shouldCloneVarArgs = true;
if (thunkAdjustments.Return.isEmpty()) {
switch (cgm.getTriple().getArch()) {
case llvm::Triple::x86_64:
case llvm::Triple::x86:
case llvm::Triple::aarch64:
shouldCloneVarArgs = false;
break;
default:
break;
}
}
}
if (shouldCloneVarArgs) {
if (useAvailableExternallyLinkage)
return thunkFn;
cgm.errorNYI("varargs thunk cloning");
} else {
// Normal thunk body generation.
mlir::OpBuilder::InsertionGuard guard(cgm.getBuilder());
CIRGenFunction cgf(cgm, cgm.getBuilder());
cgf.generateThunk(thunkFn, fnInfo, gd, thunkAdjustments, isUnprototyped);
}
setThunkProperties(cgm, thunkAdjustments, thunkFn, forVTable, gd);
return thunkFn;
}
void CIRGenVTables::emitThunks(GlobalDecl gd) {
const CXXMethodDecl *md =
cast<CXXMethodDecl>(gd.getDecl())->getCanonicalDecl();
@ -524,5 +917,6 @@ void CIRGenVTables::emitThunks(GlobalDecl gd) {
if (!thunkInfoVector)
return;
cgm.errorNYI(md->getSourceRange(), "emitThunks");
for (const ThunkInfo &thunk : *thunkInfoVector)
maybeEmitThunk(gd, thunk, /*forVTable=*/false);
}

View File

@ -95,6 +95,11 @@ public:
/// Emit the associated thunks for the given global decl.
void emitThunks(GlobalDecl gd);
/// Emit a thunk for the given global decl if needed, or return an existing
/// thunk.
cir::FuncOp maybeEmitThunk(GlobalDecl gd, const ThunkInfo &thunkAdjustments,
bool forVTable);
/// Generate all the class data required to be generated upon definition of a
/// KeyFunction. This includes the vtable, the RTTI data structure (if RTTI
/// is enabled) and the VTT (if the class has virtual bases).

View File

@ -0,0 +1,216 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -fclangir -emit-llvm %s -o %t-cir.ll
// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -emit-llvm %s -o %t.ll
// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s
// Note: -fno-rtti is used to simplify vtable output (no typeinfo references).
namespace Test1 {
// Non-virtual this-adjusting thunk for a void method.
// When C overrides f() from both A and B, the vtable for B-in-C needs a thunk
// that adjusts 'this' from B* to C* before calling C::f().
struct A {
virtual void f();
};
struct B {
virtual void f();
};
struct C : A, B {
void f() override;
};
void C::f() {}
} // namespace Test1
namespace Test2 {
// Non-virtual this-adjusting thunk for a method with a non-void return type.
// The thunk should forward the return value from the adjusted call.
struct A {
virtual int g();
};
struct B {
virtual int g();
};
struct C : A, B {
int g() override;
};
int C::g() { return 42; }
} // namespace Test2
namespace Test3 {
// Non-virtual this-adjusting thunks for destructors.
// When D inherits from both E and F, the complete and deleting destructors for
// F-in-D need thunks that adjust 'this'.
struct E {
virtual ~E();
};
struct F {
virtual ~F();
};
struct D : E, F {
~D() override;
};
D::~D() {}
} // namespace Test3
// In CIR, all globals are emitted before functions.
// Test1 vtable: C's vtable references the thunk for B's entry.
// CIR-DAG: cir.global "private" external @_ZTVN5Test11CE = #cir.vtable<{
// CIR-DAG: #cir.global_view<@_ZN5Test11C1fEv> : !cir.ptr<!u8i>
// CIR-DAG: #cir.global_view<@_ZThn8_N5Test11C1fEv> : !cir.ptr<!u8i>
// Test2 vtable: C's vtable references the thunk for B's entry.
// CIR-DAG: cir.global "private" external @_ZTVN5Test21CE = #cir.vtable<{
// CIR-DAG: #cir.global_view<@_ZN5Test21C1gEv> : !cir.ptr<!u8i>
// CIR-DAG: #cir.global_view<@_ZThn8_N5Test21C1gEv> : !cir.ptr<!u8i>
// Test3 vtable: D's vtable references D1, D0, and their thunks.
// CIR-DAG: cir.global "private" external @_ZTVN5Test31DE = #cir.vtable<{
// CIR-DAG: #cir.global_view<@_ZN5Test31DD1Ev> : !cir.ptr<!u8i>
// CIR-DAG: #cir.global_view<@_ZN5Test31DD0Ev> : !cir.ptr<!u8i>
// CIR-DAG: #cir.global_view<@_ZThn8_N5Test31DD1Ev> : !cir.ptr<!u8i>
// CIR-DAG: #cir.global_view<@_ZThn8_N5Test31DD0Ev> : !cir.ptr<!u8i>
// --- Test1: void method thunk ---
// CIR: cir.func {{.*}} @_ZN5Test11C1fEv
// The thunk adjusts 'this' by -8 bytes and calls C::f().
// CIR: cir.func {{.*}} @_ZThn8_N5Test11C1fEv(%arg0: !cir.ptr<
// CIR: %[[T1_THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init]
// CIR: cir.store %arg0, %[[T1_THIS_ADDR]]
// CIR: %[[T1_THIS:.*]] = cir.load %[[T1_THIS_ADDR]]
// CIR: %[[T1_CAST:.*]] = cir.cast bitcast %[[T1_THIS]] : !cir.ptr<{{.*}}> -> !cir.ptr<!u8i>
// CIR: %[[T1_OFFSET:.*]] = cir.const #cir.int<-8> : !s64i
// CIR: %[[T1_ADJUSTED:.*]] = cir.ptr_stride %[[T1_CAST]], %[[T1_OFFSET]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
// CIR: %[[T1_RESULT:.*]] = cir.cast bitcast %[[T1_ADJUSTED]] : !cir.ptr<!u8i> -> !cir.ptr<
// CIR: cir.call @_ZN5Test11C1fEv(%[[T1_RESULT]])
// CIR: cir.return
// --- Test2: non-void return type thunk ---
// CIR: cir.func {{.*}} @_ZN5Test21C1gEv
// CIR: cir.func {{.*}} @_ZThn8_N5Test21C1gEv(%arg0: !cir.ptr<
// CIR: %[[T2_THIS:.*]] = cir.load
// CIR: %[[T2_CAST:.*]] = cir.cast bitcast %[[T2_THIS]] : !cir.ptr<{{.*}}> -> !cir.ptr<!u8i>
// CIR: %[[T2_OFFSET:.*]] = cir.const #cir.int<-8> : !s64i
// CIR: %[[T2_ADJUSTED:.*]] = cir.ptr_stride %[[T2_CAST]], %[[T2_OFFSET]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
// CIR: %[[T2_RESULT:.*]] = cir.cast bitcast %[[T2_ADJUSTED]] : !cir.ptr<!u8i> -> !cir.ptr<
// CIR: %[[T2_RET:.*]] = cir.call @_ZN5Test21C1gEv(%[[T2_RESULT]])
// CIR: cir.store {{.*}} %[[T2_RET]]
// CIR: %[[T2_RET_VAL:.*]] = cir.load
// CIR: cir.return %[[T2_RET_VAL]]
// --- Test3: destructor thunks ---
// Complete destructor thunk: adjusts 'this' and calls D1.
// CIR: cir.func {{.*}} @_ZThn8_N5Test31DD1Ev(%arg0: !cir.ptr<
// CIR: %[[T3A_THIS:.*]] = cir.load
// CIR: %[[T3A_CAST:.*]] = cir.cast bitcast %[[T3A_THIS]] : !cir.ptr<{{.*}}> -> !cir.ptr<!u8i>
// CIR: %[[T3A_OFFSET:.*]] = cir.const #cir.int<-8> : !s64i
// CIR: %[[T3A_ADJUSTED:.*]] = cir.ptr_stride %[[T3A_CAST]], %[[T3A_OFFSET]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
// CIR: %[[T3A_RESULT:.*]] = cir.cast bitcast %[[T3A_ADJUSTED]] : !cir.ptr<!u8i> -> !cir.ptr<
// CIR: cir.call @_ZN5Test31DD1Ev(%[[T3A_RESULT]])
// CIR: cir.return
// Deleting destructor thunk: adjusts 'this' and calls D0.
// CIR: cir.func {{.*}} @_ZThn8_N5Test31DD0Ev(%arg0: !cir.ptr<
// CIR: %[[T3B_THIS:.*]] = cir.load
// CIR: %[[T3B_CAST:.*]] = cir.cast bitcast %[[T3B_THIS]] : !cir.ptr<{{.*}}> -> !cir.ptr<!u8i>
// CIR: %[[T3B_OFFSET:.*]] = cir.const #cir.int<-8> : !s64i
// CIR: %[[T3B_ADJUSTED:.*]] = cir.ptr_stride %[[T3B_CAST]], %[[T3B_OFFSET]] : (!cir.ptr<!u8i>, !s64i) -> !cir.ptr<!u8i>
// CIR: %[[T3B_RESULT:.*]] = cir.cast bitcast %[[T3B_ADJUSTED]] : !cir.ptr<!u8i> -> !cir.ptr<
// CIR: cir.call @_ZN5Test31DD0Ev(%[[T3B_RESULT]])
// CIR: cir.return
// --- LLVM checks ---
// LLVM: @_ZTVN5Test11CE = global { [3 x ptr], [3 x ptr] } {
// LLVM-SAME: [3 x ptr] [ptr null, ptr null, ptr @_ZN5Test11C1fEv],
// LLVM-SAME: [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test11C1fEv]
// LLVM-SAME: }
// LLVM: @_ZTVN5Test21CE = global { [3 x ptr], [3 x ptr] } {
// LLVM-SAME: [3 x ptr] [ptr null, ptr null, ptr @_ZN5Test21C1gEv],
// LLVM-SAME: [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test21C1gEv]
// LLVM-SAME: }
// LLVM: @_ZTVN5Test31DE = global { [4 x ptr], [4 x ptr] } {
// LLVM-SAME: [4 x ptr] [ptr null, ptr null, ptr @_ZN5Test31DD1Ev, ptr @_ZN5Test31DD0Ev],
// LLVM-SAME: [4 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test31DD1Ev, ptr @_ZThn8_N5Test31DD0Ev]
// LLVM-SAME: }
// LLVM: define {{.*}} void @_ZThn8_N5Test11C1fEv(ptr{{.*}})
// LLVM: %[[L1_THIS:.*]] = load ptr, ptr
// LLVM: %[[L1_ADJ:.*]] = getelementptr i8, ptr %[[L1_THIS]], i64 -8
// LLVM: call void @_ZN5Test11C1fEv(ptr{{.*}} %[[L1_ADJ]])
// LLVM: define {{.*}} i32 @_ZThn8_N5Test21C1gEv(ptr{{.*}})
// LLVM: %[[L2_THIS:.*]] = load ptr, ptr
// LLVM: %[[L2_ADJ:.*]] = getelementptr i8, ptr %[[L2_THIS]], i64 -8
// LLVM: %[[L2_RET:.*]] = call {{.*}} i32 @_ZN5Test21C1gEv(ptr{{.*}} %[[L2_ADJ]])
// LLVM: define {{.*}} void @_ZThn8_N5Test31DD1Ev(ptr{{.*}})
// LLVM: %[[L3A_THIS:.*]] = load ptr, ptr
// LLVM: %[[L3A_ADJ:.*]] = getelementptr i8, ptr %[[L3A_THIS]], i64 -8
// LLVM: call void @_ZN5Test31DD1Ev(ptr{{.*}} %[[L3A_ADJ]])
// LLVM: define {{.*}} void @_ZThn8_N5Test31DD0Ev(ptr{{.*}})
// LLVM: %[[L3B_THIS:.*]] = load ptr, ptr
// LLVM: %[[L3B_ADJ:.*]] = getelementptr i8, ptr %[[L3B_THIS]], i64 -8
// LLVM: call void @_ZN5Test31DD0Ev(ptr{{.*}} %[[L3B_ADJ]])
// --- OGCG checks ---
// OGCG: @_ZTVN5Test11CE = unnamed_addr constant { [3 x ptr], [3 x ptr] } {
// OGCG-SAME: [3 x ptr] [ptr null, ptr null, ptr @_ZN5Test11C1fEv],
// OGCG-SAME: [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test11C1fEv]
// OGCG-SAME: }
// OGCG: @_ZTVN5Test21CE = unnamed_addr constant { [3 x ptr], [3 x ptr] } {
// OGCG-SAME: [3 x ptr] [ptr null, ptr null, ptr @_ZN5Test21C1gEv],
// OGCG-SAME: [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test21C1gEv]
// OGCG-SAME: }
// OGCG: @_ZTVN5Test31DE = unnamed_addr constant { [4 x ptr], [4 x ptr] } {
// OGCG-SAME: [4 x ptr] [ptr null, ptr null, ptr @_ZN5Test31DD1Ev, ptr @_ZN5Test31DD0Ev],
// OGCG-SAME: [4 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test31DD1Ev, ptr @_ZThn8_N5Test31DD0Ev]
// OGCG-SAME: }
// OGCG: define {{.*}} void @_ZThn8_N5Test11C1fEv(ptr{{.*}})
// OGCG: %[[O1_THIS:.*]] = load ptr, ptr
// OGCG: %[[O1_ADJ:.*]] = getelementptr inbounds i8, ptr %[[O1_THIS]], i64 -8
// OGCG: call void @_ZN5Test11C1fEv(ptr{{.*}} %[[O1_ADJ]])
// OGCG: define {{.*}} i32 @_ZThn8_N5Test21C1gEv(ptr{{.*}})
// OGCG: %[[O2_THIS:.*]] = load ptr, ptr
// OGCG: %[[O2_ADJ:.*]] = getelementptr inbounds i8, ptr %[[O2_THIS]], i64 -8
// OGCG: {{.*}}call {{.*}} i32 @_ZN5Test21C1gEv(ptr{{.*}} %[[O2_ADJ]])
// OGCG: define {{.*}} void @_ZThn8_N5Test31DD1Ev(ptr{{.*}})
// OGCG: %[[O3A_THIS:.*]] = load ptr, ptr
// OGCG: %[[O3A_ADJ:.*]] = getelementptr inbounds i8, ptr %[[O3A_THIS]], i64 -8
// OGCG: call void @_ZN5Test31DD1Ev(ptr{{.*}} %[[O3A_ADJ]])
// OGCG: define {{.*}} void @_ZThn8_N5Test31DD0Ev(ptr{{.*}})
// OGCG: %[[O3B_THIS:.*]] = load ptr, ptr
// OGCG: %[[O3B_ADJ:.*]] = getelementptr inbounds i8, ptr %[[O3B_THIS]], i64 -8
// OGCG: call void @_ZN5Test31DD0Ev(ptr{{.*}} %[[O3B_ADJ]])