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/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..7d937b2b67b1 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -2130,6 +2130,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(); + + 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/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..aa932d041fa8 100644 --- a/clang/lib/CIR/CodeGen/CIRGenVTables.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenVTables.cpp @@ -47,6 +47,87 @@ static void setThunkProperties(CIRGenModule &cgm, const ThunkInfo &thunk, llvm_unreachable("NYI"); } +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"); + llvm_unreachable("NYI: emitCallAndReturnForThunk"); +} + +void CIRGenFunction::emitMustTailThunk(GlobalDecl GD, + mlir::Value AdjustedThisPtr, + cir::FuncOp Callee) { + llvm_unreachable("NYI"); +} + +void CIRGenFunction::generateThunk(cir::FuncOp Fn, + const CIRGenFunctionInfo &FnInfo, + GlobalDecl GD, const ThunkInfo &Thunk, + bool IsUnprototyped) { + 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) { return CGM.getTarget().getCXXABI().isItaniumFamily() && CGM.getItaniumVTableContext().isRelativeLayout(); @@ -280,11 +361,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 +856,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..ad5312549679 --- /dev/null +++ b/clang/test/CIR/CodeGen/vtable-thunk.cpp @@ -0,0 +1,43 @@ +// 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 that thunk function is generated with correct mangling +// CIR: cir.func linkonce_odr @_ZThn8_N7Derived3barEv +// CIR-SAME: (!cir.ptr) + +// Check thunk is in vtable +// CIR: cir.global linkonce_odr @_ZTV7Derived = #cir.vtable +// CIR-SAME: #cir.global_view<@_ZThn8_N7Derived3barEv> + +// LLVM: define linkonce_odr void @_ZThn8_N7Derived3barEv +// LLVM-SAME: ptr + +// LLVM: @_ZTV7Derived = linkonce_odr global +// LLVM-SAME: @_ZThn8_N7Derived3barEv diff --git a/clang/test/CIR/Lowering/vtable-thunk.cpp b/clang/test/CIR/Lowering/vtable-thunk.cpp new file mode 100644 index 000000000000..743395011d91 --- /dev/null +++ b/clang/test/CIR/Lowering/vtable-thunk.cpp @@ -0,0 +1,34 @@ +// 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 thunk function is defined with correct linkage +// LLVM: define linkonce_odr void @_ZThn8_N7Derived3barEv(ptr{{.*}} %0) + +// Check vtable contains thunk with correct offset (8 bytes on x86_64) +// LLVM: @_ZTV7Derived = linkonce_odr global +// LLVM-SAME: @_ZThn8_N7Derived3barEv