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..c001c9cf055f 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -234,6 +234,24 @@ 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; + + /// 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..09418ae2447a 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -243,6 +243,15 @@ class CIRGenItaniumCXXABI : public CIRGenCXXABI { } 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 58d10d8cd66b..2a3dd83b49d1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -826,14 +826,22 @@ cir::GlobalOp CIRGenModule::createGlobalOp(CIRGenModule &cgm, // 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 @@ -2675,6 +2683,11 @@ void CIRGenModule::setDSOLocal(mlir::Operation *op) const { } } +void CIRGenModule::setGVProperties(mlir::Operation *op, GlobalDecl gd) const { + assert(!cir::MissingFeatures::setDLLImportDLLExport()); + setGVPropertiesAux(op, dyn_cast(gd.getDecl())); +} + void CIRGenModule::setGVProperties(mlir::Operation *op, const NamedDecl *d) const { assert(!cir::MissingFeatures::setDLLImportDLLExport()); @@ -2710,10 +2723,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); @@ -2739,8 +2754,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; } @@ -3064,20 +3085,27 @@ 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()); if (!isIncompleteFunction && func.isDeclaration()) - getTargetCIRGenInfo().setTargetAttributes(globalDecl.getDecl(), func, - *this); + getTargetCIRGenInfo().setTargetAttributes(funcDecl, 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 diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 52282041a944..e6edcd48e657 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -680,8 +680,9 @@ class CIRGenModule : public CIRGenTypeCache { 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(mlir::Operation *op, GlobalDecl gd) const; + void setGVProperties(mlir::Operation *op, const NamedDecl *d) const; + void setGVPropertiesAux(mlir::Operation *op, const NamedDecl *d) const; /// Set the TLS mode for the given global Op for the thread-local /// variable declaration D. @@ -760,8 +761,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, diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp index a4a6a786fe4d..35ac4afaaf13 100644 --- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp @@ -44,7 +44,224 @@ 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()); + + 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); +} + +/// Perform return adjustment for a thunk. This mirrors CodeGen's +/// PerformReturnAdjustment logic. +/// TODO: Add null checking for non-reference return types like CodeGen does +static RValue performReturnAdjustment(CIRGenFunction &cgf, QualType resultType, + RValue rv, const ThunkInfo &thunk) { + mlir::Value returnValue = rv.getScalarVal(); + + // Get class information for adjustment + CXXRecordDecl *classDecl = resultType->getPointeeType()->getAsCXXRecordDecl(); + clang::CharUnits classAlign = cgf.CGM.getClassPointerAlignment(classDecl); + mlir::Type pointeeType = cgf.convertTypeForMem(resultType->getPointeeType()); + + // Perform the adjustment + returnValue = cgf.CGM.getCXXABI().performReturnAdjustment( + cgf, Address(returnValue, pointeeType, classAlign), classDecl, + thunk.Return); + + 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); + } + + // Start defining the function. + // NOTE(cir): No ApplyDebugLocation in CIR + StartFunction(GlobalDecl(), resultType, fn, fnInfo, functionArgs, + md->getLocation(), md->getLocation()); + // NOTE(cir): No ApplyDebugLocation in CIR + + // 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(); + + // Adjust the this pointer if necessary + mlir::Value adjustedThisPtr = + thunk ? CGM.getCXXABI().performThisAdjustment(*this, LoadCXXThisAddress(), + thisValueClass, *thunk) + : LoadCXXThis(); + + // Handle special cases requiring musttail (variadic, inalloca, unprototyped) + 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); + + // Handle destructor special case (may add implicit parameters) + // TODO: Implement adjustCallArgsForDestructorThunk if needed + // if (isa(MD)) + // CGM.getCXXABI().adjustCallArgsForDestructorThunk(*this, CurGD, CallArgs); + + // Add the rest of the method parameters + for (const ParmVarDecl *pd : md->parameters()) + emitDelegateCallArg(callArgs, pd, SourceLocation()); + + const FunctionProtoType *fpt = md->getType()->castAs(); + + // Determine the result type + QualType resultType = CGM.getCXXABI().HasThisReturn(CurGD) ? thisType + : CGM.getCXXABI().hasMostDerivedReturn(CurGD) + ? CGM.getASTContext().VoidPtrTy + : fpt->getReturnType(); + + // Determine if we need a return value slot + ReturnValueSlot slot; + if (!resultType->isVoidType() && hasAggregateEvaluationKind(resultType)) + slot = ReturnValueSlot(ReturnValue, resultType.isVolatileQualified(), + /*IsUnused=*/false, + /*IsExternallyDestructed=*/true); + + // Emit the call to the actual target function + CIRGenCallee cirCallee = CIRGenCallee::forDirect(callee, CurGD); + auto loc = builder.getUnknownLoc(); + RValue rv = emitCall(*CurFnInfo, cirCallee, slot, callArgs, + /*callOrTryCall=*/nullptr, /*IsMustTail=*/false, loc); + + // Apply return adjustment if needed (for covariant return types) + if (thunk && !thunk->Return.isEmpty()) + rv = performReturnAdjustment(*this, resultType, rv, *thunk); + + // Emit the return statement + if (!resultType->isVoidType() && slot.isNull()) + CGM.getCXXABI().emitReturnFromThunk(*this, rv, resultType); + + // Disable final ARC autorelease (not used in CIR, but kept for completeness) + // AutoreleaseResult = false; + + 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); + // NOTE(cir): No ApplyDebugLocation in CIR + + // 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 +449,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,32 +483,34 @@ 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); } @@ -776,7 +999,9 @@ cir::FuncOp CIRGenVTables::maybeEmitThunk(GlobalDecl GD, return ThunkFn; llvm_unreachable("NYI method, see OG GenerateVarArgsThunk"); } 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..24c6c82b38b3 --- /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 global{{.*}}@_ZThn{{[0-9]+}}_N7Derived3barEv +// CODEGEN-DAG: @_ZTV7Derived = linkonce_odr {{.*}}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..87898a0638ad --- /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 +} + +// ============================================================================ +// CIR Output - 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 - Both CIR and OGCG +// ============================================================================ + +// Check that vtable contains destructor thunks +// LLVM: @_ZTV7Derived = linkonce_odr global +// LLVM-DAG: @_ZThn16_N7DerivedD1Ev +// LLVM-DAG: @_ZThn16_N7DerivedD0Ev + +// OGCG: @_ZTV7Derived = linkonce_odr {{.*}} constant +// OGCG-DAG: @_ZThn16_N7DerivedD1Ev +// OGCG-DAG: @_ZThn16_N7DerivedD0Ev + +// ============================================================================ +// Thunk Implementation - LLVM Lowering vs OGCG +// ============================================================================ + +// 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..004154d507b5 --- /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 global +// 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 global +// 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 global +// 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..0e64fe47f3f8 --- /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 global +// 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..40fb2fb9dc58 --- /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 global +// 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..8dccb77990a6 --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk.cpp @@ -0,0 +1,113 @@ +// 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 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 global +// 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..c42fd8ed8f1e --- /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 global { [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]]) +