Skip to content

Commit c385cf7

Browse files
committed
[CIR] Add basic thunk generation infrastructure
This commit adds the foundational support for generating C++ thunks needed for multiple inheritance, particularly for virtual function calls through base class pointers. The implementation closely follows CodeGen. Key changes: 1. Fix VTable double-insertion bug in createGlobalOp/createCIRFunction: - Clear insertion point before calling create() to prevent auto-insertion - Manually insert operations at correct location - This was a pre-existing bug blocking VTable generation during thunk emission 2. Implement thunk generation infrastructure matching CodeGen: - Add startThunk() matching CodeGen's StartThunk() - Add generateThunk() matching CodeGen's generateThunk() - Add emitCallAndReturnForThunk() stub matching CodeGen's EmitCallAndReturnForThunk() - Add emitMustTailThunk() stub matching CodeGen's EmitMustTailThunk() - Implement finishThunk() matching CodeGen's FinishThunk() for cleanup - Enable thunk handling in addVTableComponent() for VTable emission - Call generateThunk() from maybeEmitThunk() 3. Make StartFunction null-safe for thunks: - Add null checks for GlobalDecl in XRay attributes - Guard function body access for thunks (which have no AST body) 4. Handle thunk attributes in setFunctionAttributes: - Remove assertion blocking thunks - Allow thunks to proceed through attribute setting 5. Add comprehensive test coverage: - clang/test/CIR/CodeGen/vtable-thunk.cpp - CIR output test with specific offset verification - clang/test/CIR/Lowering/vtable-thunk.cpp - LLVM lowering test with correct offset - clang/test/CIR/CodeGen/vtable-thunk-compare-codegen.cpp - CodeGen comparison test (XFAIL until thunk bodies implemented) Note: Thunk bodies are marked with llvm_unreachable("NYI") placeholders. Full thunk body emission (this adjustment, call forwarding, return adjustment) will be implemented in follow-up commits. ghstack-source-id: 933ad05 Pull-Request: #2003
1 parent b6283be commit c385cf7

File tree

8 files changed

+259
-20
lines changed

8 files changed

+259
-20
lines changed

clang/lib/CIR/CodeGen/CIRGenCXX.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,12 @@ void CIRGenModule::emitCXXGlobalVarDeclInit(const VarDecl *varDecl,
429429
builder.setInsertionPointToEnd(block);
430430
cir::YieldOp::create(builder, addr->getLoc());
431431
}
432+
433+
void CIRGenFunction::finishThunk() {
434+
// Clear these to restore the invariants expected by
435+
// StartFunction/FinishFunction.
436+
CurCodeDecl = nullptr;
437+
CurFuncDecl = nullptr;
438+
439+
finishFunction(SourceLocation());
440+
}

