diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h index f688795a1616..02d22bb62890 100644 --- a/clang/include/clang/CIR/MissingFeatures.h +++ b/clang/include/clang/CIR/MissingFeatures.h @@ -329,6 +329,7 @@ struct MissingFeatures { static bool pointerOverflowSanitizer() { return false; } static bool preservedAccessIndexRegion() { return false; } static bool requiresCleanups() { return false; } + static bool returnValueSlotFeatures() { return false; } static bool runCleanupsScope() { return false; } static bool sanitizers() { return false; } static bool setDLLStorageClass() { return false; } diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp index aa0182e9677e..e5e4f860faf3 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp @@ -96,3 +96,11 @@ bool CIRGenCXXABI::requiresArrayCookie(const CXXNewExpr *e) { return e->getAllocatedType().isDestructedType(); } + +void CIRGenCXXABI::emitReturnFromThunk(CIRGenFunction &cgf, RValue rv, + QualType resultType) { + assert(!cgf.hasAggregateEvaluationKind(resultType) && + "cannot handle aggregates"); + mlir::Location loc = cgf.getBuilder().getUnknownLoc(); + cgf.emitReturnOfRValue(loc, rv, resultType); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h index 9be9132f3532..161f16d388d1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenCXXABI.h +++ b/clang/lib/CIR/CodeGen/CIRGenCXXABI.h @@ -227,6 +227,36 @@ public: /// this emits virtual table tables. virtual void emitVirtualInheritanceTables(const CXXRecordDecl *rd) = 0; + /// Returns true if the thunk should be exported. + virtual bool exportThunk() = 0; + + /// Set the linkage and visibility of a thunk function. + virtual void setThunkLinkage(cir::FuncOp thunk, bool forVTable, GlobalDecl gd, + bool returnAdjustment) = 0; + + /// Perform adjustment on the 'this' pointer for a thunk. + /// Returns the adjusted 'this' pointer value. + virtual mlir::Value + performThisAdjustment(CIRGenFunction &cgf, Address thisAddr, + const CXXRecordDecl *unadjustedClass, + const ThunkInfo &ti) = 0; + + /// Perform adjustment on a return pointer for a thunk (covariant returns). + /// Returns the adjusted return pointer value. + virtual mlir::Value + performReturnAdjustment(CIRGenFunction &cgf, Address ret, + const CXXRecordDecl *unadjustedClass, + const ReturnAdjustment &ra) = 0; + + /// Adjust call arguments for a destructor thunk. + virtual void adjustCallArgsForDestructorThunk(CIRGenFunction &cgf, + GlobalDecl globalDecl, + CallArgList &callArgs) {} + + /// Emit a return from a thunk. + virtual void emitReturnFromThunk(CIRGenFunction &cgf, RValue rv, + QualType resultType); + /// Returns true if the given destructor type should be emitted as a linkonce /// delegating thunk, regardless of whether the dtor is defined in this TU or /// not. diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 8e956539da54..e10c6d44cd35 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -529,7 +529,7 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType, didCallStackSave = false; curCodeDecl = d; const auto *fd = dyn_cast_or_null(d); - curFuncDecl = d->getNonClosureContext(); + curFuncDecl = (d ? d->getNonClosureContext() : nullptr); prologueCleanupDepth = ehStack.stable_begin(); diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index ac09901f4a9f..2af3f35e355b 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -122,6 +122,7 @@ public: const clang::Decl *curFuncDecl = nullptr; /// This is the inner-most code context, which includes blocks. const clang::Decl *curCodeDecl = nullptr; + const CIRGenFunctionInfo *curFnInfo = nullptr; /// The current function or global initializer that is generated code for. /// This is usually a cir::FuncOp, but it can also be a cir::GlobalOp for @@ -186,6 +187,10 @@ public: /// this fuction. These can potentially set the return value. bool sawAsmBlock = false; + /// In C++, whether we are code generating a thunk. This controls whether we + /// should emit cleanups. + bool curFuncIsThunk = false; + mlir::Type convertTypeForMem(QualType t); mlir::Type convertType(clang::QualType t); @@ -1234,6 +1239,18 @@ public: Destroyer *getDestroyer(clang::QualType::DestructionKind kind); + /// Start generating a thunk function. + void startThunk(cir::FuncOp fn, GlobalDecl gd, + const CIRGenFunctionInfo &fnInfo, bool isUnprototyped); + + /// Finish generating a thunk function. + void finishThunk(); + + /// Generate code for a thunk function. + void generateThunk(cir::FuncOp fn, const CIRGenFunctionInfo &fnInfo, + GlobalDecl gd, const ThunkInfo &thunk, + bool isUnprototyped); + /// ---------------------- /// CIR emit functions /// ---------------------- @@ -1425,6 +1442,11 @@ public: RValue emitCall(clang::QualType calleeTy, const CIRGenCallee &callee, const clang::CallExpr *e, ReturnValueSlot returnValue); + + /// Emit the call and return for a thunk function. + void emitCallAndReturnForThunk(cir::FuncOp callee, const ThunkInfo *thunk, + bool isUnprototyped); + void emitCallArg(CallArgList &args, const clang::Expr *e, clang::QualType argType); void emitCallArgs( @@ -1770,6 +1792,10 @@ public: LValue emitMemberExpr(const MemberExpr *e); + /// Emit a musttail call for a thunk with a potentially different ABI. + void emitMustTailThunk(GlobalDecl gd, mlir::Value adjustedThisPtr, + cir::FuncOp callee); + /// Emit a call to an AMDGPU builtin function. std::optional emitAMDGPUBuiltinExpr(unsigned builtinID, const CallExpr *expr); diff --git a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp index a18e2b91b1dd..0e66587254bd 100644 --- a/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenItaniumCXXABI.cpp @@ -119,6 +119,24 @@ public: const CXXRecordDecl *rd) override; void emitVirtualInheritanceTables(const CXXRecordDecl *rd) override; + void setThunkLinkage(cir::FuncOp thunk, bool forVTable, GlobalDecl gd, + bool returnAdjustment) override { + if (forVTable && !thunk.hasLocalLinkage()) + thunk.setLinkage(cir::GlobalLinkageKind::AvailableExternallyLinkage); + const auto *nd = cast(gd.getDecl()); + cgm.setGVProperties(thunk, nd); + } + + bool exportThunk() override { return true; } + + mlir::Value performThisAdjustment(CIRGenFunction &cgf, Address thisAddr, + const CXXRecordDecl *unadjustedClass, + const ThunkInfo &ti) override; + + mlir::Value performReturnAdjustment(CIRGenFunction &cgf, Address ret, + const CXXRecordDecl *unadjustedClass, + const ReturnAdjustment &ra) override; + mlir::Attribute getAddrOfRTTIDescriptor(mlir::Location loc, QualType ty) override; CatchTypeInfo @@ -2487,3 +2505,61 @@ void CIRGenItaniumCXXABI::emitBeginCatch(CIRGenFunction &cgf, catchStmt->getBeginLoc()); cgf.emitAutoVarCleanups(var); } + +static mlir::Value performTypeAdjustment(CIRGenFunction &cgf, + Address initialPtr, + const CXXRecordDecl *unadjustedClass, + int64_t nonVirtualAdjustment, + int64_t virtualAdjustment, + bool isReturnAdjustment) { + if (!nonVirtualAdjustment && !virtualAdjustment) + return initialPtr.getPointer(); + + CIRGenBuilderTy &builder = cgf.getBuilder(); + mlir::Location loc = builder.getUnknownLoc(); + cir::PointerType i8PtrTy = builder.getUInt8PtrTy(); + mlir::Value v = builder.createBitcast(initialPtr.getPointer(), i8PtrTy); + + // In a base-to-derived cast, the non-virtual adjustment is applied first. + if (nonVirtualAdjustment && !isReturnAdjustment) { + cir::ConstantOp offsetConst = builder.getSInt64(nonVirtualAdjustment, loc); + v = cir::PtrStrideOp::create(builder, loc, i8PtrTy, v, offsetConst); + } + + // Perform the virtual adjustment if we have one. + mlir::Value resultPtr; + if (virtualAdjustment) { + cgf.cgm.errorNYI("virtual adjustment in thunk"); + resultPtr = v; + } else { + resultPtr = v; + } + + // In a derived-to-base conversion, the non-virtual adjustment is + // applied second. + if (nonVirtualAdjustment && isReturnAdjustment) { + cir::ConstantOp offsetConst = builder.getSInt64(nonVirtualAdjustment, loc); + resultPtr = + cir::PtrStrideOp::create(builder, loc, i8PtrTy, resultPtr, offsetConst); + } + + // Cast back to original pointer type. + return builder.createBitcast(resultPtr, initialPtr.getType()); +} + +mlir::Value CIRGenItaniumCXXABI::performThisAdjustment( + CIRGenFunction &cgf, Address thisAddr, const CXXRecordDecl *unadjustedClass, + const ThunkInfo &ti) { + return performTypeAdjustment(cgf, thisAddr, unadjustedClass, + ti.This.NonVirtual, + ti.This.Virtual.Itanium.VCallOffsetOffset, + /*isReturnAdjustment=*/false); +} + +mlir::Value CIRGenItaniumCXXABI::performReturnAdjustment( + CIRGenFunction &cgf, Address ret, const CXXRecordDecl *unadjustedClass, + const ReturnAdjustment &ra) { + return performTypeAdjustment(cgf, ret, unadjustedClass, ra.NonVirtual, + ra.Virtual.Itanium.VBaseOffsetOffset, + /*isReturnAdjustment=*/true); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 223b53731359..ecfdee5dce96 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2372,12 +2372,6 @@ cir::FuncOp CIRGenModule::getOrCreateCIRFunction( mlir::ArrayAttr extraAttrs) { const Decl *d = gd.getDecl(); - if (isThunk) - errorNYI(d->getSourceRange(), "getOrCreateCIRFunction: thunk"); - - // In what follows, we continue past 'errorNYI' as if nothing happened because - // the rest of the implementation is better than doing nothing. - if (const auto *fd = cast_or_null(d)) { // For the device mark the function as one that should be emitted. if (getLangOpts().OpenMPIsTargetDevice && fd->isDefined() && !dontDefer && diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 52464a8bc30c..7cac3cfe39b1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -273,6 +273,9 @@ public: getAddrOfGlobalVar(const VarDecl *d, mlir::Type ty = {}, ForDefinition_t isForDefinition = NotForDefinition); + /// Get or create a thunk function with the given name and type. + cir::FuncOp getAddrOfThunk(StringRef name, mlir::Type fnTy, GlobalDecl gd); + /// Return the mlir::GlobalViewAttr for the address of the given global. cir::GlobalViewAttr getAddrOfGlobalVarAttr(const VarDecl *d); diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp index 301954405027..d87f063ec810 100644 --- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp @@ -26,6 +26,31 @@ using namespace clang::CIRGen; CIRGenVTables::CIRGenVTables(CIRGenModule &cgm) : cgm(cgm), vtContext(cgm.getASTContext().getVTableContext()) {} +cir::FuncOp CIRGenModule::getAddrOfThunk(StringRef name, mlir::Type fnTy, + GlobalDecl gd) { + return getOrCreateCIRFunction(name, fnTy, gd, /*forVTable=*/true, + /*dontDefer=*/true, /*isThunk=*/true); +} + +static void setThunkProperties(CIRGenModule &cgm, const ThunkInfo &thunk, + cir::FuncOp thunkFn, bool forVTable, + GlobalDecl gd) { + cgm.setFunctionLinkage(gd, thunkFn); + cgm.getCXXABI().setThunkLinkage(thunkFn, forVTable, gd, + !thunk.Return.isEmpty()); + + // Set the right visibility. + cgm.setGVProperties(thunkFn, cast(gd.getDecl())); + + if (!cgm.getCXXABI().exportThunk()) { + assert(!cir::MissingFeatures::setDLLStorageClass()); + cgm.setDSOLocal(static_cast(thunkFn)); + } + + if (cgm.supportsCOMDAT() && thunkFn.isWeakForLinker()) + thunkFn.setComdat(true); +} + mlir::Type CIRGenModule::getVTableComponentType() { mlir::Type ptrTy = builder.getUInt8PtrTy(); assert(!cir::MissingFeatures::vtableRelativeLayout()); @@ -161,8 +186,11 @@ mlir::Attribute CIRGenVTables::getVTableComponent( } else if (nextVTableThunkIndex < layout.vtable_thunks().size() && layout.vtable_thunks()[nextVTableThunkIndex].first == componentIndex) { - cgm.errorNYI("getVTableComponent: CK_FunctionPointer: thunk"); - return mlir::Attribute(); + const ThunkInfo &thunkInfo = + layout.vtable_thunks()[nextVTableThunkIndex].second; + nextVTableThunkIndex++; + fnPtr = maybeEmitThunk(gd, thunkInfo, /*forVTable=*/true); + assert(!cir::MissingFeatures::pointerAuthentication()); } else { // Otherwise we can use the method definition directly. cir::FuncType fnTy = cgm.getTypes().getFunctionTypeForVTable(gd); @@ -510,6 +538,371 @@ uint64_t CIRGenVTables::getSecondaryVirtualPointerIndex(const CXXRecordDecl *rd, return it->second; } +static RValue performReturnAdjustment(CIRGenFunction &cgf, QualType resultType, + RValue rv, const ThunkInfo &thunk) { + // Emit the return adjustment. + bool nullCheckValue = !resultType->isReferenceType(); + + mlir::Value returnValue = rv.getValue(); + + if (nullCheckValue) + cgf.cgm.errorNYI( + "return adjustment with null check for non-reference types"); + + const CXXRecordDecl *classDecl = + resultType->getPointeeType()->getAsCXXRecordDecl(); + CharUnits classAlign = cgf.cgm.getClassPointerAlignment(classDecl); + mlir::Type pointeeType = cgf.convertTypeForMem(resultType->getPointeeType()); + returnValue = cgf.cgm.getCXXABI().performReturnAdjustment( + cgf, Address(returnValue, pointeeType, classAlign), classDecl, + thunk.Return); + + if (nullCheckValue) + cgf.cgm.errorNYI( + "return adjustment with null check for non-reference types"); + + return RValue::get(returnValue); +} + +void CIRGenFunction::startThunk(cir::FuncOp fn, GlobalDecl gd, + const CIRGenFunctionInfo &fnInfo, + bool isUnprototyped) { + assert(!curGD.getDecl() && "curGD was already set!"); + curGD = gd; + curFuncIsThunk = true; + + // Build FunctionArgs. + const CXXMethodDecl *md = cast(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()); + + // Start defining the function. + cir::FuncType funcType = cgm.getTypes().getFunctionType(fnInfo); + startFunction(GlobalDecl(), resultType, fn, funcType, functionArgs, + md->getLocation(), md->getLocation()); + // TODO(cir): Move this into startFunction. + curFnInfo = &fnInfo; + assert(!cir::MissingFeatures::generateDebugInfo()); + + // Since we didn't pass a GlobalDecl to startFunction, do this ourselves. + cgm.getCXXABI().emitInstanceFunctionProlog(md->getLocation(), *this); + cxxThisValue = cxxabiThisValue; + curCodeDecl = md; + curFuncDecl = md; +} + +void CIRGenFunction::finishThunk() { + // Clear these to restore the invariants expected by + // startFunction/finishFunction. + curCodeDecl = nullptr; + curFuncDecl = nullptr; + + finishFunction(SourceLocation()); +} + +void CIRGenFunction::emitCallAndReturnForThunk(cir::FuncOp callee, + const ThunkInfo *thunk, + bool isUnprototyped) { + assert(isa(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. + assert(!cir::MissingFeatures::opCallInAlloca()); + if ((curFnInfo && curFnInfo->isVariadic()) || isUnprototyped) { + // Error if return adjustment is needed (can't do with musttail). + if (thunk && !thunk->Return.isEmpty()) { + if (isUnprototyped) + cgm.errorUnsupported( + md, "return-adjusting thunk with incomplete parameter type"); + else if (curFnInfo && curFnInfo->isVariadic()) + llvm_unreachable("shouldn't try to emit musttail return-adjusting " + "thunks for variadic functions"); + else + cgm.errorUnsupported( + md, "non-trivial argument copy for return-adjusting thunk"); + } + emitMustTailThunk(curGD, adjustedThisPtr, callee); + return; + } + + // Build the call argument list. + CallArgList callArgs; + QualType thisType = md->getThisType(); + callArgs.add(RValue::get(adjustedThisPtr), thisType); + + if (isa(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 + const CIRGenFunctionInfo &callFnInfo = cgm.getTypes().arrangeCXXMethodCall( + callArgs, fpt, RequiredArgs::getFromProtoWithExtraSlots(fpt, 1), + prefixArgs); + assert(callFnInfo.argTypeSize() == curFnInfo->argTypeSize()); +#endif + + // Determine whether we have a return value slot to use. + QualType resultType = cgm.getCXXABI().hasThisReturn(curGD) ? thisType + : cgm.getCXXABI().hasMostDerivedReturn(curGD) + ? cgm.getASTContext().VoidPtrTy + : fpt->getReturnType(); + + ReturnValueSlot slot; + // This should also be tracking volatile, unused, and externally destructed. + assert(!cir::MissingFeatures::returnValueSlotFeatures()); + if (!resultType->isVoidType() && hasAggregateEvaluationKind(resultType)) + slot = ReturnValueSlot(returnValue); + + // Now emit our call. + CIRGenCallee cirCallee = CIRGenCallee::forDirect(callee, curGD); + mlir::Location loc = builder.getUnknownLoc(); + RValue rv = emitCall(*curFnInfo, cirCallee, slot, callArgs, + /*callOrTryCall=*/nullptr, loc); + + // Consider return adjustment if we have ThunkInfo. + if (thunk && !thunk->Return.isEmpty()) + rv = performReturnAdjustment(*this, resultType, rv, *thunk); + else + assert(!cir::MissingFeatures::opCallMustTail()); + + // Emit return. + if (!resultType->isVoidType() && slot.isNull()) + cgm.getCXXABI().emitReturnFromThunk(*this, rv, resultType); + + // Disable final ARC autorelease. + assert(!cir::MissingFeatures::objCLifetime()); + + finishThunk(); +} + +void CIRGenFunction::emitMustTailThunk(GlobalDecl gd, + mlir::Value adjustedThisPtr, + cir::FuncOp callee) { + assert(!cir::MissingFeatures::opCallMustTail()); + cgm.errorNYI("musttail thunk"); +} + +void CIRGenFunction::generateThunk(cir::FuncOp fn, + const CIRGenFunctionInfo &fnInfo, + GlobalDecl gd, const ThunkInfo &thunk, + bool isUnprototyped) { + // Create entry block and set up the builder's insertion point. + // This must be done before calling startThunk() which calls startFunction(). + assert(fn.isDeclaration() && "Function already has body?"); + mlir::Block *entryBb = fn.addEntryBlock(); + builder.setInsertionPointToStart(entryBb); + + // Create a scope in the symbol table to hold variable declarations. + // This is required before startFunction processes parameters, as it will + // insert them into the symbolTable (ScopedHashTable) which requires an + // active scope. + SymTableScopeTy varScope(symbolTable); + + // Create lexical scope - must stay alive for entire thunk generation. + // startFunction() requires currLexScope to be set. + mlir::Location unknownLoc = builder.getUnknownLoc(); + LexicalScope lexScope{*this, unknownLoc, entryBb}; + + startThunk(fn, gd, fnInfo, isUnprototyped); + assert(!cir::MissingFeatures::generateDebugInfo()); + + // Get our callee. Use a placeholder type if this method is unprototyped so + // that CIRGenModule doesn't try to set attributes. + mlir::Type ty; + if (isUnprototyped) + cgm.errorNYI("unprototyped thunk placeholder type"); + else + ty = cgm.getTypes().getFunctionType(fnInfo); + + cir::FuncOp calleeOp = cgm.getAddrOfFunction(gd, ty, /*forVTable=*/true); + + // Make the call and return the result. + emitCallAndReturnForThunk(calleeOp, &thunk, isUnprototyped); +} + +static bool shouldEmitVTableThunk(CIRGenModule &cgm, const CXXMethodDecl *md, + bool isUnprototyped, bool forVTable) { + // Always emit thunks in the MS C++ ABI. We cannot rely on other TUs to + // provide thunks for us. + if (cgm.getTarget().getCXXABI().isMicrosoft()) + return true; + + // In the Itanium C++ ABI, vtable thunks are provided by TUs that provide + // definitions of the main method. Therefore, emitting thunks with the vtable + // is purely an optimization. Emit the thunk if optimizations are enabled and + // all of the parameter types are complete. + if (forVTable) + return cgm.getCodeGenOpts().OptimizationLevel && !isUnprototyped; + + // Always emit thunks along with the method definition. + return true; +} + +cir::FuncOp CIRGenVTables::maybeEmitThunk(GlobalDecl gd, + const ThunkInfo &thunkAdjustments, + bool forVTable) { + const CXXMethodDecl *md = cast(gd.getDecl()); + SmallString<256> name; + MangleContext &mCtx = cgm.getCXXABI().getMangleContext(); + + llvm::raw_svector_ostream out(name); + if (const CXXDestructorDecl *dd = dyn_cast(md)) { + mCtx.mangleCXXDtorThunk(dd, gd.getDtorType(), thunkAdjustments, + /*elideOverrideInfo=*/false, out); + } else { + mCtx.mangleThunk(md, thunkAdjustments, /*elideOverrideInfo=*/false, out); + } + + if (cgm.getASTContext().useAbbreviatedThunkName(gd, name.str())) { + name = ""; + if (const CXXDestructorDecl *dd = dyn_cast(md)) + mCtx.mangleCXXDtorThunk(dd, gd.getDtorType(), thunkAdjustments, + /*elideOverrideInfo=*/true, out); + else + mCtx.mangleThunk(md, thunkAdjustments, /*elideOverrideInfo=*/true, out); + } + + cir::FuncType thunkVTableTy = cgm.getTypes().getFunctionTypeForVTable(gd); + cir::FuncOp thunk = cgm.getAddrOfThunk(name, thunkVTableTy, gd); + + // If we don't need to emit a definition, return this declaration as is. + bool isUnprototyped = !cgm.getTypes().isFuncTypeConvertible( + md->getType()->castAs()); + if (!shouldEmitVTableThunk(cgm, md, isUnprototyped, forVTable)) + return thunk; + + // Arrange a function prototype appropriate for a function definition. In some + // cases in the MS ABI, we may need to build an unprototyped musttail thunk. + const CIRGenFunctionInfo &fnInfo = + isUnprototyped ? (cgm.errorNYI("unprototyped must-tail thunk"), + cgm.getTypes().arrangeGlobalDeclaration(gd)) + : cgm.getTypes().arrangeGlobalDeclaration(gd); + cir::FuncType thunkFnTy = cgm.getTypes().getFunctionType(fnInfo); + + // This is to replace OG's casting to a function, keeping it here to + // streamline the 1-to-1 mapping from OG starting below. + cir::FuncOp thunkFn = thunk; + if (thunk.getFunctionType() != thunkFnTy) { + cir::FuncOp oldThunkFn = thunkFn; + + assert(oldThunkFn.isDeclaration() && "Shouldn't replace non-declaration"); + + // Remove the name from the old thunk function and get a new thunk. + oldThunkFn.setName(StringRef()); + thunkFn = + cir::FuncOp::create(cgm.getBuilder(), thunk->getLoc(), name.str(), + thunkFnTy, cir::GlobalLinkageKind::ExternalLinkage); + cgm.setCIRFunctionAttributes(md, fnInfo, thunkFn, /*isThunk=*/false); + + if (!oldThunkFn->use_empty()) + oldThunkFn->replaceAllUsesWith(thunkFn); + + // Remove the old thunk. + oldThunkFn->erase(); + } + + bool abiHasKeyFunctions = cgm.getTarget().getCXXABI().hasKeyFunctions(); + bool useAvailableExternallyLinkage = forVTable && abiHasKeyFunctions; + + // If the type of the underlying GlobalValue is wrong, we'll have to replace + // it. It should be a declaration. + if (!thunkFn.isDeclaration()) { + if (!abiHasKeyFunctions || useAvailableExternallyLinkage) { + // There is already a thunk emitted for this function, do nothing. + return thunkFn; + } + + setThunkProperties(cgm, thunkAdjustments, thunkFn, forVTable, gd); + return thunkFn; + } + + // TODO(cir): Add "thunk" attribute if unprototyped. + + cgm.setCIRFunctionAttributesForDefinition(cast(gd.getDecl()), + thunkFn); + + // Thunks for variadic methods are special because in general variadic + // arguments cannot be perfectly forwarded. In the general case, clang + // implements such thunks by cloning the original function body. However, for + // thunks with no return adjustment on targets that support musttail, we can + // use musttail to perfectly forward the variadic arguments. + bool shouldCloneVarArgs = false; + if (!isUnprototyped && thunkFn.getFunctionType().isVarArg()) { + shouldCloneVarArgs = true; + if (thunkAdjustments.Return.isEmpty()) { + switch (cgm.getTriple().getArch()) { + case llvm::Triple::x86_64: + case llvm::Triple::x86: + case llvm::Triple::aarch64: + shouldCloneVarArgs = false; + break; + default: + break; + } + } + } + + if (shouldCloneVarArgs) { + if (useAvailableExternallyLinkage) + return thunkFn; + cgm.errorNYI("varargs thunk cloning"); + } else { + // Normal thunk body generation. + mlir::OpBuilder::InsertionGuard guard(cgm.getBuilder()); + CIRGenFunction cgf(cgm, cgm.getBuilder()); + cgf.generateThunk(thunkFn, fnInfo, gd, thunkAdjustments, isUnprototyped); + } + + setThunkProperties(cgm, thunkAdjustments, thunkFn, forVTable, gd); + return thunkFn; +} + void CIRGenVTables::emitThunks(GlobalDecl gd) { const CXXMethodDecl *md = cast(gd.getDecl())->getCanonicalDecl(); @@ -524,5 +917,6 @@ void CIRGenVTables::emitThunks(GlobalDecl gd) { if (!thunkInfoVector) return; - cgm.errorNYI(md->getSourceRange(), "emitThunks"); + for (const ThunkInfo &thunk : *thunkInfoVector) + maybeEmitThunk(gd, thunk, /*forVTable=*/false); } diff --git a/clang/lib/CIR/CodeGen/CIRGenVTables.h b/clang/lib/CIR/CodeGen/CIRGenVTables.h index 9c425ab43b3d..1afa74358623 100644 --- a/clang/lib/CIR/CodeGen/CIRGenVTables.h +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.h @@ -95,6 +95,11 @@ public: /// Emit the associated thunks for the given global decl. void emitThunks(GlobalDecl gd); + /// Emit a thunk for the given global decl if needed, or return an existing + /// thunk. + cir::FuncOp maybeEmitThunk(GlobalDecl gd, const ThunkInfo &thunkAdjustments, + bool forVTable); + /// Generate all the class data required to be generated upon definition of a /// KeyFunction. This includes the vtable, the RTTI data structure (if RTTI /// is enabled) and the VTT (if the class has virtual bases). diff --git a/clang/test/CIR/CodeGen/thunks.cpp b/clang/test/CIR/CodeGen/thunks.cpp new file mode 100644 index 000000000000..5a27eb373746 --- /dev/null +++ b/clang/test/CIR/CodeGen/thunks.cpp @@ -0,0 +1,216 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -fclangir -emit-cir %s -o %t.cir +// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -fclangir -emit-llvm %s -o %t-cir.ll +// RUN: FileCheck --check-prefix=LLVM --input-file=%t-cir.ll %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fno-rtti -emit-llvm %s -o %t.ll +// RUN: FileCheck --check-prefix=OGCG --input-file=%t.ll %s + +// Note: -fno-rtti is used to simplify vtable output (no typeinfo references). + +namespace Test1 { +// Non-virtual this-adjusting thunk for a void method. +// When C overrides f() from both A and B, the vtable for B-in-C needs a thunk +// that adjusts 'this' from B* to C* before calling C::f(). + +struct A { + virtual void f(); +}; + +struct B { + virtual void f(); +}; + +struct C : A, B { + void f() override; +}; + +void C::f() {} + +} // namespace Test1 + +namespace Test2 { +// Non-virtual this-adjusting thunk for a method with a non-void return type. +// The thunk should forward the return value from the adjusted call. + +struct A { + virtual int g(); +}; + +struct B { + virtual int g(); +}; + +struct C : A, B { + int g() override; +}; + +int C::g() { return 42; } + +} // namespace Test2 + +namespace Test3 { +// Non-virtual this-adjusting thunks for destructors. +// When D inherits from both E and F, the complete and deleting destructors for +// F-in-D need thunks that adjust 'this'. + +struct E { + virtual ~E(); +}; + +struct F { + virtual ~F(); +}; + +struct D : E, F { + ~D() override; +}; + +D::~D() {} + +} // namespace Test3 + +// In CIR, all globals are emitted before functions. + +// Test1 vtable: C's vtable references the thunk for B's entry. +// CIR-DAG: cir.global "private" external @_ZTVN5Test11CE = #cir.vtable<{ +// CIR-DAG: #cir.global_view<@_ZN5Test11C1fEv> : !cir.ptr +// CIR-DAG: #cir.global_view<@_ZThn8_N5Test11C1fEv> : !cir.ptr + +// Test2 vtable: C's vtable references the thunk for B's entry. +// CIR-DAG: cir.global "private" external @_ZTVN5Test21CE = #cir.vtable<{ +// CIR-DAG: #cir.global_view<@_ZN5Test21C1gEv> : !cir.ptr +// CIR-DAG: #cir.global_view<@_ZThn8_N5Test21C1gEv> : !cir.ptr + +// Test3 vtable: D's vtable references D1, D0, and their thunks. +// CIR-DAG: cir.global "private" external @_ZTVN5Test31DE = #cir.vtable<{ +// CIR-DAG: #cir.global_view<@_ZN5Test31DD1Ev> : !cir.ptr +// CIR-DAG: #cir.global_view<@_ZN5Test31DD0Ev> : !cir.ptr +// CIR-DAG: #cir.global_view<@_ZThn8_N5Test31DD1Ev> : !cir.ptr +// CIR-DAG: #cir.global_view<@_ZThn8_N5Test31DD0Ev> : !cir.ptr + +// --- Test1: void method thunk --- + +// CIR: cir.func {{.*}} @_ZN5Test11C1fEv + +// The thunk adjusts 'this' by -8 bytes and calls C::f(). +// CIR: cir.func {{.*}} @_ZThn8_N5Test11C1fEv(%arg0: !cir.ptr< +// CIR: %[[T1_THIS_ADDR:.*]] = cir.alloca {{.*}} ["this", init] +// CIR: cir.store %arg0, %[[T1_THIS_ADDR]] +// CIR: %[[T1_THIS:.*]] = cir.load %[[T1_THIS_ADDR]] +// CIR: %[[T1_CAST:.*]] = cir.cast bitcast %[[T1_THIS]] : !cir.ptr<{{.*}}> -> !cir.ptr +// CIR: %[[T1_OFFSET:.*]] = cir.const #cir.int<-8> : !s64i +// CIR: %[[T1_ADJUSTED:.*]] = cir.ptr_stride %[[T1_CAST]], %[[T1_OFFSET]] : (!cir.ptr, !s64i) -> !cir.ptr +// CIR: %[[T1_RESULT:.*]] = cir.cast bitcast %[[T1_ADJUSTED]] : !cir.ptr -> !cir.ptr< +// CIR: cir.call @_ZN5Test11C1fEv(%[[T1_RESULT]]) +// CIR: cir.return + +// --- Test2: non-void return type thunk --- + +// CIR: cir.func {{.*}} @_ZN5Test21C1gEv + +// CIR: cir.func {{.*}} @_ZThn8_N5Test21C1gEv(%arg0: !cir.ptr< +// CIR: %[[T2_THIS:.*]] = cir.load +// CIR: %[[T2_CAST:.*]] = cir.cast bitcast %[[T2_THIS]] : !cir.ptr<{{.*}}> -> !cir.ptr +// CIR: %[[T2_OFFSET:.*]] = cir.const #cir.int<-8> : !s64i +// CIR: %[[T2_ADJUSTED:.*]] = cir.ptr_stride %[[T2_CAST]], %[[T2_OFFSET]] : (!cir.ptr, !s64i) -> !cir.ptr +// CIR: %[[T2_RESULT:.*]] = cir.cast bitcast %[[T2_ADJUSTED]] : !cir.ptr -> !cir.ptr< +// CIR: %[[T2_RET:.*]] = cir.call @_ZN5Test21C1gEv(%[[T2_RESULT]]) +// CIR: cir.store {{.*}} %[[T2_RET]] +// CIR: %[[T2_RET_VAL:.*]] = cir.load +// CIR: cir.return %[[T2_RET_VAL]] + +// --- Test3: destructor thunks --- + +// Complete destructor thunk: adjusts 'this' and calls D1. +// CIR: cir.func {{.*}} @_ZThn8_N5Test31DD1Ev(%arg0: !cir.ptr< +// CIR: %[[T3A_THIS:.*]] = cir.load +// CIR: %[[T3A_CAST:.*]] = cir.cast bitcast %[[T3A_THIS]] : !cir.ptr<{{.*}}> -> !cir.ptr +// CIR: %[[T3A_OFFSET:.*]] = cir.const #cir.int<-8> : !s64i +// CIR: %[[T3A_ADJUSTED:.*]] = cir.ptr_stride %[[T3A_CAST]], %[[T3A_OFFSET]] : (!cir.ptr, !s64i) -> !cir.ptr +// CIR: %[[T3A_RESULT:.*]] = cir.cast bitcast %[[T3A_ADJUSTED]] : !cir.ptr -> !cir.ptr< +// CIR: cir.call @_ZN5Test31DD1Ev(%[[T3A_RESULT]]) +// CIR: cir.return + +// Deleting destructor thunk: adjusts 'this' and calls D0. +// CIR: cir.func {{.*}} @_ZThn8_N5Test31DD0Ev(%arg0: !cir.ptr< +// CIR: %[[T3B_THIS:.*]] = cir.load +// CIR: %[[T3B_CAST:.*]] = cir.cast bitcast %[[T3B_THIS]] : !cir.ptr<{{.*}}> -> !cir.ptr +// CIR: %[[T3B_OFFSET:.*]] = cir.const #cir.int<-8> : !s64i +// CIR: %[[T3B_ADJUSTED:.*]] = cir.ptr_stride %[[T3B_CAST]], %[[T3B_OFFSET]] : (!cir.ptr, !s64i) -> !cir.ptr +// CIR: %[[T3B_RESULT:.*]] = cir.cast bitcast %[[T3B_ADJUSTED]] : !cir.ptr -> !cir.ptr< +// CIR: cir.call @_ZN5Test31DD0Ev(%[[T3B_RESULT]]) +// CIR: cir.return + +// --- LLVM checks --- + +// LLVM: @_ZTVN5Test11CE = global { [3 x ptr], [3 x ptr] } { +// LLVM-SAME: [3 x ptr] [ptr null, ptr null, ptr @_ZN5Test11C1fEv], +// LLVM-SAME: [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test11C1fEv] +// LLVM-SAME: } + +// LLVM: @_ZTVN5Test21CE = global { [3 x ptr], [3 x ptr] } { +// LLVM-SAME: [3 x ptr] [ptr null, ptr null, ptr @_ZN5Test21C1gEv], +// LLVM-SAME: [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test21C1gEv] +// LLVM-SAME: } + +// LLVM: @_ZTVN5Test31DE = global { [4 x ptr], [4 x ptr] } { +// LLVM-SAME: [4 x ptr] [ptr null, ptr null, ptr @_ZN5Test31DD1Ev, ptr @_ZN5Test31DD0Ev], +// LLVM-SAME: [4 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test31DD1Ev, ptr @_ZThn8_N5Test31DD0Ev] +// LLVM-SAME: } + +// LLVM: define {{.*}} void @_ZThn8_N5Test11C1fEv(ptr{{.*}}) +// LLVM: %[[L1_THIS:.*]] = load ptr, ptr +// LLVM: %[[L1_ADJ:.*]] = getelementptr i8, ptr %[[L1_THIS]], i64 -8 +// LLVM: call void @_ZN5Test11C1fEv(ptr{{.*}} %[[L1_ADJ]]) + +// LLVM: define {{.*}} i32 @_ZThn8_N5Test21C1gEv(ptr{{.*}}) +// LLVM: %[[L2_THIS:.*]] = load ptr, ptr +// LLVM: %[[L2_ADJ:.*]] = getelementptr i8, ptr %[[L2_THIS]], i64 -8 +// LLVM: %[[L2_RET:.*]] = call {{.*}} i32 @_ZN5Test21C1gEv(ptr{{.*}} %[[L2_ADJ]]) + +// LLVM: define {{.*}} void @_ZThn8_N5Test31DD1Ev(ptr{{.*}}) +// LLVM: %[[L3A_THIS:.*]] = load ptr, ptr +// LLVM: %[[L3A_ADJ:.*]] = getelementptr i8, ptr %[[L3A_THIS]], i64 -8 +// LLVM: call void @_ZN5Test31DD1Ev(ptr{{.*}} %[[L3A_ADJ]]) + +// LLVM: define {{.*}} void @_ZThn8_N5Test31DD0Ev(ptr{{.*}}) +// LLVM: %[[L3B_THIS:.*]] = load ptr, ptr +// LLVM: %[[L3B_ADJ:.*]] = getelementptr i8, ptr %[[L3B_THIS]], i64 -8 +// LLVM: call void @_ZN5Test31DD0Ev(ptr{{.*}} %[[L3B_ADJ]]) + +// --- OGCG checks --- + +// OGCG: @_ZTVN5Test11CE = unnamed_addr constant { [3 x ptr], [3 x ptr] } { +// OGCG-SAME: [3 x ptr] [ptr null, ptr null, ptr @_ZN5Test11C1fEv], +// OGCG-SAME: [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test11C1fEv] +// OGCG-SAME: } + +// OGCG: @_ZTVN5Test21CE = unnamed_addr constant { [3 x ptr], [3 x ptr] } { +// OGCG-SAME: [3 x ptr] [ptr null, ptr null, ptr @_ZN5Test21C1gEv], +// OGCG-SAME: [3 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test21C1gEv] +// OGCG-SAME: } + +// OGCG: @_ZTVN5Test31DE = unnamed_addr constant { [4 x ptr], [4 x ptr] } { +// OGCG-SAME: [4 x ptr] [ptr null, ptr null, ptr @_ZN5Test31DD1Ev, ptr @_ZN5Test31DD0Ev], +// OGCG-SAME: [4 x ptr] [ptr inttoptr (i64 -8 to ptr), ptr null, ptr @_ZThn8_N5Test31DD1Ev, ptr @_ZThn8_N5Test31DD0Ev] +// OGCG-SAME: } + +// OGCG: define {{.*}} void @_ZThn8_N5Test11C1fEv(ptr{{.*}}) +// OGCG: %[[O1_THIS:.*]] = load ptr, ptr +// OGCG: %[[O1_ADJ:.*]] = getelementptr inbounds i8, ptr %[[O1_THIS]], i64 -8 +// OGCG: call void @_ZN5Test11C1fEv(ptr{{.*}} %[[O1_ADJ]]) + +// OGCG: define {{.*}} i32 @_ZThn8_N5Test21C1gEv(ptr{{.*}}) +// OGCG: %[[O2_THIS:.*]] = load ptr, ptr +// OGCG: %[[O2_ADJ:.*]] = getelementptr inbounds i8, ptr %[[O2_THIS]], i64 -8 +// OGCG: {{.*}}call {{.*}} i32 @_ZN5Test21C1gEv(ptr{{.*}} %[[O2_ADJ]]) + +// OGCG: define {{.*}} void @_ZThn8_N5Test31DD1Ev(ptr{{.*}}) +// OGCG: %[[O3A_THIS:.*]] = load ptr, ptr +// OGCG: %[[O3A_ADJ:.*]] = getelementptr inbounds i8, ptr %[[O3A_THIS]], i64 -8 +// OGCG: call void @_ZN5Test31DD1Ev(ptr{{.*}} %[[O3A_ADJ]]) + +// OGCG: define {{.*}} void @_ZThn8_N5Test31DD0Ev(ptr{{.*}}) +// OGCG: %[[O3B_THIS:.*]] = load ptr, ptr +// OGCG: %[[O3B_ADJ:.*]] = getelementptr inbounds i8, ptr %[[O3B_THIS]], i64 -8 +// OGCG: call void @_ZN5Test31DD0Ev(ptr{{.*}} %[[O3B_ADJ]])