From ce0ac2b7ee2bc0826e072995445d5f17fd4520d8 Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Sat, 22 Nov 2025 17:32:21 -0800 Subject: [PATCH 1/2] Update [ghstack-poisoned] --- clang/lib/CIR/CodeGen/CIRGenCXX.cpp | 9 + clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp | 7 + clang/lib/CIR/CodeGen/CIRGenCXXABI.h | 18 ++ clang/lib/CIR/CodeGen/CIRGenFunction.cpp | 13 +- clang/lib/CIR/CodeGen/CIRGenFunction.h | 17 ++ clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp | 87 ++++++++ clang/lib/CIR/CodeGen/CIRGenModule.cpp | 33 ++- clang/lib/CIR/CodeGen/CIRGenVTables.cpp | 210 +++++++++++++++++- .../CodeGen/vtable-thunk-compare-codegen.cpp | 37 +++ clang/test/CIR/CodeGen/vtable-thunk.cpp | 46 ++++ clang/test/CIR/Lowering/vtable-thunk.cpp | 35 +++ 11 files changed, 488 insertions(+), 24 deletions(-) create mode 100644 clang/test/CIR/CodeGen/vtable-thunk-compare-codegen.cpp create mode 100644 clang/test/CIR/CodeGen/vtable-thunk.cpp create mode 100644 clang/test/CIR/Lowering/vtable-thunk.cpp diff --git a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp index 4b886ae15b87..30677c539242 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXX.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXX.cpp @@ -429,3 +429,12 @@ void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl, builder.setInsertionPointToEnd(block); cir::YieldOp::create(builder, addr->getLoc()); } + +void CIRGenFunction::finishThunk() { + // Clear these to restore the invariants expected by + // StartFunction/FinishFunction. + CurCodeDecl = nullptr; + CurFuncDecl = nullptr; + + finishFunction(SourceLocation()); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp index 672bc60268e0..e2cbe4b558f6 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp @@ -104,3 +104,10 @@ bool CIRGenCXXABI::requiresArrayCookie(const CXXNewExpr *E) { return E->getAllocatedType().isDestructedType(); } + +void CIRGenCXXABI::EmitReturnFromThunk(CIRGenFunction &CGF, RValue RV, + QualType ResultType) { + // Default implementation: just emit a normal return + 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..92f71c28b80b 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 This, + 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..fa79d48b3957 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -765,8 +765,8 @@ 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 +1158,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 +1365,13 @@ 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 670b90987fe1..5c8c47ddd067 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2130,6 +2130,23 @@ 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(); + + 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 a393e8bec03d..24285a9467c5 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 This, + 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, @@ -2148,6 +2157,84 @@ mlir::Attribute CIRGenItaniumCXXABI::getAddrOfRTTIDescriptor(mlir::Location loc, return CIRGenItaniumRTTIBuilder(*this, CGM).BuildTypeInfo(loc, Ty); } +mlir::Value +CIRGenItaniumCXXABI::performThisAdjustment(CIRGenFunction &CGF, Address This, + const CXXRecordDecl *UnadjustedClass, + const ThunkInfo &TI) { + // Handle trivial case: no adjustment needed + if (!TI.This.NonVirtual && !TI.This.Virtual.Itanium.VCallOffsetOffset) + return This.getPointer(); + + auto &builder = CGF.getBuilder(); + auto loc = builder.getUnknownLoc(); + mlir::Value V = This.getPointer(); + + // Apply non-virtual adjustment first (for base-to-derived cast) + if (TI.This.NonVirtual) { + // Cast to i8* for byte-level pointer arithmetic + auto i8PtrTy = builder.getUInt8PtrTy(); + V = builder.createBitcast(V, i8PtrTy); + + // Create offset constant (NonVirtual is in bytes) + auto offsetConst = builder.getSInt64(TI.This.NonVirtual, loc); + + // Perform pointer arithmetic: this_ptr + offset + V = builder.create(loc, i8PtrTy, V, offsetConst); + + // Cast back to original pointer type + V = builder.createBitcast(V, This.getType()); + } + + // Virtual adjustment (vcall offset lookup from vtable) + if (TI.This.Virtual.Itanium.VCallOffsetOffset) { + // TODO: Implement virtual adjustment - requires GetVTablePtr equivalent + llvm_unreachable( + "Virtual this adjustment NYI - requires vtable offset lookup"); + } + + return V; +} + +mlir::Value CIRGenItaniumCXXABI::performReturnAdjustment( + CIRGenFunction &CGF, Address Ret, const CXXRecordDecl *UnadjustedClass, + const ReturnAdjustment &RA) { + // Handle trivial case: no adjustment needed + if (!RA.NonVirtual && !RA.Virtual.Itanium.VBaseOffsetOffset) + return Ret.getPointer(); + + auto &builder = CGF.getBuilder(); + auto loc = builder.getUnknownLoc(); + mlir::Value V = Ret.getPointer(); + + // For return adjustment, virtual adjustment is applied first, + // then non-virtual (opposite order from this adjustment) + + // Virtual adjustment + if (RA.Virtual.Itanium.VBaseOffsetOffset) { + // TODO: Implement virtual return adjustment + llvm_unreachable( + "Virtual return adjustment NYI - requires vtable offset lookup"); + } + + // Apply non-virtual adjustment second (for derived-to-base cast) + if (RA.NonVirtual) { + // Cast to i8* for byte-level pointer arithmetic + auto i8PtrTy = builder.getUInt8PtrTy(); + V = builder.createBitcast(V, i8PtrTy); + + // Create offset constant + auto offsetConst = builder.getSInt64(RA.NonVirtual, loc); + + // Perform pointer arithmetic + V = builder.create(loc, i8PtrTy, V, offsetConst); + + // Cast back to original pointer type + V = builder.createBitcast(V, Ret.getType()); + } + + return V; +} + 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 a78187beba6e..70980d48d4a1 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 @@ -2710,10 +2718,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 +2749,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; } @@ -3058,7 +3074,6 @@ 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) { setCIRFunctionAttributes(globalDecl, diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp index a4a6a786fe4d..3c49d21ab5e4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp @@ -41,10 +41,201 @@ cir::FuncOp CIRGenModule::getAddrOfThunk(StringRef name, mlir::Type fnTy, /*DontDefer=*/true, /*IsThunk=*/true); } -static void setThunkProperties(CIRGenModule &cgm, const ThunkInfo &thunk, - cir::FuncOp thunkFn, bool forVTable, - GlobalDecl gd) { - llvm_unreachable("NYI"); +static void setThunkProperties(CIRGenModule &CGM, const ThunkInfo &Thunk, + cir::FuncOp ThunkFn, bool ForVTable, + GlobalDecl GD) { + // Set function linkage (like CodeGen does) + CGM.setFunctionLinkage(GD, ThunkFn); + + // Let the ABI set thunk-specific linkage + CGM.getCXXABI().setThunkLinkage(ThunkFn, ForVTable, GD, + !Thunk.Return.isEmpty()); + + // Note: setThunkLinkage already calls setGVProperties for us, + // so we don't need to call it again here (unlike CodeGen) + + // TODO: Handle COMDAT when CIR supports it + // CodeGen has: if (CGM.supportsCOMDAT() && ThunkFn->isWeakForLinker()) + // ThunkFn->setComdat(...); +} + +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::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()) { + // Perform return adjustment + RV = RValue::get(CGM.getCXXABI().performReturnAdjustment( + *this, RV.getAggregateAddress(), ThisValueClass, Thunk->Return)); + } + + // 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) { @@ -280,11 +471,10 @@ void CIRGenVTables::addVTableComponent(ConstantArrayBuilder &builder, layout.vtable_thunks()[nextVTableThunkIndex].first == componentIndex) { // Thunks. - llvm_unreachable("NYI"); - // auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; + auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second; - // nextVTableThunkIndex++; - // fnPtr = maybeEmitThunk(GD, thunkInfo, /*ForVTable=*/true); + nextVTableThunkIndex++; + fnPtr = maybeEmitThunk(GD, thunkInfo, /*ForVTable=*/true); } else { // Otherwise we can use the method definition directly. @@ -776,7 +966,9 @@ cir::FuncOp CIRGenVTables::maybeEmitThunk(GlobalDecl GD, return ThunkFn; llvm_unreachable("NYI method, see OG GenerateVarArgsThunk"); } else { - llvm_unreachable("NYI method, see OG generateThunk"); + // Generate the thunk body + 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..76d36234b74d --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk-compare-codegen.cpp @@ -0,0 +1,37 @@ +// XFAIL: * +// 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: diff -u %t.codegen.ll %t.cir.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 generate a thunk +// CIR: define linkonce_odr void @_ZThn{{[0-9]+}}_N7Derived3barEv +// CODEGEN: define linkonce_odr void @_ZThn{{[0-9]+}}_N7Derived3barEv + +// Both should have the thunk in the vtable +// CIR: @_ZTV7Derived = linkonce_odr global{{.*}}@_ZThn{{[0-9]+}}_N7Derived3barEv +// CODEGEN: @_ZTV7Derived = linkonce_odr global{{.*}}@_ZThn{{[0-9]+}}_N7Derived3barEv diff --git a/clang/test/CIR/CodeGen/vtable-thunk.cpp b/clang/test/CIR/CodeGen/vtable-thunk.cpp new file mode 100644 index 000000000000..8a0b40a8e257 --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk.cpp @@ -0,0 +1,46 @@ +// 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(); +} + +// Check thunk is in vtable +// CIR: cir.global linkonce_odr @_ZTV7Derived = #cir.vtable +// CIR: #cir.global_view<@_ZThn16_N7Derived3barEv> + +// Check that thunk function is generated with correct mangling +// CIR: cir.func linkonce_odr @_ZThn16_N7Derived3barEv +// CIR: cir.ptr_stride +// CIR: cir.call @_ZN7Derived3barEv + + +// LLVM: @_ZTV7Derived = linkonce_odr global +// LLVM-SAME: @_ZThn16_N7Derived3barEv + +// LLVM: define linkonce_odr void @_ZThn16_N7Derived3barEv +// LLVM-SAME: ptr + diff --git a/clang/test/CIR/Lowering/vtable-thunk.cpp b/clang/test/CIR/Lowering/vtable-thunk.cpp new file mode 100644 index 000000000000..c9d9f402b985 --- /dev/null +++ b/clang/test/CIR/Lowering/vtable-thunk.cpp @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll +// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM + +// Test that thunks lower correctly from CIR to LLVM IR + +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(); +} + +// Check vtable contains thunk with correct offset (16 bytes on x86_64) +// LLVM: @_ZTV7Derived = linkonce_odr global +// LLVM: @_ZThn16_N7Derived3barEv + +// Check thunk function is defined with correct linkage +// LLVM: define linkonce_odr void @_ZThn16_N7Derived3barEv(ptr{{.*}} %0) + From 48910becdadfce7142e748e7b96de66455314af0 Mon Sep 17 00:00:00 2001 From: Nathan Lanza Date: Sat, 22 Nov 2025 20:58:00 -0800 Subject: [PATCH 2/2] Update [ghstack-poisoned] --- .../vtable-thunk-virtual-inheritance.cpp | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 clang/test/CIR/CodeGen/vtable-thunk-virtual-inheritance.cpp 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]])