clang/lib/CIR/CodeGen/CIRGenFunction.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -765,8 +765,8 @@ cir::FuncOp CIRGenFunction::generateCode(clang::GlobalDecl gd, cir::FuncOp fn,
765765
// Create a scope in the symbol table to hold variable declarations.
766766
SymTableScopeTy varScope(symbolTable);
767767
// Compiler synthetized functions might have invalid slocs...
768-
auto bSrcLoc = fd->getBody()->getBeginLoc();
769-
auto eSrcLoc = fd->getBody()->getEndLoc();
768+
auto bSrcLoc = (fd && fd->getBody()) ? fd->getBody()->getBeginLoc() : SourceLocation();
769+
auto eSrcLoc = (fd && fd->getBody()) ? fd->getBody()->getEndLoc() : SourceLocation();
770770
auto unknownLoc = builder.getUnknownLoc();
771771

772772
auto fnBeginLoc = bSrcLoc.isValid() ? getLoc(bSrcLoc) : unknownLoc;
@@ -1158,11 +1158,11 @@ void CIRGenFunction::StartFunction(GlobalDecl gd, QualType retTy,
11581158
llvm_unreachable("NYI");
11591159

11601160
// Apply xray attributes to the function (as a string, for now)
1161-
if (d->getAttr<XRayInstrumentAttr>()) {
1161+
if (d && d->getAttr<XRayInstrumentAttr>()) {
11621162
assert(!cir::MissingFeatures::xray());
11631163
}
11641164

1165-
if (ShouldXRayInstrumentFunction()) {
1165+
if (d && ShouldXRayInstrumentFunction()) {
11661166
assert(!cir::MissingFeatures::xray());
11671167
}
11681168

@@ -1365,12 +1365,13 @@ void CIRGenFunction::StartFunction(GlobalDecl gd, QualType retTy,
13651365

13661366
// Location of the store to the param storage tracked as beginning of
13671367
// the function body.
1368-
auto fnBodyBegin = getLoc(fd->getBody()->getBeginLoc());
1368+
auto fnBodyBegin =
1369+
(fd && fd->getBody()) ? getLoc(fd->getBody()->getBeginLoc()) : getLoc(Loc);
13691370
builder.CIRBaseBuilderTy::createStore(fnBodyBegin, paramVal, addr);
13701371
}
13711372
assert(builder.getInsertionBlock() && "Should be valid");
13721373

1373-
auto fnEndLoc = getLoc(fd->getBody()->getEndLoc());
1374+
auto fnEndLoc = (fd && fd->getBody()) ? getLoc(fd->getBody()->getEndLoc()) : getLoc(Loc);
13741375

13751376
// When the current function is not void, create an address to store the
13761377
// result value.

clang/lib/CIR/CodeGen/CIRGenFunction.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2130,6 +2130,24 @@ class CIRGenFunction : public CIRGenTypeCache {
21302130

21312131
void emitDestructorBody(FunctionArgList &Args);
21322132

2133+
/// Generate a thunk for the given method.
2134+
void generateThunk(cir::FuncOp Fn, const CIRGenFunctionInfo &FnInfo,
2135+
GlobalDecl GD, const ThunkInfo &Thunk,
2136+
bool IsUnprototyped);
2137+
2138+
void startThunk(cir::FuncOp Fn, GlobalDecl GD,
2139+
const CIRGenFunctionInfo &FnInfo, bool IsUnprototyped);
2140+
2141+
void emitCallAndReturnForThunk(cir::FuncOp Callee,
2142+
const ThunkInfo *Thunk,
2143+
bool IsUnprototyped);
2144+
2145+
/// Finish thunk generation.
2146+
void finishThunk();
2147+
2148+
void emitMustTailThunk(GlobalDecl GD, mlir::Value AdjustedThisPtr,
2149+
cir::FuncOp Callee);
2150+
21332151
mlir::LogicalResult emitDoStmt(const clang::DoStmt &S);
21342152

21352153
mlir::Value emitDynamicCast(Address ThisAddr, const CXXDynamicCastExpr *DCE);

clang/lib/CIR/CodeGen/CIRGenModule.cpp

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -826,14 +826,22 @@ cir::GlobalOp CIRGenModule::createGlobalOp(CIRGenModule &cgm,
826826
// Some global emissions are triggered while emitting a function, e.g.
827827
// void s() { const char *s = "yolo"; ... }
828828
//
829-
// Be sure to insert global before the current function
829+
// Save the current function context for later insertion logic
830830
auto *curCGF = cgm.getCurrCIRGenFun();
831-
if (curCGF)
832-
builder.setInsertionPoint(curCGF->CurFn);
831+
832+
// Clear insertion point to prevent auto-insertion by create()
833+
// We'll manually insert at the correct location below
834+
builder.clearInsertionPoint();
833835

834836
g = cir::GlobalOp::create(builder, loc, name, t, isConstant, linkage,
835837
addrSpace);
836-
if (!curCGF) {
838+
839+
// Manually insert at the correct location
840+
if (curCGF) {
841+
// Insert before the current function being generated
842+
cgm.getModule().insert(mlir::Block::iterator(curCGF->CurFn), g);
843+
} else {
844+
// Insert at specified point or at end of module
837845
if (insertPoint)
838846
cgm.getModule().insert(insertPoint, g);
839847
else
@@ -2710,10 +2718,12 @@ cir::FuncOp CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name,
27102718
// Some global emissions are triggered while emitting a function, e.g.
27112719
// void s() { x.method() }
27122720
//
2713-
// Be sure to insert a new function before a current one.
2721+
// Save the current function context for later insertion logic
27142722
auto *curCGF = getCurrCIRGenFun();
2715-
if (curCGF)
2716-
builder.setInsertionPoint(curCGF->CurFn);
2723+
2724+
// Clear insertion point to prevent auto-insertion by create()
2725+
// We'll manually insert at the correct location below
2726+
builder.clearInsertionPoint();
27172727

27182728
f = cir::FuncOp::create(builder, loc, name, ty);
27192729

@@ -2739,8 +2749,14 @@ cir::FuncOp CIRGenModule::createCIRFunction(mlir::Location loc, StringRef name,
27392749
// Set the special member attribute for this function, if applicable.
27402750
setCXXSpecialMemberAttr(f, fd);
27412751

2742-
if (!curCGF)
2752+
// Manually insert at the correct location
2753+
if (curCGF) {
2754+
// Insert before the current function being generated
2755+
theModule.insert(mlir::Block::iterator(curCGF->CurFn), f);
2756+
} else {
2757+
// Insert at end of module
27432758
theModule.push_back(f);
2759+
}
27442760
}
27452761
return f;
27462762
}
@@ -3058,7 +3074,6 @@ void CIRGenModule::setFunctionAttributes(GlobalDecl globalDecl,
30583074
// NOTE(cir): Original CodeGen checks if this is an intrinsic. In CIR we
30593075
// represent them in dedicated ops. The correct attributes are ensured during
30603076
// translation to LLVM. Thus, we don't need to check for them here.
3061-
assert(!isThunk && "isThunk NYI");
30623077

30633078
if (!isIncompleteFunction) {
30643079
setCIRFunctionAttributes(globalDecl,

clang/lib/CIR/CodeGen/CIRGenVTables.cpp

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,87 @@ static void setThunkProperties(CIRGenModule &cgm, const ThunkInfo &thunk,
4747
llvm_unreachable("NYI");
4848
}
4949

50+
void CIRGenFunction::startThunk(cir::FuncOp Fn, GlobalDecl GD,
51+
const CIRGenFunctionInfo &FnInfo,
52+
bool IsUnprototyped) {
53+
assert(!CurGD.getDecl() && "CurGD was already set!");
54+
CurGD = GD;
55+
CurFuncIsThunk = true;
56+
57+
// Build FunctionArgs.
58+
const CXXMethodDecl *MD = cast<CXXMethodDecl>(GD.getDecl());
59+
QualType ThisType = MD->getThisType();
60+
QualType ResultType;
61+
if (IsUnprototyped)
62+
ResultType = CGM.getASTContext().VoidTy;
63+
else if (CGM.getCXXABI().HasThisReturn(GD))
64+
ResultType = ThisType;
65+
else if (CGM.getCXXABI().hasMostDerivedReturn(GD))
66+
ResultType = CGM.getASTContext().VoidPtrTy;
67+
else
68+
ResultType = MD->getType()->castAs<FunctionProtoType>()->getReturnType();
69+
FunctionArgList FunctionArgs;
70+
71+
// Create the implicit 'this' parameter declaration.
72+
CGM.getCXXABI().buildThisParam(*this, FunctionArgs);
73+
74+
// Add the rest of the parameters, if we have a prototype to work with.
75+
if (!IsUnprototyped) {
76+
FunctionArgs.append(MD->param_begin(), MD->param_end());
77+
78+
if (isa<CXXDestructorDecl>(MD))
79+
CGM.getCXXABI().addImplicitStructorParams(*this, ResultType,
80+
FunctionArgs);
81+
}
82+
83+
// Start defining the function.
84+
// NOTE(cir): No ApplyDebugLocation in CIR
85+
StartFunction(GlobalDecl(), ResultType, Fn, FnInfo, FunctionArgs,
86+
MD->getLocation(), MD->getLocation());
87+
// NOTE(cir): No ApplyDebugLocation in CIR
88+
89+
// Since we didn't pass a GlobalDecl to StartFunction, do this ourselves.
90+
CGM.getCXXABI().emitInstanceFunctionProlog(MD->getLocation(), *this);
91+
CXXThisValue = CXXABIThisValue;
92+
CurCodeDecl = MD;
93+
CurFuncDecl = MD;
94+
}
95+
96+
void CIRGenFunction::emitCallAndReturnForThunk(cir::FuncOp Callee,
97+
const ThunkInfo *Thunk,
98+
bool IsUnprototyped) {
99+
assert(isa<CXXMethodDecl>(CurGD.getDecl()) &&
100+
"Please use a new CGF for this thunk");
101+
llvm_unreachable("NYI: emitCallAndReturnForThunk");
102+
}
103+
104+
void CIRGenFunction::emitMustTailThunk(GlobalDecl GD,
105+
mlir::Value AdjustedThisPtr,
106+
cir::FuncOp Callee) {
107+
llvm_unreachable("NYI");
108+
}
109+
110+
void CIRGenFunction::generateThunk(cir::FuncOp Fn,
111+
const CIRGenFunctionInfo &FnInfo,
112+
GlobalDecl GD, const ThunkInfo &Thunk,
113+
bool IsUnprototyped) {
114+
startThunk(Fn, GD, FnInfo, IsUnprototyped);
115+
// NOTE(cir): No ApplyDebugLocation in CIR
116+
117+
// Get our callee. Use a placeholder type if this method is unprototyped so
118+
// that CIRGenModule doesn't try to set attributes.
119+
mlir::Type Ty;
120+
if (IsUnprototyped)
121+
llvm_unreachable("NYI: unprototyped thunk placeholder type");
122+
else
123+
Ty = CGM.getTypes().GetFunctionType(FnInfo);
124+
125+
cir::FuncOp Callee = CGM.GetAddrOfFunction(GD, Ty, /*ForVTable=*/true);
126+
127+
// Make the call and return the result.
128+
emitCallAndReturnForThunk(Callee, &Thunk, IsUnprototyped);
129+
}
130+
50131
static bool UseRelativeLayout(const CIRGenModule &CGM) {
51132
return CGM.getTarget().getCXXABI().isItaniumFamily() &&
52133
CGM.getItaniumVTableContext().isRelativeLayout();
@@ -280,11 +361,10 @@ void CIRGenVTables::addVTableComponent(ConstantArrayBuilder &builder,
280361
layout.vtable_thunks()[nextVTableThunkIndex].first ==
281362
componentIndex) {
282363
// Thunks.
283-
llvm_unreachable("NYI");
284-
// auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second;
364+
auto &thunkInfo = layout.vtable_thunks()[nextVTableThunkIndex].second;
285365

286-
// nextVTableThunkIndex++;
287-
// fnPtr = maybeEmitThunk(GD, thunkInfo, /*ForVTable=*/true);
366+
nextVTableThunkIndex++;
367+
fnPtr = maybeEmitThunk(GD, thunkInfo, /*ForVTable=*/true);
288368

289369
} else {
290370
// Otherwise we can use the method definition directly.
@@ -776,7 +856,9 @@ cir::FuncOp CIRGenVTables::maybeEmitThunk(GlobalDecl GD,
776856
return ThunkFn;
777857
llvm_unreachable("NYI method, see OG GenerateVarArgsThunk");
778858
} else {
779-
llvm_unreachable("NYI method, see OG generateThunk");
859+
// Generate the thunk body
860+
CIRGenFunction CGF(CGM, CGM.getBuilder());
861+
CGF.generateThunk(ThunkFn, FnInfo, GD, ThunkAdjustments, IsUnprototyped);
780862
}
781863

782864
setThunkProperties(CGM, ThunkAdjustments, ThunkFn, ForVTable, GD);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// XFAIL: *
2+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.cir.ll
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o %t.codegen.ll
4+
// RUN: diff -u %t.codegen.ll %t.cir.ll
5+
// RUN: FileCheck --input-file=%t.cir.ll %s --check-prefix=CIR
6+
// RUN: FileCheck --input-file=%t.codegen.ll %s --check-prefix=CODEGEN
7+
8+
// Test that CIR thunk generation matches CodeGen behavior
9+
10+
class Base1 {
11+
public:
12+
virtual void foo() {}
13+
};
14+
15+
class Base2 {
16+
public:
17+
virtual void bar() {}
18+
};
19+
20+
class Derived : public Base1, public Base2 {
21+
public:
22+
void bar() override {}
23+
};
24+
25+
void test() {
26+
Derived d;
27+
Base2* b2 = &d;
28+
b2->bar();
29+
}
30+
31+
// Both should generate a thunk
32+
// CIR: define linkonce_odr void @_ZThn{{[0-9]+}}_N7Derived3barEv
33+
// CODEGEN: define linkonce_odr void @_ZThn{{[0-9]+}}_N7Derived3barEv
34+
35+
// Both should have the thunk in the vtable
36+
// CIR: @_ZTV7Derived = linkonce_odr global{{.*}}@_ZThn{{[0-9]+}}_N7Derived3barEv
37+
// CODEGEN: @_ZTV7Derived = linkonce_odr global{{.*}}@_ZThn{{[0-9]+}}_N7Derived3barEv
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fclangir -emit-cir %s -o %t.cir
2+
// RUN: FileCheck --check-prefix=CIR --input-file=%t.cir %s
3+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -mconstructor-aliases -fclangir -emit-llvm -fno-clangir-call-conv-lowering %s -o %t.ll
4+
// RUN: FileCheck --check-prefix=LLVM --input-file=%t.ll %s
5+
6+
// Test basic thunk generation for multiple inheritance with non-virtual thunks
7+
8+
class Base1 {
9+
public:
10+
virtual void foo() {}
11+
int x;
12+
};
13+
14+
class Base2 {
15+
public:
16+
virtual void bar() {}
17+
int y;
18+
};
19+
20+
class Derived : public Base1, public Base2 {
21+
public:
22+
void bar() override {}
23+
};
24+
25+
void test() {
26+
Derived d;
27+
Base2* b2 = &d;
28+
b2->bar();
29+
}
30+
31+
// Check that thunk function is generated with correct mangling
32+
// CIR: cir.func linkonce_odr @_ZThn8_N7Derived3barEv
33+
// CIR-SAME: (!cir.ptr<![[DerivedTy:.*]]>)
34+
35+
// Check thunk is in vtable
36+
// CIR: cir.global linkonce_odr @_ZTV7Derived = #cir.vtable
37+
// CIR-SAME: #cir.global_view<@_ZThn8_N7Derived3barEv>
38+
39+
// LLVM: define linkonce_odr void @_ZThn8_N7Derived3barEv
40+
// LLVM-SAME: ptr
41+
42+
// LLVM: @_ZTV7Derived = linkonce_odr global
43+
// LLVM-SAME: @_ZThn8_N7Derived3barEv
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm %s -o %t.ll
2+
// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
3+
4+
// Test that thunks lower correctly from CIR to LLVM IR
5+
6+
class Base1 {
7+
public:
8+
virtual void foo() {}
9+
int x;
10+
};
11+
12+
class Base2 {
13+
public:
14+
virtual void bar() {}
15+
int y;
16+
};
17+
18+
class Derived : public Base1, public Base2 {
19+
public:
20+
void bar() override {}
21+
};
22+
23+
void test() {
24+
Derived d;
25+
Base2* b2 = &d;
26+
b2->bar();
27+
}
28+
29+
// Check thunk function is defined with correct linkage
30+
// LLVM: define linkonce_odr void @_ZThn8_N7Derived3barEv(ptr{{.*}} %0)
31+
32+
// Check vtable contains thunk with correct offset (8 bytes on x86_64)
33+
// LLVM: @_ZTV7Derived = linkonce_odr global
34+
// LLVM-SAME: @_ZThn8_N7Derived3barEv

0 commit comments

Comments
 (0)