diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.h b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.h index 86064619af7d..62ee6607d5b6 100644 --- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.h +++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.h @@ -26,4 +26,33 @@ namespace cir {} // namespace cir namespace cir {} // namespace cir +// Casting specializations for CIRGlobalValueInterface to concrete types. +// OpInterfaces don't support casting to concrete types with reference return +// types, so we specialize CastInfo to return by value instead. +namespace llvm { +template +struct CastInfo< + To, cir::CIRGlobalValueInterface, + std::enable_if_t::value>> { + using CastReturnType = To; + + static inline bool isPossible(cir::CIRGlobalValueInterface interface) { + return llvm::isa(interface.getOperation()); + } + + static inline CastReturnType doCast(cir::CIRGlobalValueInterface interface) { + return llvm::cast(interface.getOperation()); + } + + static inline CastReturnType castFailed() { return CastReturnType(nullptr); } + + static inline CastReturnType + doCastIfPossible(cir::CIRGlobalValueInterface interface) { + if (!isPossible(interface)) + return castFailed(); + return doCast(interface); + } +}; +} // namespace llvm + #endif // MLIR_INTERFACES_CIR_OP_H_ diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td index 9f171c2ae4cd..27bfe4e6adbb 100644 --- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td +++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td @@ -146,6 +146,18 @@ let cppNamespace = "::cir" in { }] >, InterfaceMethod<"", + "void", "setGlobalVisibility", (ins "cir::VisibilityKind":$val), [{}], + /*defaultImplementation=*/[{ + $_op.setGlobalVisibility(val); + }] + >, + InterfaceMethod<"", + "void", "setLinkage", (ins "cir::GlobalLinkageKind":$val), [{}], + /*defaultImplementation=*/[{ + $_op.setLinkage(val); + }] + >, + InterfaceMethod<"", "bool", "isDSOLocal", (ins), [{}], /*defaultImplementation=*/[{ return $_op.getDsoLocal(); diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index 2e8f04e7e16d..a7a6e2f03f51 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -193,6 +193,7 @@ struct MissingFeatures { // ABIInfo queries. static bool useTargetLoweringABIInfo() { return false; } static bool isEmptyFieldForLayout() { return false; } + static bool ABIArgInfo() { return false; } // Misc static bool cacheRecordLayouts() { return false; } @@ -482,6 +483,8 @@ struct MissingFeatures { static bool skipTempCopy() { return false; } static bool dataLayoutPtrHandlingBasedOnLangAS() { return false; } + + static bool tailCall() { return false; } }; } // namespace cir diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp index 672bc60268e0..9d8b43587a19 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp @@ -104,3 +104,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"); + auto loc = cgf.getBuilder().getUnknownLoc(); + cgf.emitReturnOfRValue(loc, rv, resultType); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index 05dbcc4cd80c..9a82ea046935 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -234,6 +234,28 @@ class CIRGenCXXABI { 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; + + virtual void adjustCallArgsForDestructorThunk(CIRGenFunction &cgf, + GlobalDecl globalDecl, + CallArgList &callArgs) {} + + /// Emit a return from a thunk. + virtual void emitReturnFromThunk(CIRGenFunction &cgf, RValue rv, + QualType resultType); + virtual mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc, QualType Ty) = 0; virtual CatchTypeInfo diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 808fe95af5b2..db711506a03a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -765,8 +765,10 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn, // Create a scope in the symbol table to hold variable declarations. SymTableScopeTy varScope(symbolTable); // Compiler synthetized functions might have invalid slocs... - auto bSrcLoc = fd->getBody()->getBeginLoc(); - auto eSrcLoc = fd->getBody()->getEndLoc(); + auto bSrcLoc = + (fd && fd->getBody()) ? fd->getBody()->getBeginLoc() : SourceLocation(); + auto eSrcLoc = + (fd && fd->getBody()) ? fd->getBody()->getEndLoc() : SourceLocation(); auto unknownLoc = builder.getUnknownLoc(); auto fnBeginLoc = bSrcLoc.isValid() ? getLoc(bSrcLoc) : unknownLoc; @@ -1158,11 +1160,11 @@ void CIRGenFunction::StartFunction(GlobalDecl gd, QualType retTy, llvm_unreachable("NYI"); // Apply xray attributes to the function (as a string, for now) - if (d->getAttr()) { + if (d && d->getAttr()) { assert(!cir::MissingFeatures::xray()); } - if (ShouldXRayInstrumentFunction()) { + if (d && ShouldXRayInstrumentFunction()) { assert(!cir::MissingFeatures::xray()); } @@ -1365,12 +1367,15 @@ void CIRGenFunction::StartFunction(GlobalDecl gd, QualType retTy, // Location of the store to the param storage tracked as beginning of // the function body. - auto fnBodyBegin = getLoc(fd->getBody()->getBeginLoc()); + auto fnBodyBegin = (fd && fd->getBody()) + ? getLoc(fd->getBody()->getBeginLoc()) + : getLoc(Loc); builder.CIRBaseBuilderTy::createStore(fnBodyBegin, paramVal, addr); } assert(builder.getInsertionBlock() && "Should be valid"); - auto fnEndLoc = getLoc(fd->getBody()->getEndLoc()); + auto fnEndLoc = (fd && fd->getBody()) ? getLoc(fd->getBody()->getEndLoc()) + : getLoc(Loc); // When the current function is not void, create an address to store the // result value. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 081136b33e8f..420eafbc21d7 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2131,6 +2131,24 @@ class CIRGenFunction : public CIRGenTypeCache { void emitDestructorBody(FunctionArgList &Args); + /// Generate a thunk for the given method. + void generateThunk(cir::FuncOp fn, const CIRGenFunctionInfo &fnInfo, + GlobalDecl gd, const ThunkInfo &thunk, + bool isUnprototyped); + + void startThunk(cir::FuncOp fn, GlobalDecl gd, + const CIRGenFunctionInfo &fnInfo, bool isUnprototyped); + + void emitCallAndReturnForThunk(cir::FuncOp callee, const ThunkInfo *thunk, + bool isUnprototyped); + + /// Finish thunk generation. + void finishThunk(); + + /// Emit a musttail call for a thunk with a potentially adjusted this pointer. + void emitMustTailThunk(GlobalDecl gd, mlir::Value adjustedThisPtr, + cir::FuncOp callee); + mlir::LogicalResult emitDoStmt(const clang::DoStmt &S); mlir::Value emitDynamicCast(Address ThisAddr, const CXXDynamicCastExpr *DCE); diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index 30da5a357f66..cd597fcc7572 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -239,10 +239,19 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI { if (ForVTable && !Thunk.hasLocalLinkage()) Thunk.setLinkage(cir::GlobalLinkageKind::AvailableExternallyLinkage); const auto *ND = cast(GD.getDecl()); - CGM.setGVProperties(Thunk.getOperation(), ND); + 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; bool useThunkForDtorVariant(const CXXDestructorDecl *Dtor, @@ -2144,6 +2153,63 @@ mlir::Attribute CIRGenItaniumCXXABI::getAddrOfRTTIDescriptor(mlir::Location loc, return CIRGenItaniumRTTIBuilder(*this, CGM).BuildTypeInfo(loc, Ty); } +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(); + + auto &builder = cgf.getBuilder(); + auto loc = builder.getUnknownLoc(); + auto 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) { + auto offsetConst = builder.getSInt64(nonVirtualAdjustment, loc); + v = builder.create(loc, i8PtrTy, v, offsetConst); + } + + // Perform the virtual adjustment if we have one. + mlir::Value resultPtr; + if (virtualAdjustment) { + llvm_unreachable("Virtual adjustment NYI - requires vtable offset lookup"); + } else { + resultPtr = v; + } + + // In a derived-to-base conversion, the non-virtual adjustment is + // applied second. + if (nonVirtualAdjustment && isReturnAdjustment) { + auto offsetConst = builder.getSInt64(nonVirtualAdjustment, loc); + resultPtr = + builder.create(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); +} + void CIRGenItaniumCXXABI::emitVTableDefinitions(CIRGenVTables &CGVT, const CXXRecordDecl *RD) { auto VTable = getAddrOfVTable(RD, CharUnits()); diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 0902f0751bae..8bfdbb7cef12 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -35,6 +35,7 @@ #include "clang/AST/Expr.h" #include "clang/Basic/Cuda.h" #include "clang/CIR/Dialect/IR/CIRTypes.h" +#include "clang/CIR/Interfaces/CIROpInterfaces.h" #include "clang/CIR/MissingFeatures.h" #include "clang/AST/ASTConsumer.h" @@ -470,7 +471,7 @@ static bool shouldAssumeDSOLocal(const CIRGenModule &cgm, // executable, a copy relocation will be needed at link time. dso_local is // excluded for thread-local variables because they generally don't support // copy relocations. - if (auto globalOp = dyn_cast(gv.getOperation())) + if (auto globalOp = dyn_cast(gv)) if (!globalOp.getTlsModelAttr()) return true; @@ -825,14 +826,22 @@ cir::GlobalOp CIRGenModule::createGlobalOp( // Some global emissions are triggered while emitting a function, e.g. // void s() { const char *s = "yolo"; ... } // - // Be sure to insert global before the current function + // Save the current function context for later insertion logic auto *curCGF = cgm.getCurrCIRGenFun(); - if (curCGF) - builder.setInsertionPoint(curCGF->CurFn); + + // Clear insertion point to prevent auto-insertion by create() + // We'll manually insert at the correct location below + builder.clearInsertionPoint(); g = cir::GlobalOp::create(builder, loc, name, t, isConstant, linkage, addrSpace); - if (!curCGF) { + + // Manually insert at the correct location + if (curCGF) { + // Insert before the current function being generated + cgm.getModule().insert(mlir::Block::iterator(curCGF->CurFn), g); + } else { + // Insert at specified point or at end of module if (insertPoint) cgm.getModule().insert(insertPoint, g); else @@ -847,7 +856,8 @@ cir::GlobalOp CIRGenModule::createGlobalOp( return g; } -void CIRGenModule::setCommonAttributes(GlobalDecl gd, mlir::Operation *gv) { +void CIRGenModule::setCommonAttributes(GlobalDecl gd, + cir::CIRGlobalValueInterface gv) { const Decl *d = gd.getDecl(); if (isa_and_nonnull(d)) setGVProperties(gv, dyn_cast(d)); @@ -867,13 +877,14 @@ void CIRGenModule::setCommonAttributes(GlobalDecl gd, mlir::Operation *gv) { assert(!cir::MissingFeatures::addUsedOrCompilerUsedGlobal()); } -void CIRGenModule::setNonAliasAttributes(GlobalDecl gd, mlir::Operation *go) { +void CIRGenModule::setNonAliasAttributes(GlobalDecl gd, + cir::CIRGlobalValueInterface gv) { const Decl *d = gd.getDecl(); - setCommonAttributes(gd, go); + setCommonAttributes(gd, gv); if (d) { - auto gv = llvm::dyn_cast_or_null(go); - if (gv) { + auto globalOp = llvm::dyn_cast_or_null(gv); + if (globalOp) { if (d->hasAttr()) assert(!cir::MissingFeatures::addUsedGlobal()); if (d->getAttr()) @@ -885,7 +896,7 @@ void CIRGenModule::setNonAliasAttributes(GlobalDecl gd, mlir::Operation *go) { if (d->getAttr()) assert(!cir::MissingFeatures::addSectionAttributes()); } - auto f = llvm::dyn_cast_or_null(go); + auto f = llvm::dyn_cast_or_null(gv); if (f) { if (d->hasAttr()) assert(!cir::MissingFeatures::addUsedGlobal()); @@ -898,17 +909,17 @@ void CIRGenModule::setNonAliasAttributes(GlobalDecl gd, mlir::Operation *go) { if (const auto *csa = d->getAttr()) { assert(!cir::MissingFeatures::setSectionForFuncOp()); - if (gv) - gv.setSection(csa->getName()); + if (globalOp) + globalOp.setSection(csa->getName()); if (f) assert(!cir::MissingFeatures::setSectionForFuncOp()); } else if (const auto *sa = d->getAttr()) - if (gv) - gv.setSection(sa->getName()); + if (globalOp) + globalOp.setSection(sa->getName()); if (f) assert(!cir::MissingFeatures::setSectionForFuncOp()); } - getTargetCIRGenInfo().setTargetAttributes(d, go, *this); + getTargetCIRGenInfo().setTargetAttributes(d, gv, *this); } static llvm::SmallVector indexesOfArrayAttr(mlir::ArrayAttr indexes) { @@ -1072,6 +1083,44 @@ void CIRGenModule::setTLSMode(mlir::Operation *op, const VarDecl &d) const { global.setTlsModel(tlm); } +static void setGlobalVisibilityHelper(const CIRGenModule &cgm, + cir::CIRGlobalValueInterface gv, + cir::VisibilityKind visibility) { + gv.setGlobalVisibility(cir::VisibilityKind::Default); + // Also update MLIR symbol visibility to match linkage + if (auto globalOp = dyn_cast(gv.getOperation())) + mlir::SymbolTable::setSymbolVisibility(globalOp, + cgm.getMLIRVisibility(globalOp)); + else if (auto funcOp = dyn_cast(gv.getOperation())) + mlir::SymbolTable::setSymbolVisibility( + funcOp, cgm.getMLIRVisibilityFromCIRLinkage(funcOp.getLinkage())); +} + +// This is the equivalent to GlobalValue::setLinkage in LLVM codegen. +static void gvSetLinkage(const CIRGenModule &cgm, + cir::CIRGlobalValueInterface gv, + GlobalLinkageKind linkage) { + if (isLocalLinkage(linkage)) { + setGlobalVisibilityHelper(cgm, gv, cir::VisibilityKind::Default); + assert(!cir::MissingFeatures::setDSOLocal()); + } + gv.setLinkage(linkage); + assert(!cir::MissingFeatures::setDSOLocal()); +} + +static void setLinkageForGV(const CIRGenModule &cgm, + cir::CIRGlobalValueInterface global, + const NamedDecl *namedDecl) { + // Set linkage and visibility in case we never see a definition. + LinkageInfo lv = namedDecl->getLinkageAndVisibility(); + // Don't set internal linkage on declarations. + // "extern_weak" is overloaded in LLVM; we probably should have + // separate linkage types for this. + if (isExternallyVisible(lv.getLinkage()) && + (namedDecl->hasAttr() || namedDecl->isWeakImported())) + gvSetLinkage(cgm, global, cir::GlobalLinkageKind::ExternalWeakLinkage); +} + /// If the specified mangled name is not in the module, /// create and return an mlir GlobalOp with the specified type (TODO(cir): /// address space). @@ -1192,10 +1241,12 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, // FIXME: This code is overly simple and should be merged with other global // handling. - gv.setAlignmentAttr(getSize(astContext.getDeclAlign(d))); gv.setConstant(d->getType().isConstantStorage( astContext, /*ExcludeCtor=*/false, /*ExcludeDtor=*/false)); - // TODO(cir): setLinkageForGV(GV, D); + + gv.setAlignmentAttr(getSize(astContext.getDeclAlign(d))); + + setLinkageForGV(*this, gv, d); if (d->getTLSKind()) { if (d->getTLSKind() == VarDecl::TLS_Dynamic) @@ -1217,12 +1268,13 @@ CIRGenModule::getOrCreateCIRGlobal(StringRef mangledName, mlir::Type ty, gv.setSectionAttr(builder.getStringAttr(sa->getName())); } - gv.setGlobalVisibilityAttr(getGlobalVisibilityAttrFromDecl(d)); - // Handle XCore specific ABI requirements. if (getTriple().getArch() == llvm::Triple::xcore) assert(0 && "not implemented"); + if (const auto *_ = d->getAttr()) + llvm_unreachable("NYI"); + // Check if we a have a const declaration with an initializer, we maybe // able to emit it as available_externally to expose it's value to the // optimizer. @@ -1569,10 +1621,10 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *d, if (const SectionAttr *sa = d->getAttr()) gv.setSectionAttr(builder.getStringAttr(sa->getName())); - gv.setGlobalVisibilityAttr(getGlobalVisibilityAttrFromDecl(d)); - - // TODO(cir): - // GV->setAlignment(getContext().getDeclAlign(D).getAsAlign()); + CharUnits alignVal = getASTContext().getDeclAlign(d); + // Check for alignment specifed in an 'omp allocate' directive. + assert(!cir::MissingFeatures::openMP()); + gv.setAlignment(alignVal.getAsAlign().value()); // On Darwin, unlike other Itanium C++ ABI platforms, the thread-wrapper // function is only defined alongside the variable, not also alongside @@ -1596,11 +1648,13 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *d, assert(0 && "not implemented"); } - // Set CIR linkage and DLL storage class. - gv.setLinkage(linkage); - // FIXME(cir): setLinkage should likely set MLIR's visibility automatically. - gv.setVisibility(getMLIRVisibilityFromCIRLinkage(linkage)); - // TODO(cir): handle DLL storage classes in CIR? + // HLSL variables in the input address space maps like memory-mapped + // variables. Even if they are 'static', they are externally initialized and + // read/write by the hardware/driver/pipeline. + if (langOpts.HLSL) + llvm_unreachable("NYI"); + + gvSetLinkage(*this, gv, linkage); if (d->hasAttr()) assert(!cir::MissingFeatures::setDLLStorageClass()); else if (d->hasAttr()) @@ -1630,7 +1684,6 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *d, maybeSetTrivialComdat(*d, gv); - // TODO(cir): // Emit the initializer function if necessary. if (needsGlobalCtor || needsGlobalDtor) { globalOpContext = gv; @@ -1638,9 +1691,10 @@ void CIRGenModule::emitGlobalVarDefinition(const clang::VarDecl *d, globalOpContext = nullptr; } - // TODO(cir): sanitizers (reportGlobalToASan) and global variable debug - // information. + // SanitizerMD->reportGlobal(GV, *D, NeedsGlobalCtor); assert(!cir::MissingFeatures::sanitizeOther()); + + // Emit global variable debug information. assert(!cir::MissingFeatures::generateDebugInfo()); } @@ -2667,9 +2721,34 @@ void CIRGenModule::emitTentativeDefinition(const VarDecl *d) { emitGlobalVarDefinition(d); } -void CIRGenModule::setGlobalVisibility(mlir::Operation *gv, +void CIRGenModule::setGlobalVisibility(cir::CIRGlobalValueInterface gv, const NamedDecl *d) const { - assert(!cir::MissingFeatures::setGlobalVisibility()); + // Internal definitions always have default visibility. + if (gv.hasLocalLinkage()) { + setGlobalVisibilityHelper(*this, gv, cir::VisibilityKind::Default); + return; + } + if (!d) + return; + + // Set visibility for definitions, and for declarations if requested globally + // or set explicitly. + LinkageInfo lv = d->getLinkageAndVisibility(); + + // OpenMP declare target variables must be visible to the host so they can + // be registered. We require protected visibility unless the variable has + // the DT_nohost modifier and does not need to be registered. + if (getASTContext().getLangOpts().OpenMP) + assert(!cir::MissingFeatures::openMP()); + + if (getASTContext().getLangOpts().HLSL) + llvm_unreachable("HLSL visibility NYI"); + + assert(!cir::MissingFeatures::setDLLImportDLLExport()); + + if (lv.isVisibilityExplicit() || getLangOpts().SetVisibilityForExternDecls || + !gv.isDeclarationForLinker()) + gv.setGlobalVisibility(getCIRVisibilityKind(lv.getVisibility())); } void CIRGenModule::setDSOLocal(mlir::Operation *op) const { @@ -2679,16 +2758,22 @@ void CIRGenModule::setDSOLocal(mlir::Operation *op) const { } } -void CIRGenModule::setGVProperties(mlir::Operation *op, +void CIRGenModule::setGVProperties(cir::CIRGlobalValueInterface gv, + GlobalDecl gd) const { + assert(!cir::MissingFeatures::setDLLImportDLLExport()); + setGVPropertiesAux(gv, dyn_cast(gd.getDecl())); +} + +void CIRGenModule::setGVProperties(cir::CIRGlobalValueInterface gv, const NamedDecl *d) const { assert(!cir::MissingFeatures::setDLLImportDLLExport()); - setGVPropertiesAux(op, d); + setGVPropertiesAux(gv, d); } -void CIRGenModule::setGVPropertiesAux(mlir::Operation *op, +void CIRGenModule::setGVPropertiesAux(cir::CIRGlobalValueInterface gv, const NamedDecl *d) const { - setGlobalVisibility(op, d); - setDSOLocal(op); + setGlobalVisibility(gv, d); + setDSOLocal(gv); assert(!cir::MissingFeatures::setPartition()); } @@ -2714,10 +2799,12 @@ cir::FuncOp CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name, // Some global emissions are triggered while emitting a function, e.g. // void s() { x.method() } // - // Be sure to insert a new function before a current one. + // Save the current function context for later insertion logic auto *curCGF = getCurrCIRGenFun(); - if (curCGF) - builder.setInsertionPoint(curCGF->CurFn); + + // Clear insertion point to prevent auto-insertion by create() + // We'll manually insert at the correct location below + builder.clearInsertionPoint(); f = cir::FuncOp::create(builder, loc, name, ty); @@ -2743,8 +2830,14 @@ cir::FuncOp CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name, // Set the special member attribute for this function, if applicable. setCXXSpecialMemberAttr(f, fd); - if (!curCGF) + // Manually insert at the correct location + if (curCGF) { + // Insert before the current function being generated + theModule.insert(mlir::Block::iterator(curCGF->CurFn), f); + } else { + // Insert at end of module theModule.push_back(f); + } } return f; } @@ -3061,6 +3154,30 @@ void CIRGenModule::setCIRFunctionAttributes(GlobalDecl gd, func.setCallingConv(callingConv); } +void CIRGenModule::createFunctionTypeMetadataForIcall(const FunctionDecl *fd, + cir::FuncOp func) { + // Only if we are checking indirect calls. + if (!getLangOpts().Sanitize.has(SanitizerKind::CFIICall)) + return; + + // Non-static class methods are handled via vtable or member function pointer + // checks elsewhere. + if (isa(fd) && !cast(fd)->isStatic()) + return; + + llvm_unreachable("NYI: type metadata for CFI indirect call checking"); +} + +void CIRGenModule::createIndirectFunctionTypeMD(const FunctionDecl *fd, + cir::FuncOp func) { + llvm_unreachable( + "NYI: indirect function type metadata for call graph section"); +} + +void CIRGenModule::setKCFIType(const FunctionDecl *fd, cir::FuncOp func) { + llvm_unreachable("NYI: KCFI type metadata"); +} + void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl, cir::FuncOp func, bool isIncompleteFunction, @@ -3068,28 +3185,82 @@ void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl, // NOTE(cir): Original CodeGen checks if this is an intrinsic. In CIR we // represent them in dedicated ops. The correct attributes are ensured during // translation to LLVM. Thus, we don't need to check for them here. - assert(!isThunk && "isThunk NYI"); - if (!isIncompleteFunction) { + const auto *funcDecl = dyn_cast(globalDecl.getDecl()); + + if (!isIncompleteFunction) setCIRFunctionAttributes(globalDecl, getTypes().arrangeGlobalDeclaration(globalDecl), func, isThunk); + + // Add the Returned attribute for "this", except for iOS 5 and earlier + // where substantial code, including the libstdc++ dylib, was compiled with + // GCC and does not actually return "this". + if (!isThunk && getCXXABI().HasThisReturn(globalDecl) && + !(getTriple().isiOS() && getTriple().isOSVersionLT(6))) { + llvm_unreachable("NYI"); } - // TODO(cir): Complete the remaining part of the function. - assert(!cir::MissingFeatures::setFunctionAttributes()); + // Only a few attributes are set on declarations; these may later be + // overridden by a definition. + + setLinkageForGV(*this, func, funcDecl); + setGVProperties(func, funcDecl); if (!isIncompleteFunction && func.isDeclaration()) - getTargetCIRGenInfo().setTargetAttributes(globalDecl.getDecl(), func, - *this); - - // TODO(cir): This needs a lot of work to better match CodeGen. That - // ultimately ends up in setGlobalVisibility, which already has the linkage of - // the LLVM GV (corresponding to our FuncOp) computed, so it doesn't have to - // recompute it here. This is a minimal fix for now. - if (!isLocalLinkage(getFunctionLinkage(globalDecl))) { - const auto *decl = globalDecl.getDecl(); - func.setGlobalVisibilityAttr(getGlobalVisibilityAttrFromDecl(decl)); + getTargetCIRGenInfo().setTargetAttributes(funcDecl, func, *this); + + if (const auto *_ = funcDecl->getAttr()) + llvm_unreachable("NYI"); + else if (const auto *_ = funcDecl->getAttr()) + llvm_unreachable("NYI"); + + if (const auto *ea = funcDecl->getAttr()) { + if (ea->isError()) + assert(!cir::MissingFeatures::setFunctionAttributes()); + else if (ea->isWarning()) + assert(!cir::MissingFeatures::setFunctionAttributes()); + } + + // If we plan on emitting this inline builtin, we can't treat it as a builtin. + if (funcDecl->isInlineBuiltinDeclaration()) { + llvm_unreachable("NYI"); + } + + if (funcDecl->isReplaceableGlobalAllocationFunction()) { + // A replaceable global allocation function does not act like a builtin by + // default, only if it is invoked by a new-expression or delete-expression. + assert(!cir::MissingFeatures::attributeBuiltin()); + } + + if (isa(funcDecl) || isa(funcDecl)) + assert(!cir::MissingFeatures::unnamedAddr()); + else if (const auto *md = dyn_cast(funcDecl)) + if (md->isVirtual()) + assert(!cir::MissingFeatures::unnamedAddr()); + + // Don't emit entries for function declarations in the cross-DSO mode. This + // is handled with better precision by the receiving DSO. But if jump tables + // are non-canonical then we need type metadata in order to produce the local + // jump table. + if (!codeGenOpts.SanitizeCfiCrossDso || + !codeGenOpts.SanitizeCfiCanonicalJumpTables) + createFunctionTypeMetadataForIcall(funcDecl, func); + + if (codeGenOpts.CallGraphSection) + createIndirectFunctionTypeMD(funcDecl, func); + + if (langOpts.Sanitize.has(SanitizerKind::KCFI)) + setKCFIType(funcDecl, func); + + if (getLangOpts().OpenMP && funcDecl->hasAttr()) + llvm_unreachable("NYI"); + + if (codeGenOpts.InlineMaxStackSize != UINT_MAX) + llvm_unreachable("NYI"); + + if (const auto *_ = funcDecl->getAttr()) { + llvm_unreachable("NYI"); } } diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index a2e17d9330d7..10eecb10cd5b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -472,6 +472,18 @@ class CIRGenModule : public CIRGenTypeCache { llvm_unreachable("unknown visibility!"); } + static cir::VisibilityKind getCIRVisibilityKind(Visibility V) { + switch (V) { + case DefaultVisibility: + return cir::VisibilityKind::Default; + case HiddenVisibility: + return cir::VisibilityKind::Hidden; + case ProtectedVisibility: + return cir::VisibilityKind::Protected; + } + llvm_unreachable("unknown visibility!"); + } + llvm::DenseMap ConstantStringMap; /// Return a constant array for the given string. @@ -520,7 +532,7 @@ class CIRGenModule : public CIRGenTypeCache { /// Objective-C method, function, global variable). /// /// NOTE: This should only be called for definitions. - void setCommonAttributes(GlobalDecl GD, mlir::Operation *GV); + void setCommonAttributes(GlobalDecl gd, cir::CIRGlobalValueInterface gv); const TargetCIRGenInfo &getTargetCIRGenInfo(); const ABIInfo &getABIInfo(); @@ -681,12 +693,16 @@ class CIRGenModule : public CIRGenTypeCache { mlir::Type convertType(clang::QualType type); /// Set the visibility for the given global. - void setGlobalVisibility(mlir::Operation *Op, const NamedDecl *D) const; + void setGlobalVisibility(cir::CIRGlobalValueInterface gv, + const NamedDecl *d) const; void setDSOLocal(mlir::Operation *Op) const; /// Set visibility, dllimport/dllexport and dso_local. /// This must be called after dllimport/dllexport is set. - void setGVProperties(mlir::Operation *Op, const NamedDecl *D) const; - void setGVPropertiesAux(mlir::Operation *Op, const NamedDecl *D) const; + void setGVProperties(cir::CIRGlobalValueInterface gv, GlobalDecl gd) const; + void setGVProperties(cir::CIRGlobalValueInterface gv, + const NamedDecl *d) const; + void setGVPropertiesAux(cir::CIRGlobalValueInterface gv, + const NamedDecl *d) const; /// Set the TLS mode for the given global Op for the thread-local /// variable declaration D. @@ -765,8 +781,8 @@ class CIRGenModule : public CIRGenTypeCache { void UpdateCompletedType(const clang::TagDecl *TD); /// Set function attributes for a function declaration. - void setFunctionAttributes(GlobalDecl GD, cir::FuncOp F, - bool IsIncompleteFunction, bool IsThunk); + void setFunctionAttributes(GlobalDecl globalDecl, cir::FuncOp func, + bool isIncompleteFunction, bool isThunk); /// Set the CIR function attributes (sext, zext, etc). void setCIRFunctionAttributes(GlobalDecl GD, const CIRGenFunctionInfo &info, @@ -777,6 +793,17 @@ class CIRGenModule : public CIRGenTypeCache { void setCIRFunctionAttributesForDefinition(const Decl *decl, cir::FuncOp func); + /// Create and attach type metadata to the given function. + void createFunctionTypeMetadataForIcall(const FunctionDecl *fd, + cir::FuncOp func); + + /// Create and attach type metadata if the function is a potential indirect + /// call target to support call graph section. + void createIndirectFunctionTypeMD(const FunctionDecl *fd, cir::FuncOp func); + + /// Set type metadata to the given function. + void setKCFIType(const FunctionDecl *fd, cir::FuncOp func); + void emitGlobalDefinition(clang::GlobalDecl D, mlir::Operation *Op = nullptr); void emitGlobalFunctionDefinition(clang::GlobalDecl D, mlir::Operation *Op); void emitGlobalVarDefinition(const clang::VarDecl *D, @@ -982,7 +1009,7 @@ class CIRGenModule : public CIRGenTypeCache { /// pipeline since LLVM has opaque pointers but CIR not. void replacePointerTypeArgs(cir::FuncOp OldF, cir::FuncOp NewF); - void setNonAliasAttributes(GlobalDecl GD, mlir::Operation *GV); + void setNonAliasAttributes(GlobalDecl gd, cir::CIRGlobalValueInterface gv); /// Map source language used to a CIR attribute. cir::SourceLanguage getCIRSourceLanguage(); diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp index a4a6a786fe4d..ca846ee69180 100644 --- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp @@ -44,7 +44,256 @@ cir::FuncOp CIRGenModule::getAddrOfThunk(StringRef name, mlir::Type fnTy, static void setThunkProperties(CIRGenModule &cgm, const ThunkInfo &thunk, cir::FuncOp thunkFn, bool forVTable, GlobalDecl gd) { - llvm_unreachable("NYI"); + cgm.setFunctionLinkage(gd, thunkFn); + cgm.getCXXABI().setThunkLinkage(thunkFn, forVTable, gd, + !thunk.Return.isEmpty()); + + // Set the right visibility. + cgm.setGVProperties(thunkFn, gd); + + if (!cgm.getCXXABI().exportThunk()) { + assert(!cir::MissingFeatures::setDLLStorageClass()); + cgm.setDSOLocal(static_cast(thunkFn)); + } + + if (cgm.supportsCOMDAT() && thunkFn.isWeakForLinker()) + thunkFn.setComdat(true); +} + +static RValue performReturnAdjustment(CIRGenFunction &cgf, QualType resultType, + RValue rv, const ThunkInfo &thunk) { + // Emit the return adjustment. + bool nullCheckValue = !resultType->isReferenceType(); + + mlir::Value returnValue = rv.getScalarVal(); + + if (nullCheckValue) + llvm_unreachable( + "NYI: return adjustment with null check for non-reference types"); + + CXXRecordDecl *classDecl = resultType->getPointeeType()->getAsCXXRecordDecl(); + clang::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) + llvm_unreachable( + "NYI: 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(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()->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(md)) + CGM.getCXXABI().addImplicitStructorParams(*this, resultType, + functionArgs); + } + + assert(!cir::MissingFeatures::generateDebugInfo()); + // auto nl = ApplyDebugLocation::createEmpty(*this); + // Start defining the function. + StartFunction(GlobalDecl(), resultType, fn, fnInfo, functionArgs, + md->getLocation(), md->getLocation()); + assert(!cir::MissingFeatures::generateDebugInfo()); + // auto al = ApplyDebugLocation::createArtificial(*this); + + // 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(CurGD.getDecl()) && + "Please use a new CGF for this thunk"); + const CXXMethodDecl *md = cast(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. + if (CurFnInfo->usesInAlloca() || 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->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(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(); + +#ifndef NDEBUG + assert(!cir::MissingFeatures::ABIArgInfo()); + const CIRGenFunctionInfo &callFnInfo = CGM.getTypes().arrangeCXXMethodCall( + callArgs, fpt, RequiredArgs::forPrototypePlus(fpt, 1), prefixArgs); + // assert(callFnInfo.getRegParm() == CurFnInfo->getRegParm() && + // callFnInfo.isNoReturn() == CurFnInfo->isNoReturn() && + // callFnInfo.getCallingConvention() == + // CurFnInfo->getCallingConvention()); + // assert(isa(md) || // ignore dtor return types + // similar(callFnInfo.getReturnInfo(), callFnInfo.getReturnType(), + // CurFnInfo->getReturnInfo(), CurFnInfo->getReturnType())); + assert(callFnInfo.arg_size() == CurFnInfo->arg_size()); + // for (unsigned i = 0, e = CurFnInfo->arg_size(); i != e; ++i) + // assert(similar(callFnInfo.arg_begin()[i].info, + // callFnInfo.arg_begin()[i].type, + // CurFnInfo->arg_begin()[i].info, + // CurFnInfo->arg_begin()[i].type)); +#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; + if (!resultType->isVoidType() && hasAggregateEvaluationKind(resultType)) + slot = ReturnValueSlot(ReturnValue, resultType.isVolatileQualified(), + /*IsUnused=*/false, + /*IsExternallyDestructed=*/true); + + // Now emit our call. + CIRGenCallee cirCallee = CIRGenCallee::forDirect(callee, CurGD); + auto loc = builder.getUnknownLoc(); + RValue rv = emitCall(*CurFnInfo, cirCallee, slot, callArgs, + /*callOrTryCall=*/nullptr, /*IsMustTail=*/false, loc); + + // Consider return adjustment if we have ThunkInfo. + if (thunk && !thunk->Return.isEmpty()) + rv = performReturnAdjustment(*this, resultType, rv, *thunk); + else + assert(!cir::MissingFeatures::tailCall()); + + // Emit return. + if (!resultType->isVoidType() && slot.isNull()) + CGM.getCXXABI().emitReturnFromThunk(*this, rv, resultType); + + // Disable final ARC autorelease. + assert(!cir::MissingFeatures::ARC()); + + finishThunk(); +} + +void CIRGenFunction::emitMustTailThunk(GlobalDecl gd, + mlir::Value adjustedThisPtr, + cir::FuncOp callee) { + llvm_unreachable("emitMustTailThunk NYI - requires musttail call generation"); +} + +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 + auto unknownLoc = builder.getUnknownLoc(); + LexicalScope lexScope{*this, unknownLoc, entryBb}; + + startThunk(fn, gd, fnInfo, isUnprototyped); + assert(!cir::MissingFeatures::generateDebugInfo()); + // auto al = ApplyDebugLocation::createArtificial(*this); + + // 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) + llvm_unreachable("NYI: unprototyped thunk placeholder type"); + else + ty = CGM.getTypes().GetFunctionType(fnInfo); + + cir::FuncOp callee = CGM.GetAddrOfFunction(gd, ty, /*ForVTable=*/true); + + // Make the call and return the result. + emitCallAndReturnForThunk(callee, &thunk, isUnprototyped); } static bool UseRelativeLayout(const CIRGenModule &CGM) { @@ -232,6 +481,10 @@ void CIRGenVTables::addVTableComponent(ConstantArrayBuilder &builder, case VTableComponent::CK_DeletingDtorPointer: { GlobalDecl GD = component.getGlobalDecl(); + const bool isThunk = + nextVTableThunkIndex < layout.vtable_thunks().size() && + layout.vtable_thunks()[nextVTableThunkIndex].first == componentIndex; + if (CGM.getLangOpts().CUDA) { llvm_unreachable("NYI"); } @@ -262,38 +515,42 @@ void CIRGenVTables::addVTableComponent(ConstantArrayBuilder &builder, }; cir::FuncOp fnPtr; + + // Pure virtual member functions. if (cast(GD.getDecl())->isPureVirtual()) { - // Pure virtual member functions. if (!PureVirtualFn) PureVirtualFn = getSpecialVirtualFn(CGM.getCXXABI().getPureVirtualCallName()); fnPtr = PureVirtualFn; - } else if (cast(GD.getDecl())->isDeleted()) { // Deleted virtual member functions. + } else if (cast(GD.getDecl())->isDeleted()) { if (!DeletedVirtualFn) DeletedVirtualFn = getSpecialVirtualFn(CGM.getCXXABI().getDeletedVirtualCallName()); fnPtr = DeletedVirtualFn; - } else if (nextVTableThunkIndex < layout.vtable_thunks().size() && - layout.vtable_thunks()[nextVTableThunkIndex].first == - componentIndex) { // Thunks. - llvm_unreachable("NYI"); - // auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; - - // nextVTableThunkIndex++; - // fnPtr = maybeEmitThunk(GD, thunkInfo, /*ForVTable=*/true); + } else if (isThunk) { + auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; + + nextVTableThunkIndex++; + fnPtr = maybeEmitThunk(GD, thunkInfo, /*ForVTable=*/true); + if (CGM.getCodeGenOpts().PointerAuth.CXXVirtualFunctionPointers) { + assert(thunkInfo.Method && "Method not set"); + GD = GD.getWithDecl(thunkInfo.Method); + } - } else { // Otherwise we can use the method definition directly. + } else { auto fnTy = CGM.getTypes().GetFunctionTypeForVTable(GD); fnPtr = CGM.GetAddrOfFunction(GD, fnTy, /*ForVTable=*/true); + if (CGM.getCodeGenOpts().PointerAuth.CXXVirtualFunctionPointers) + llvm_unreachable("NYI"); } if (useRelativeLayout()) { - llvm_unreachable("NYI"); + llvm_unreachable("relative layout vtable components"); } else { return builder.add(cir::GlobalViewAttr::get( CGM.getBuilder().getUInt8PtrTy(), @@ -756,27 +1013,30 @@ cir::FuncOp CIRGenVTables::maybeEmitThunk(GlobalDecl GD, // 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; + bool shouldCloneVarArgs = false; if (!IsUnprototyped && ThunkFn.getFunctionType().isVarArg()) { - ShouldCloneVarArgs = true; + 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; + shouldCloneVarArgs = false; break; default: break; } } } - if (ShouldCloneVarArgs) { + + if (shouldCloneVarArgs) { if (UseAvailableExternallyLinkage) return ThunkFn; - llvm_unreachable("NYI method, see OG GenerateVarArgsThunk"); + llvm_unreachable("NYI"); } else { - llvm_unreachable("NYI method, see OG generateThunk"); + // Normal thunk body generation. + CIRGenFunction cgf(CGM, CGM.getBuilder()); + cgf.generateThunk(ThunkFn, FnInfo, GD, ThunkAdjustments, IsUnprototyped); } setThunkProperties(CGM, ThunkAdjustments, ThunkFn, ForVTable, GD); diff --git a/clang/test/CIR/CodeGen/vtable-thunk-compare-codegen.cpp b/clang/test/CIR/CodeGen/vtable-thunk-compare-codegen.cpp new file mode 100644 index 000000000000..3d50d7577031 --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk-compare-codegen.cpp @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.cir.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.codegen.ll +// RUN: FileCheck --input-file=%t.cir.ll %s --check-prefix=CIR +// RUN: FileCheck --input-file=%t.codegen.ll %s --check-prefix=CODEGEN + +// Test that CIR thunk generation matches CodeGen behavior + +class Base1 { +public: + virtual void foo() {} +}; + +class Base2 { +public: + virtual void bar() {} +}; + +class Derived : public Base1, public Base2 { +public: + void bar() override {} +}; + +void test() { + Derived d; + Base2* b2 = &d; + b2->bar(); +} + +// Both should have the thunk in the vtable +// CIR-DAG: @_ZTV7Derived = linkonce_odr constant {{.*}} @_ZThn{{[0-9]+}}_N7Derived3barEv +// CODEGEN-DAG: @_ZTV7Derived = linkonce_odr unnamed_addr constant {{.*}} @_ZThn{{[0-9]+}}_N7Derived3barEv + +// Both should generate a thunk +// CIR-DAG: define linkonce_odr void @_ZThn{{[0-9]+}}_N7Derived3barEv +// CODEGEN-DAG: define linkonce_odr void @_ZThn{{[0-9]+}}_N7Derived3barEv diff --git a/clang/test/CIR/CodeGen/vtable-thunk-destructor.cpp b/clang/test/CIR/CodeGen/vtable-thunk-destructor.cpp new file mode 100644 index 000000000000..794783b3df68 --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk-destructor.cpp @@ -0,0 +1,86 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -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 -fclangir -emit-llvm %s -o %t.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ogcg.ll %s + +// Test thunk generation for virtual destructors in multiple inheritance + +class Base1 { +public: + virtual ~Base1() {} + int x; +}; + +class Base2 { +public: + virtual ~Base2() {} + int y; +}; + +class Derived : public Base1, public Base2 { +public: + ~Derived() override {} +}; + +void test() { + Base2* b2 = new Derived(); + delete b2; // Uses destructor thunk +} + +// ============================================================================ +// Destructor Thunks +// ============================================================================ + +// Derived's destructor needs thunks when called through Base2* because +// Base2 is at offset 16 in Derived (after Base1's vtable + data) +// The Itanium ABI generates multiple destructor variants: +// - D2 (base object destructor) +// - D1 (complete object destructor) +// - D0 (deleting destructor) + +// Check for complete destructor thunk (D1) - appears first in output +// CIR: cir.func comdat linkonce_odr @_ZThn16_N7DerivedD1Ev +// CIR: cir.ptr_stride +// CIR: cir.call @_ZN7DerivedD1Ev + +// Check for deleting destructor thunk (D0) - appears second in output +// CIR: cir.func comdat linkonce_odr @_ZThn16_N7DerivedD0Ev +// CIR: cir.ptr_stride +// CIR: cir.call @_ZN7DerivedD0Ev + +// ============================================================================ +// VTable Structure +// ============================================================================ + +// Check that vtable contains destructor thunks +// LLVM: @_ZTV7Derived = linkonce_odr constant +// LLVM-DAG: @_ZThn16_N7DerivedD1Ev +// LLVM-DAG: @_ZThn16_N7DerivedD0Ev + +// OGCG: @_ZTV7Derived = linkonce_odr {{.*}} constant +// OGCG-DAG: @_ZThn16_N7DerivedD1Ev +// OGCG-DAG: @_ZThn16_N7DerivedD0Ev + +// ============================================================================ +// Thunk Implementation +// ============================================================================ + +// Complete destructor thunk (D1) +// LLVM-LABEL: define linkonce_odr void @_ZThn16_N7DerivedD1Ev +// LLVM: getelementptr i8, ptr %{{[0-9]+}}, i64 -16 +// LLVM: call void @_ZN7DerivedD1Ev + +// OGCG-LABEL: define linkonce_odr void @_ZThn16_N7DerivedD1Ev +// OGCG: getelementptr inbounds i8, ptr %{{.*}}, i64 -16 +// OGCG: call void @_ZN7DerivedD1Ev + +// Deleting destructor thunk (D0) +// LLVM-LABEL: define linkonce_odr void @_ZThn16_N7DerivedD0Ev +// LLVM: getelementptr i8, ptr %{{[0-9]+}}, i64 -16 +// LLVM: call void @_ZN7DerivedD0Ev + +// OGCG-LABEL: define linkonce_odr void @_ZThn16_N7DerivedD0Ev +// OGCG: getelementptr inbounds i8, ptr %{{.*}}, i64 -16 +// OGCG: call void @_ZN7DerivedD0Ev diff --git a/clang/test/CIR/CodeGen/vtable-thunk-edge-cases.cpp b/clang/test/CIR/CodeGen/vtable-thunk-edge-cases.cpp new file mode 100644 index 000000000000..bb99d5bdf0ef --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk-edge-cases.cpp @@ -0,0 +1,178 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -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 -fclangir -emit-llvm %s -o %t.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ogcg.ll %s + +// Test edge cases for thunk generation: +// 1. Deep inheritance hierarchies +// 2. Empty base optimization affecting offsets +// 3. Multiple overrides in diamond inheritance +// 4. Mix of polymorphic and non-polymorphic bases + +// ============================================================================ +// Test 1: Deep Inheritance Hierarchy +// ============================================================================ + +class Level0 { +public: + virtual void method0() {} +}; + +class Level1 : public Level0 { +public: + virtual void method1() {} + int data1; +}; + +class Level2A : public Level1 { +public: + virtual void method2a() {} + int data2a; +}; + +class Level2B { +public: + virtual void method2b() {} + int data2b; +}; + +class DeepDerived : public Level2A, public Level2B { +public: + void method2b() override {} +}; + +void testDeep() { + DeepDerived d; + Level2B* b = &d; + b->method2b(); // Needs thunk due to Level2B offset +} + +// Check thunk for deep hierarchy +// CIR: cir.func comdat linkonce_odr @_ZThn{{[0-9]+}}_N11DeepDerived8method2bEv + +// LLVM: @_ZTV11DeepDerived = linkonce_odr constant +// LLVM-SAME: @_ZThn{{[0-9]+}}_N11DeepDerived8method2bEv + +// OGCG: @_ZTV11DeepDerived = linkonce_odr {{.*}} constant +// OGCG-SAME: @_ZThn{{[0-9]+}}_N11DeepDerived8method2bEv + +// ============================================================================ +// Test 2: Empty Base Optimization +// ============================================================================ + +// Empty base class should not affect layout +class EmptyBase { +public: + virtual void emptyMethod() {} +}; + +class NonEmptyBase { +public: + virtual void nonEmptyMethod() {} + int data; +}; + +class EmptyDerived : public EmptyBase, public NonEmptyBase { +public: + void nonEmptyMethod() override {} +}; + +void testEmpty() { + EmptyDerived d; + NonEmptyBase* b = &d; + b->nonEmptyMethod(); // Needs thunk, offset affected by empty base +} + +// Check thunk with empty base +// CIR: cir.func comdat linkonce_odr @_ZThn{{[0-9]+}}_N12EmptyDerived14nonEmptyMethodEv + +// LLVM: @_ZTV12EmptyDerived = linkonce_odr constant +// LLVM-SAME: @_ZThn{{[0-9]+}}_N12EmptyDerived14nonEmptyMethodEv + +// OGCG: @_ZTV12EmptyDerived = linkonce_odr {{.*}} constant +// OGCG-SAME: @_ZThn{{[0-9]+}}_N12EmptyDerived14nonEmptyMethodEv + +// ============================================================================ +// Test 3: Multiple Methods Requiring Different Thunk Offsets +// ============================================================================ + +class MultiBase1 { +public: + virtual void method1() {} + int data1; +}; + +class MultiBase2 { +public: + virtual void method2a() {} + virtual void method2b() {} + int data2; +}; + +class MultiDerived : public MultiBase1, public MultiBase2 { +public: + void method2a() override {} + void method2b() override {} +}; + +void testMulti() { + MultiDerived d; + MultiBase2* b = &d; + b->method2a(); // Both need same thunk offset + b->method2b(); +} + +// Check multiple thunks with same offset +// CIR: cir.func comdat linkonce_odr @_ZThn{{[0-9]+}}_N12MultiDerived8method2aEv +// CIR: cir.func comdat linkonce_odr @_ZThn{{[0-9]+}}_N12MultiDerived8method2bEv + +// LLVM: @_ZTV12MultiDerived = linkonce_odr constant +// LLVM-DAG: @_ZThn{{[0-9]+}}_N12MultiDerived8method2aEv +// LLVM-DAG: @_ZThn{{[0-9]+}}_N12MultiDerived8method2bEv + +// OGCG: @_ZTV12MultiDerived = linkonce_odr {{.*}} constant +// OGCG-DAG: @_ZThn{{[0-9]+}}_N12MultiDerived8method2aEv +// OGCG-DAG: @_ZThn{{[0-9]+}}_N12MultiDerived8method2bEv + +// ============================================================================ +// Thunk Implementation Checks +// ============================================================================ + +// Verify thunk implementations match between CIR lowering and OGCG + +// Deep hierarchy thunk +// LLVM-LABEL: define linkonce_odr void @_ZThn{{[0-9]+}}_N11DeepDerived8method2bEv +// LLVM: getelementptr i8, ptr %{{[0-9]+}}, i64 -{{[0-9]+}} +// LLVM: call void @_ZN11DeepDerived8method2bEv + +// OGCG-LABEL: define linkonce_odr void @_ZThn{{[0-9]+}}_N11DeepDerived8method2bEv +// OGCG: getelementptr inbounds i8, ptr %{{.*}}, i64 -{{[0-9]+}} +// OGCG: call void @_ZN11DeepDerived8method2bEv + +// Empty base thunk +// LLVM-LABEL: define linkonce_odr void @_ZThn{{[0-9]+}}_N12EmptyDerived14nonEmptyMethodEv +// LLVM: getelementptr i8, ptr %{{[0-9]+}}, i64 -{{[0-9]+}} +// LLVM: call void @_ZN12EmptyDerived14nonEmptyMethodEv + +// OGCG-LABEL: define linkonce_odr void @_ZThn{{[0-9]+}}_N12EmptyDerived14nonEmptyMethodEv +// OGCG: getelementptr inbounds i8, ptr %{{.*}}, i64 -{{[0-9]+}} +// OGCG: call void @_ZN12EmptyDerived14nonEmptyMethodEv + +// Multiple methods thunks +// LLVM-LABEL: define linkonce_odr void @_ZThn{{[0-9]+}}_N12MultiDerived8method2aEv +// LLVM: getelementptr i8, ptr %{{[0-9]+}}, i64 -{{[0-9]+}} +// LLVM: call void @_ZN12MultiDerived8method2aEv + +// OGCG-LABEL: define linkonce_odr void @_ZThn{{[0-9]+}}_N12MultiDerived8method2aEv +// OGCG: getelementptr inbounds i8, ptr %{{.*}}, i64 -{{[0-9]+}} +// OGCG: call void @_ZN12MultiDerived8method2aEv + +// LLVM-LABEL: define linkonce_odr void @_ZThn{{[0-9]+}}_N12MultiDerived8method2bEv +// LLVM: getelementptr i8, ptr %{{[0-9]+}}, i64 -{{[0-9]+}} +// LLVM: call void @_ZN12MultiDerived8method2bEv + +// OGCG-LABEL: define linkonce_odr void @_ZThn{{[0-9]+}}_N12MultiDerived8method2bEv +// OGCG: getelementptr inbounds i8, ptr %{{.*}}, i64 -{{[0-9]+}} +// OGCG: call void @_ZN12MultiDerived8method2bEv diff --git a/clang/test/CIR/CodeGen/vtable-thunk-multibase.cpp b/clang/test/CIR/CodeGen/vtable-thunk-multibase.cpp new file mode 100644 index 000000000000..b9f4a02dfe09 --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk-multibase.cpp @@ -0,0 +1,67 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -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 -fclangir -emit-llvm %s -o %t.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ogcg.ll %s + +// Test thunk generation with multiple base classes +// This validates thunks for void-returning methods (no return adjustment). +// Full covariant return adjustment for pointer-returning methods is NYI. + +class Base1 { +public: + virtual void foo() {} +}; + +class Base2 { +public: + virtual void bar() {} + int data; +}; + +class Derived : public Base1, public Base2 { +public: + void bar() override {} +}; + +void test() { + Derived d; + Base2* b2 = &d; + b2->bar(); // Needs this-adjusting thunk (no return adjustment) +} + +// ============================================================================ +// CIR Output - Thunk with This-Adjustment Only +// ============================================================================ + +// Derived::bar() needs a thunk when called through Base2* because +// Base2 is at offset 8 in Derived (after Base1's vtable pointer) + +// CIR: cir.func comdat linkonce_odr @_ZThn8_N7Derived3barEv +// CIR: cir.ptr_stride +// CIR: cir.call @_ZN7Derived3barEv + +// ============================================================================ +// VTable Structure - Both CIR and OGCG +// ============================================================================ + +// Check that vtable contains the thunk +// LLVM: @_ZTV7Derived = linkonce_odr constant +// LLVM-SAME: @_ZThn8_N7Derived3barEv + +// OGCG: @_ZTV7Derived = linkonce_odr {{.*}} constant +// OGCG-SAME: @_ZThn8_N7Derived3barEv + +// ============================================================================ +// Thunk Implementation - LLVM Lowering vs OGCG +// ============================================================================ + +// CIR lowering should produce this-adjustment (no return adjustment for void) +// LLVM-LABEL: define linkonce_odr void @_ZThn8_N7Derived3barEv +// LLVM: getelementptr i8, ptr %{{[0-9]+}}, i64 -8 +// LLVM: call void @_ZN7Derived3barEv + +// OGCG-LABEL: define linkonce_odr void @_ZThn8_N7Derived3barEv +// OGCG: getelementptr inbounds i8, ptr %{{.*}}, i64 -8 +// OGCG: call void @_ZN7Derived3barEv diff --git a/clang/test/CIR/CodeGen/vtable-thunk-virtual-inheritance.cpp b/clang/test/CIR/CodeGen/vtable-thunk-virtual-inheritance.cpp new file mode 100644 index 000000000000..981144ef1db5 --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk-virtual-inheritance.cpp @@ -0,0 +1,74 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -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 -fclangir -emit-llvm %s -o %t.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ogcg.ll %s + +// Test thunk generation with virtual inheritance (diamond problem) + +class Base { +public: + virtual void method() {} + int a; +}; + +class Left : public virtual Base { +public: + virtual void leftMethod() {} + int b; +}; + +class Right : public virtual Base { +public: + virtual void rightMethod() {} + int c; +}; + +class Diamond : public Left, public Right { +public: + void leftMethod() override {} + void rightMethod() override {} +}; + +void test() { + Diamond d; + Left* l = &d; + Right* r = &d; + l->leftMethod(); + r->rightMethod(); +} + +// ============================================================================ +// CIR Output - Thunk Generation +// ============================================================================ + +// Diamond's rightMethod needs a thunk because Right is at offset 16 +// leftMethod doesn't need a thunk because Left is at offset 0 +// CIR: cir.func comdat linkonce_odr @_ZThn16_N7Diamond11rightMethodEv +// CIR: cir.ptr_stride +// CIR: cir.call @_ZN7Diamond11rightMethodEv + +// ============================================================================ +// VTable Structure - Both CIR and OGCG +// ============================================================================ + +// Check that vtable contains the thunk reference at the correct position +// LLVM: @_ZTV7Diamond = linkonce_odr constant +// LLVM-SAME: @_ZThn16_N7Diamond11rightMethodEv + +// OGCG: @_ZTV7Diamond = linkonce_odr {{.*}} constant +// OGCG-SAME: @_ZThn16_N7Diamond11rightMethodEv + +// ============================================================================ +// Thunk Implementation - LLVM Lowering vs OGCG +// ============================================================================ + +// CIR lowering should produce the same this-pointer adjustment as OGCG +// LLVM-LABEL: define linkonce_odr void @_ZThn16_N7Diamond11rightMethodEv +// LLVM: %[[VAR1:[0-9]+]] = getelementptr i8, ptr %{{[0-9]+}}, i64 -16 +// LLVM: call void @_ZN7Diamond11rightMethodEv(ptr %[[VAR1]]) + +// OGCG-LABEL: define linkonce_odr void @_ZThn16_N7Diamond11rightMethodEv +// OGCG: %[[VAR2:[0-9]+]] = getelementptr inbounds i8, ptr %{{.*}}, i64 -16 +// OGCG: call void @_ZN7Diamond11rightMethodEv(ptr {{.*}} %[[VAR2]]) diff --git a/clang/test/CIR/CodeGen/vtable-thunk.cpp b/clang/test/CIR/CodeGen/vtable-thunk.cpp new file mode 100644 index 000000000000..e2262d81ef37 --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk.cpp @@ -0,0 +1,111 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -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 -mconstructor-aliases -fclangir -emit-llvm -fno-clangir-call-conv-lowering %s -o %t.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s + +// Test basic thunk generation for multiple inheritance with non-virtual thunks + +class Base1 { +public: + virtual void foo() {} + int x; +}; + +class Base2 { +public: + virtual void bar() {} + int y; +}; + +class Derived : public Base1, public Base2 { +public: + void bar() override {} +}; + +void test() { + Derived d; + Base2* b2 = &d; + b2->bar(); +} + +// ============================================================================ +// CIR VTable Structure +// ============================================================================ + +// Check thunk is in vtable +// CIR: cir.global constant linkonce_odr @_ZTV7Derived = #cir.vtable +// CIR: #cir.global_view<@_ZThn16_N7Derived3barEv> + +// ============================================================================ +// CIR Thunk Function Generation +// ============================================================================ + +// Check that thunk function is generated with: +// - comdat attribute (for deduplication across TUs) +// - linkonce_odr linkage (one definition rule, discardable) +// - correct mangling (_ZThn_) +// CIR: cir.func comdat linkonce_odr @_ZThn16_N7Derived3barEv + +// ============================================================================ +// CIR Thunk Implementation - This Pointer Adjustment +// ============================================================================ + +// The thunk should: +// 1. Adjust the 'this' pointer by the offset (-16 bytes) +// 2. Call the actual implementation with the adjusted pointer + +// CIR: cir.ptr_stride +// CIR: cir.call @_ZN7Derived3barEv + +// ============================================================================ +// LLVM IR Output Validation +// ============================================================================ + +// LLVM: @_ZTV7Derived = linkonce_odr constant +// LLVM-SAME: @_ZThn16_N7Derived3barEv + +// LLVM: define linkonce_odr void @_ZThn16_N7Derived3barEv +// LLVM-SAME: ptr + +// ============================================================================ +// Test Multiple Base Classes (Different Offsets) +// ============================================================================ + +class A { +public: + virtual void methodA() {} + long long a; // 8 bytes +}; + +class B { +public: + virtual void methodB() {} + long long b; // 8 bytes +}; + +class C { +public: + virtual void methodC() {} + long long c; // 8 bytes +}; + +class Multi : public A, public B, public C { +public: + void methodB() override {} + void methodC() override {} +}; + +void test_multi() { + Multi m; + B* pb = &m; + C* pc = &m; + pb->methodB(); + pc->methodC(); +} + +// Different thunks for different offsets +// Offset to B should be 16 (A's vptr + a) +// CIR: cir.func comdat linkonce_odr @_ZThn16_N5Multi7methodBEv + +// Offset to C should be 32 (A's vptr + a + B's vptr + b) +// CIR: cir.func comdat linkonce_odr @_ZThn32_N5Multi7methodCEv diff --git a/clang/test/CIR/Lowering/vtable-thunk.cpp b/clang/test/CIR/Lowering/vtable-thunk.cpp new file mode 100644 index 000000000000..176d75a36ce0 --- /dev/null +++ b/clang/test/CIR/Lowering/vtable-thunk.cpp @@ -0,0 +1,55 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ogcg.ll %s + +// Test that thunks lower correctly from CIR to LLVM IR and match OGCG output + +class Base1 { +public: + virtual void foo() {} + int x; +}; + +class Base2 { +public: + virtual void bar() {} + int y; +}; + +class Derived : public Base1, public Base2 { +public: + void bar() override {} +}; + +void test() { + Derived d; + Base2* b2 = &d; + b2->bar(); +} + +// ============================================================================ +// VTable Structure Validation +// ============================================================================ + +// Check vtable contains thunk with correct offset (16 bytes on x86_64) +// Both CIR and OGCG should produce identical vtable structure +// LLVM: @_ZTV7Derived = linkonce_odr constant { [4 x ptr], [3 x ptr] } +// LLVM-SAME: @_ZThn16_N7Derived3barEv + +// OGCG: @_ZTV7Derived = linkonce_odr {{.*}} constant { [4 x ptr], [3 x ptr] } +// OGCG-SAME: @_ZThn16_N7Derived3barEv + +// ============================================================================ +// Thunk Implementation - This Pointer Adjustment +// ============================================================================ + +// CIR lowering should produce the same pointer adjustment as OGCG +// LLVM-LABEL: define linkonce_odr void @_ZThn16_N7Derived3barEv +// LLVM: %[[VAR1:[0-9]+]] = getelementptr i8, ptr %{{[0-9]+}}, i64 -16 +// LLVM: call void @_ZN7Derived3barEv(ptr %[[VAR1]]) + +// OGCG-LABEL: define linkonce_odr void @_ZThn16_N7Derived3barEv +// OGCG: %[[VAR2:[0-9]+]] = getelementptr inbounds i8, ptr %{{.*}}, i64 -16 +// OGCG: call void @_ZN7Derived3barEv(ptr {{.*}} %[[VAR2]]) + diff --git a/clang/test/CIR/divergences/vtable-thunk-destructor.cpp b/clang/test/CIR/divergences/vtable-thunk-destructor.cpp new file mode 100644 index 000000000000..3146589e27a9 --- /dev/null +++ b/clang/test/CIR/divergences/vtable-thunk-destructor.cpp @@ -0,0 +1,38 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.ogcg.ll +// RUN: FileCheck --input-file=%t.ll %s +// RUN: FileCheck --input-file=%t.ogcg.ll %s + +// XFAIL: * + +// Test thunk generation for virtual destructors in multiple inheritance + +class Base1 { +public: + virtual ~Base1() {} + int x; +}; + +class Base2 { +public: + virtual ~Base2() {} + int y; +}; + +class Derived : public Base1, public Base2 { +public: + ~Derived() override {} +}; + +void test() { + Base2* b2 = new Derived(); + delete b2; // Uses destructor thunk +} + +// CHECK-LABEL: define linkonce_odr void @_ZThn16_N7DerivedD1Ev +// CHECK: getelementptr inbounds i8, ptr %{{.*}}, i64 -16 +// CHECK: call void @_ZN7DerivedD1Ev + +// CHECK-LABEL: define linkonce_odr void @_ZThn16_N7DerivedD0Ev +// CHECK: getelementptr inbounds i8, ptr %{{.*}}, i64 -16 +// CHECK: call void @_ZN7DerivedD0Ev