Skip to content

Commit 5bf482b

Browse files
Lancerntomtor
authored andcommitted
[CIR] Function calls with aggregate arguments and return values (llvm#143377)
This patch updates cir.call operation and allows function calls with aggregate arguments and return values. It seems that C++ class support is still at a minimum now. I tried to make a call to a C++ function with an argument of aggregate type but it failed because the initialization of C++ class / struct is NYI. I also tried to inline this part of support into this patch, but the mixed patch quickly blows in size and becomes unsuitable for review. Thus, tests for calling functions with aggregate arguments are added only for C for now.
1 parent dc23003 commit 5bf482b

File tree

11 files changed

+411
-16
lines changed

11 files changed

+411
-16
lines changed

clang/include/clang/CIR/MissingFeatures.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ struct MissingFeatures {
173173
static bool stackSaveOp() { return false; }
174174
static bool aggValueSlot() { return false; }
175175
static bool aggValueSlotMayOverlap() { return false; }
176+
static bool aggValueSlotVolatile() { return false; }
177+
static bool aggValueSlotDestructedFlag() { return false; }
178+
static bool aggValueSlotAlias() { return false; }
179+
static bool aggValueSlotGC() { return false; }
176180
static bool generateDebugInfo() { return false; }
177181
static bool pointerOverflowSanitizer() { return false; }
178182
static bool fpConstraints() { return false; }
@@ -230,6 +234,8 @@ struct MissingFeatures {
230234
static bool attributeNoBuiltin() { return false; }
231235
static bool thunks() { return false; }
232236
static bool runCleanupsScope() { return false; }
237+
static bool lowerAggregateLoadStore() { return false; }
238+
static bool dataLayoutTypeAllocSize() { return false; }
233239

234240
// Missing types
235241
static bool dataMemberType() { return false; }

clang/lib/CIR/CodeGen/CIRGenBuilder.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,18 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
332332
return Address(baseAddr, destType, addr.getAlignment());
333333
}
334334

335+
/// Cast the element type of the given address to a different type,
336+
/// preserving information like the alignment.
337+
Address createElementBitCast(mlir::Location loc, Address addr,
338+
mlir::Type destType) {
339+
if (destType == addr.getElementType())
340+
return addr;
341+
342+
auto ptrTy = getPointerTo(destType);
343+
return Address(createBitcast(loc, addr.getPointer(), ptrTy), destType,
344+
addr.getAlignment());
345+
}
346+
335347
cir::LoadOp createLoad(mlir::Location loc, Address addr,
336348
bool isVolatile = false) {
337349
mlir::IntegerAttr align = getAlignmentAttr(addr.getAlignment());

clang/lib/CIR/CodeGen/CIRGenCall.cpp

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,23 @@ CIRGenCallee CIRGenCallee::prepareConcreteCallee(CIRGenFunction &cgf) const {
6060
return *this;
6161
}
6262

63+
void CIRGenFunction::emitAggregateStore(mlir::Value value, Address dest) {
64+
// In classic codegen:
65+
// Function to store a first-class aggregate into memory. We prefer to
66+
// store the elements rather than the aggregate to be more friendly to
67+
// fast-isel.
68+
// In CIR codegen:
69+
// Emit the most simple cir.store possible (e.g. a store for a whole
70+
// record), which can later be broken down in other CIR levels (or prior
71+
// to dialect codegen).
72+
73+
// Stored result for the callers of this function expected to be in the same
74+
// scope as the value, don't make assumptions about current insertion point.
75+
mlir::OpBuilder::InsertionGuard guard(builder);
76+
builder.setInsertionPointAfter(value.getDefiningOp());
77+
builder.createStore(*currSrcLoc, value, dest);
78+
}
79+
6380
/// Returns the canonical formal type of the given C++ method.
6481
static CanQual<FunctionProtoType> getFormalType(const CXXMethodDecl *md) {
6582
return md->getType()
@@ -439,8 +456,49 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
439456
assert(!cir::MissingFeatures::opCallBitcastArg());
440457
cirCallArgs[argNo] = v;
441458
} else {
442-
assert(!cir::MissingFeatures::opCallAggregateArgs());
443-
cgm.errorNYI("emitCall: aggregate function call argument");
459+
Address src = Address::invalid();
460+
if (!arg.isAggregate())
461+
cgm.errorNYI(loc, "emitCall: non-aggregate call argument");
462+
else
463+
src = arg.hasLValue() ? arg.getKnownLValue().getAddress()
464+
: arg.getKnownRValue().getAggregateAddress();
465+
466+
// Fast-isel and the optimizer generally like scalar values better than
467+
// FCAs, so we flatten them if this is safe to do for this argument.
468+
auto argRecordTy = cast<cir::RecordType>(argType);
469+
mlir::Type srcTy = src.getElementType();
470+
// FIXME(cir): get proper location for each argument.
471+
mlir::Location argLoc = loc;
472+
473+
// If the source type is smaller than the destination type of the
474+
// coerce-to logic, copy the source value into a temp alloca the size
475+
// of the destination type to allow loading all of it. The bits past
476+
// the source value are left undef.
477+
// FIXME(cir): add data layout info and compare sizes instead of
478+
// matching the types.
479+
//
480+
// uint64_t SrcSize = CGM.getDataLayout().getTypeAllocSize(SrcTy);
481+
// uint64_t DstSize = CGM.getDataLayout().getTypeAllocSize(STy);
482+
// if (SrcSize < DstSize) {
483+
assert(!cir::MissingFeatures::dataLayoutTypeAllocSize());
484+
if (srcTy != argRecordTy) {
485+
cgm.errorNYI(loc, "emitCall: source type does not match argument type");
486+
} else {
487+
// FIXME(cir): this currently only runs when the types are exactly the
488+
// same, but should be when alloc sizes are the same, fix this as soon
489+
// as datalayout gets introduced.
490+
assert(!cir::MissingFeatures::dataLayoutTypeAllocSize());
491+
}
492+
493+
// assert(NumCIRArgs == STy.getMembers().size());
494+
// In LLVMGen: Still only pass the struct without any gaps but mark it
495+
// as such somehow.
496+
//
497+
// In CIRGen: Emit a load from the "whole" struct,
498+
// which shall be broken later by some lowering step into multiple
499+
// loads.
500+
assert(!cir::MissingFeatures::lowerAggregateLoadStore());
501+
cirCallArgs[argNo] = builder.createLoad(argLoc, src);
444502
}
445503
}
446504

@@ -479,6 +537,7 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
479537

480538
assert(!cir::MissingFeatures::opCallAttrs());
481539

540+
mlir::Location callLoc = loc;
482541
cir::CIRCallOpInterface theCall = emitCallLikeOp(
483542
*this, loc, indirectFuncTy, indirectFuncVal, directFuncOp, cirCallArgs);
484543

@@ -492,6 +551,19 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
492551
if (isa<cir::VoidType>(retCIRTy))
493552
return getUndefRValue(retTy);
494553
switch (getEvaluationKind(retTy)) {
554+
case cir::TEK_Aggregate: {
555+
Address destPtr = returnValue.getValue();
556+
557+
if (!destPtr.isValid())
558+
destPtr = createMemTemp(retTy, callLoc, getCounterAggTmpAsString());
559+
560+
mlir::ResultRange results = theCall->getOpResults();
561+
assert(results.size() <= 1 && "multiple returns from a call");
562+
563+
SourceLocRAIIObject loc{*this, callLoc};
564+
emitAggregateStore(results[0], destPtr);
565+
return RValue::getAggregate(destPtr);
566+
}
495567
case cir::TEK_Scalar: {
496568
mlir::ResultRange results = theCall->getOpResults();
497569
assert(results.size() == 1 && "unexpected number of returns");
@@ -508,7 +580,6 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
508580
return RValue::get(results[0]);
509581
}
510582
case cir::TEK_Complex:
511-
case cir::TEK_Aggregate:
512583
cgm.errorNYI(loc, "unsupported evaluation kind of function call result");
513584
return getUndefRValue(retTy);
514585
}
@@ -527,10 +598,21 @@ void CIRGenFunction::emitCallArg(CallArgList &args, const clang::Expr *e,
527598

528599
bool hasAggregateEvalKind = hasAggregateEvaluationKind(argType);
529600

530-
if (hasAggregateEvalKind) {
531-
assert(!cir::MissingFeatures::opCallAggregateArgs());
532-
cgm.errorNYI(e->getSourceRange(),
533-
"emitCallArg: aggregate function call argument");
601+
// In the Microsoft C++ ABI, aggregate arguments are destructed by the callee.
602+
// However, we still have to push an EH-only cleanup in case we unwind before
603+
// we make it to the call.
604+
if (argType->isRecordType() &&
605+
argType->castAs<RecordType>()->getDecl()->isParamDestroyedInCallee()) {
606+
assert(!cir::MissingFeatures::msabi());
607+
cgm.errorNYI(e->getSourceRange(), "emitCallArg: msabi is NYI");
608+
}
609+
610+
if (hasAggregateEvalKind && isa<ImplicitCastExpr>(e) &&
611+
cast<CastExpr>(e)->getCastKind() == CK_LValueToRValue) {
612+
LValue lv = emitLValue(cast<CastExpr>(e)->getSubExpr());
613+
assert(lv.isSimple());
614+
args.addUncopiedAggregate(lv, argType);
615+
return;
534616
}
535617

536618
args.add(emitAnyExprToTemp(e), argType);
@@ -551,12 +633,13 @@ QualType CIRGenFunction::getVarArgType(const Expr *arg) {
551633
/// Similar to emitAnyExpr(), however, the result will always be accessible
552634
/// even if no aggregate location is provided.
553635
RValue CIRGenFunction::emitAnyExprToTemp(const Expr *e) {
554-
assert(!cir::MissingFeatures::opCallAggregateArgs());
636+
AggValueSlot aggSlot = AggValueSlot::ignored();
555637

556638
if (hasAggregateEvaluationKind(e->getType()))
557-
cgm.errorNYI(e->getSourceRange(), "emit aggregate value to temp");
639+
aggSlot = createAggTemp(e->getType(), getLoc(e->getSourceRange()),
640+
getCounterAggTmpAsString());
558641

559-
return emitAnyExpr(e);
642+
return emitAnyExpr(e, aggSlot);
560643
}
561644

562645
void CIRGenFunction::emitCallArgs(

clang/lib/CIR/CodeGen/CIRGenCall.h

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,16 @@ struct CallArg {
133133
CallArg(RValue rv, clang::QualType ty)
134134
: rv(rv), hasLV(false), isUsed(false), ty(ty) {}
135135

136+
CallArg(LValue lv, clang::QualType ty)
137+
: lv(lv), hasLV(true), isUsed(false), ty(ty) {}
138+
136139
bool hasLValue() const { return hasLV; }
137140

141+
LValue getKnownLValue() const {
142+
assert(hasLV && !isUsed);
143+
return lv;
144+
}
145+
138146
RValue getKnownRValue() const {
139147
assert(!hasLV && !isUsed);
140148
return rv;
@@ -147,6 +155,10 @@ class CallArgList : public llvm::SmallVector<CallArg, 8> {
147155
public:
148156
void add(RValue rvalue, clang::QualType type) { emplace_back(rvalue, type); }
149157

158+
void addUncopiedAggregate(LValue lvalue, clang::QualType type) {
159+
emplace_back(lvalue, type);
160+
}
161+
150162
/// Add all the arguments from another CallArgList to this one. After doing
151163
/// this, the old CallArgList retains its list of arguments, but must not
152164
/// be used to emit a call.
@@ -162,7 +174,15 @@ class CallArgList : public llvm::SmallVector<CallArg, 8> {
162174

163175
/// Contains the address where the return value of a function can be stored, and
164176
/// whether the address is volatile or not.
165-
class ReturnValueSlot {};
177+
class ReturnValueSlot {
178+
Address addr = Address::invalid();
179+
180+
public:
181+
ReturnValueSlot() = default;
182+
ReturnValueSlot(Address addr) : addr(addr) {}
183+
184+
Address getValue() const { return addr; }
185+
};
166186

167187
} // namespace clang::CIRGen
168188

clang/lib/CIR/CodeGen/CIRGenExpr.cpp

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1010,16 +1010,20 @@ LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) {
10101010

10111011
/// Emit code to compute the specified expression which
10121012
/// can have any type. The result is returned as an RValue struct.
1013-
RValue CIRGenFunction::emitAnyExpr(const Expr *e) {
1013+
RValue CIRGenFunction::emitAnyExpr(const Expr *e, AggValueSlot aggSlot) {
10141014
switch (CIRGenFunction::getEvaluationKind(e->getType())) {
10151015
case cir::TEK_Scalar:
10161016
return RValue::get(emitScalarExpr(e));
10171017
case cir::TEK_Complex:
10181018
cgm.errorNYI(e->getSourceRange(), "emitAnyExpr: complex type");
10191019
return RValue::get(nullptr);
1020-
case cir::TEK_Aggregate:
1021-
cgm.errorNYI(e->getSourceRange(), "emitAnyExpr: aggregate type");
1022-
return RValue::get(nullptr);
1020+
case cir::TEK_Aggregate: {
1021+
if (aggSlot.isIgnored())
1022+
aggSlot = createAggTemp(e->getType(), getLoc(e->getSourceRange()),
1023+
getCounterAggTmpAsString());
1024+
emitAggExpr(e, aggSlot);
1025+
return aggSlot.asRValue();
1026+
}
10231027
}
10241028
llvm_unreachable("bad evaluation kind");
10251029
}

clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
2828
CIRGenFunction &cgf;
2929
AggValueSlot dest;
3030

31+
// Calls `fn` with a valid return value slot, potentially creating a temporary
32+
// to do so. If a temporary is created, an appropriate copy into `Dest` will
33+
// be emitted, as will lifetime markers.
34+
//
35+
// The given function should take a ReturnValueSlot, and return an RValue that
36+
// points to said slot.
37+
void withReturnValueSlot(const Expr *e,
38+
llvm::function_ref<RValue(ReturnValueSlot)> fn);
39+
3140
AggValueSlot ensureSlot(mlir::Location loc, QualType t) {
3241
if (!dest.isIgnored())
3342
return dest;
@@ -40,16 +49,28 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
4049
AggExprEmitter(CIRGenFunction &cgf, AggValueSlot dest)
4150
: cgf(cgf), dest(dest) {}
4251

52+
/// Given an expression with aggregate type that represents a value lvalue,
53+
/// this method emits the address of the lvalue, then loads the result into
54+
/// DestPtr.
55+
void emitAggLoadOfLValue(const Expr *e);
56+
4357
void emitArrayInit(Address destPtr, cir::ArrayType arrayTy, QualType arrayQTy,
4458
Expr *exprToVisit, ArrayRef<Expr *> args,
4559
Expr *arrayFiller);
4660

61+
/// Perform the final copy to DestPtr, if desired.
62+
void emitFinalDestCopy(QualType type, const LValue &src);
63+
4764
void emitInitializationToLValue(Expr *e, LValue lv);
4865

4966
void emitNullInitializationToLValue(mlir::Location loc, LValue lv);
5067

5168
void Visit(Expr *e) { StmtVisitor<AggExprEmitter>::Visit(e); }
5269

70+
void VisitCallExpr(const CallExpr *e);
71+
72+
void VisitDeclRefExpr(DeclRefExpr *e) { emitAggLoadOfLValue(e); }
73+
5374
void VisitInitListExpr(InitListExpr *e);
5475
void VisitCXXConstructExpr(const CXXConstructExpr *e);
5576

@@ -80,6 +101,17 @@ static bool isTrivialFiller(Expr *e) {
80101
return false;
81102
}
82103

104+
/// Given an expression with aggregate type that represents a value lvalue, this
105+
/// method emits the address of the lvalue, then loads the result into DestPtr.
106+
void AggExprEmitter::emitAggLoadOfLValue(const Expr *e) {
107+
LValue lv = cgf.emitLValue(e);
108+
109+
// If the type of the l-value is atomic, then do an atomic load.
110+
assert(!cir::MissingFeatures::opLoadStoreAtomic());
111+
112+
emitFinalDestCopy(e->getType(), lv);
113+
}
114+
83115
void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
84116
QualType arrayQTy, Expr *e,
85117
ArrayRef<Expr *> args, Expr *arrayFiller) {
@@ -182,6 +214,18 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
182214
}
183215
}
184216

217+
/// Perform the final copy to destPtr, if desired.
218+
void AggExprEmitter::emitFinalDestCopy(QualType type, const LValue &src) {
219+
// If dest is ignored, then we're evaluating an aggregate expression
220+
// in a context that doesn't care about the result. Note that loads
221+
// from volatile l-values force the existence of a non-ignored
222+
// destination.
223+
if (dest.isIgnored())
224+
return;
225+
226+
cgf.cgm.errorNYI("emitFinalDestCopy: non-ignored dest is NYI");
227+
}
228+
185229
void AggExprEmitter::emitInitializationToLValue(Expr *e, LValue lv) {
186230
const QualType type = lv.getType();
187231

@@ -250,6 +294,44 @@ void AggExprEmitter::emitNullInitializationToLValue(mlir::Location loc,
250294
cgf.emitNullInitialization(loc, lv.getAddress(), lv.getType());
251295
}
252296

297+
void AggExprEmitter::VisitCallExpr(const CallExpr *e) {
298+
if (e->getCallReturnType(cgf.getContext())->isReferenceType()) {
299+
cgf.cgm.errorNYI(e->getSourceRange(), "reference return type");
300+
return;
301+
}
302+
303+
withReturnValueSlot(
304+
e, [&](ReturnValueSlot slot) { return cgf.emitCallExpr(e, slot); });
305+
}
306+
307+
void AggExprEmitter::withReturnValueSlot(
308+
const Expr *e, llvm::function_ref<RValue(ReturnValueSlot)> fn) {
309+
QualType retTy = e->getType();
310+
311+
assert(!cir::MissingFeatures::aggValueSlotDestructedFlag());
312+
bool requiresDestruction =
313+
retTy.isDestructedType() == QualType::DK_nontrivial_c_struct;
314+
if (requiresDestruction)
315+
cgf.cgm.errorNYI(
316+
e->getSourceRange(),
317+
"withReturnValueSlot: return value requiring destruction is NYI");
318+
319+
// If it makes no observable difference, save a memcpy + temporary.
320+
//
321+
// We need to always provide our own temporary if destruction is required.
322+
// Otherwise, fn will emit its own, notice that it's "unused", and end its
323+
// lifetime before we have the chance to emit a proper destructor call.
324+
assert(!cir::MissingFeatures::aggValueSlotAlias());
325+
assert(!cir::MissingFeatures::aggValueSlotGC());
326+
327+
Address retAddr = dest.getAddress();
328+
assert(!cir::MissingFeatures::emitLifetimeMarkers());
329+
330+
assert(!cir::MissingFeatures::aggValueSlotVolatile());
331+
assert(!cir::MissingFeatures::aggValueSlotDestructedFlag());
332+
fn(ReturnValueSlot(retAddr));
333+
}
334+
253335
void AggExprEmitter::VisitInitListExpr(InitListExpr *e) {
254336
if (e->hadArrayRangeDesignator())
255337
llvm_unreachable("GNU array range designator extension");

0 commit comments

Comments
 (0)