Skip to content

[CIR] Function calls with aggregate arguments and return values #143377

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ struct MissingFeatures {
static bool stackSaveOp() { return false; }
static bool aggValueSlot() { return false; }
static bool aggValueSlotMayOverlap() { return false; }
static bool aggValueSlotVolatile() { return false; }
static bool aggValueSlotDestructedFlag() { return false; }
static bool aggValueSlotAlias() { return false; }
static bool aggValueSlotGC() { return false; }
static bool generateDebugInfo() { return false; }
static bool pointerOverflowSanitizer() { return false; }
static bool fpConstraints() { return false; }
Expand Down Expand Up @@ -230,6 +234,8 @@ struct MissingFeatures {
static bool attributeNoBuiltin() { return false; }
static bool thunks() { return false; }
static bool runCleanupsScope() { return false; }
static bool lowerAggregateLoadStore() { return false; }
static bool dataLayoutTypeAllocSize() { return false; }

// Missing types
static bool dataMemberType() { return false; }
Expand Down
12 changes: 12 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,18 @@ class CIRGenBuilderTy : public cir::CIRBaseBuilderTy {
return Address(baseAddr, destType, addr.getAlignment());
}

/// Cast the element type of the given address to a different type,
/// preserving information like the alignment.
Address createElementBitCast(mlir::Location loc, Address addr,
mlir::Type destType) {
if (destType == addr.getElementType())
return addr;

auto ptrTy = getPointerTo(destType);
return Address(createBitcast(loc, addr.getPointer(), ptrTy), destType,
addr.getAlignment());
}

cir::LoadOp createLoad(mlir::Location loc, Address addr,
bool isVolatile = false) {
mlir::IntegerAttr align = getAlignmentAttr(addr.getAlignment());
Expand Down
103 changes: 93 additions & 10 deletions clang/lib/CIR/CodeGen/CIRGenCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,23 @@ CIRGenCallee CIRGenCallee::prepareConcreteCallee(CIRGenFunction &cgf) const {
return *this;
}

void CIRGenFunction::emitAggregateStore(mlir::Value value, Address dest) {
// In classic codegen:
// Function to store a first-class aggregate into memory. We prefer to
// store the elements rather than the aggregate to be more friendly to
// fast-isel.
// In CIR codegen:
// Emit the most simple cir.store possible (e.g. a store for a whole
// record), which can later be broken down in other CIR levels (or prior
// to dialect codegen).

// Stored result for the callers of this function expected to be in the same
// scope as the value, don't make assumptions about current insertion point.
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPointAfter(value.getDefiningOp());
builder.createStore(*currSrcLoc, value, dest);
}

/// Returns the canonical formal type of the given C++ method.
static CanQual<FunctionProtoType> getFormalType(const CXXMethodDecl *md) {
return md->getType()
Expand Down Expand Up @@ -439,8 +456,49 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
assert(!cir::MissingFeatures::opCallBitcastArg());
cirCallArgs[argNo] = v;
} else {
assert(!cir::MissingFeatures::opCallAggregateArgs());
cgm.errorNYI("emitCall: aggregate function call argument");
Address src = Address::invalid();
if (!arg.isAggregate())
cgm.errorNYI(loc, "emitCall: non-aggregate call argument");
else
src = arg.hasLValue() ? arg.getKnownLValue().getAddress()
: arg.getKnownRValue().getAggregateAddress();

// Fast-isel and the optimizer generally like scalar values better than
// FCAs, so we flatten them if this is safe to do for this argument.
auto argRecordTy = cast<cir::RecordType>(argType);
mlir::Type srcTy = src.getElementType();
// FIXME(cir): get proper location for each argument.
mlir::Location argLoc = loc;

// If the source type is smaller than the destination type of the
// coerce-to logic, copy the source value into a temp alloca the size
// of the destination type to allow loading all of it. The bits past
// the source value are left undef.
// FIXME(cir): add data layout info and compare sizes instead of
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we do this now?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is data layout info available in upstream yet?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DataLayout is there, but getTypeAllocSize hasn't been upstreamed yet. This can wait, but let's not forget about it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a new missing feature flag for tracking this.

// matching the types.
//
// uint64_t SrcSize = CGM.getDataLayout().getTypeAllocSize(SrcTy);
// uint64_t DstSize = CGM.getDataLayout().getTypeAllocSize(STy);
// if (SrcSize < DstSize) {
assert(!cir::MissingFeatures::dataLayoutTypeAllocSize());
if (srcTy != argRecordTy) {
cgm.errorNYI(loc, "emitCall: source type does not match argument type");
} else {
// FIXME(cir): this currently only runs when the types are exactly the
// same, but should be when alloc sizes are the same, fix this as soon
// as datalayout gets introduced.
assert(!cir::MissingFeatures::dataLayoutTypeAllocSize());
}

// assert(NumCIRArgs == STy.getMembers().size());
// In LLVMGen: Still only pass the struct without any gaps but mark it
// as such somehow.
//
// In CIRGen: Emit a load from the "whole" struct,
// which shall be broken later by some lowering step into multiple
// loads.
assert(!cir::MissingFeatures::lowerAggregateLoadStore());
cirCallArgs[argNo] = builder.createLoad(argLoc, src);
}
}

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

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

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

Expand All @@ -492,6 +551,19 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
if (isa<cir::VoidType>(retCIRTy))
return getUndefRValue(retTy);
switch (getEvaluationKind(retTy)) {
case cir::TEK_Aggregate: {
Address destPtr = returnValue.getValue();

if (!destPtr.isValid())
destPtr = createMemTemp(retTy, callLoc, getCounterAggTmpAsString());

mlir::ResultRange results = theCall->getOpResults();
assert(results.size() <= 1 && "multiple returns from a call");

SourceLocRAIIObject loc{*this, callLoc};
emitAggregateStore(results[0], destPtr);
return RValue::getAggregate(destPtr);
}
case cir::TEK_Scalar: {
mlir::ResultRange results = theCall->getOpResults();
assert(results.size() == 1 && "unexpected number of returns");
Expand All @@ -508,7 +580,6 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
return RValue::get(results[0]);
}
case cir::TEK_Complex:
case cir::TEK_Aggregate:
cgm.errorNYI(loc, "unsupported evaluation kind of function call result");
return getUndefRValue(retTy);
}
Expand All @@ -527,10 +598,21 @@ void CIRGenFunction::emitCallArg(CallArgList &args, const clang::Expr *e,

bool hasAggregateEvalKind = hasAggregateEvaluationKind(argType);

if (hasAggregateEvalKind) {
assert(!cir::MissingFeatures::opCallAggregateArgs());
cgm.errorNYI(e->getSourceRange(),
"emitCallArg: aggregate function call argument");
// In the Microsoft C++ ABI, aggregate arguments are destructed by the callee.
// However, we still have to push an EH-only cleanup in case we unwind before
// we make it to the call.
if (argType->isRecordType() &&
argType->castAs<RecordType>()->getDecl()->isParamDestroyedInCallee()) {
assert(!cir::MissingFeatures::msabi());
cgm.errorNYI(e->getSourceRange(), "emitCallArg: msabi is NYI");
}

if (hasAggregateEvalKind && isa<ImplicitCastExpr>(e) &&
cast<CastExpr>(e)->getCastKind() == CK_LValueToRValue) {
LValue lv = emitLValue(cast<CastExpr>(e)->getSubExpr());
assert(lv.isSimple());
args.addUncopiedAggregate(lv, argType);
return;
}

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

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

return emitAnyExpr(e);
return emitAnyExpr(e, aggSlot);
}

void CIRGenFunction::emitCallArgs(
Expand Down
22 changes: 21 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenCall.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,16 @@ struct CallArg {
CallArg(RValue rv, clang::QualType ty)
: rv(rv), hasLV(false), isUsed(false), ty(ty) {}

CallArg(LValue lv, clang::QualType ty)
: lv(lv), hasLV(true), isUsed(false), ty(ty) {}

bool hasLValue() const { return hasLV; }

LValue getKnownLValue() const {
assert(hasLV && !isUsed);
return lv;
}

RValue getKnownRValue() const {
assert(!hasLV && !isUsed);
return rv;
Expand All @@ -147,6 +155,10 @@ class CallArgList : public llvm::SmallVector<CallArg, 8> {
public:
void add(RValue rvalue, clang::QualType type) { emplace_back(rvalue, type); }

void addUncopiedAggregate(LValue lvalue, clang::QualType type) {
emplace_back(lvalue, type);
}

/// Add all the arguments from another CallArgList to this one. After doing
/// this, the old CallArgList retains its list of arguments, but must not
/// be used to emit a call.
Expand All @@ -162,7 +174,15 @@ class CallArgList : public llvm::SmallVector<CallArg, 8> {

/// Contains the address where the return value of a function can be stored, and
/// whether the address is volatile or not.
class ReturnValueSlot {};
class ReturnValueSlot {
Address addr = Address::invalid();

public:
ReturnValueSlot() = default;
ReturnValueSlot(Address addr) : addr(addr) {}

Address getValue() const { return addr; }
};

} // namespace clang::CIRGen

Expand Down
12 changes: 8 additions & 4 deletions clang/lib/CIR/CodeGen/CIRGenExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1010,16 +1010,20 @@ LValue CIRGenFunction::emitBinaryOperatorLValue(const BinaryOperator *e) {

/// Emit code to compute the specified expression which
/// can have any type. The result is returned as an RValue struct.
RValue CIRGenFunction::emitAnyExpr(const Expr *e) {
RValue CIRGenFunction::emitAnyExpr(const Expr *e, AggValueSlot aggSlot) {
switch (CIRGenFunction::getEvaluationKind(e->getType())) {
case cir::TEK_Scalar:
return RValue::get(emitScalarExpr(e));
case cir::TEK_Complex:
cgm.errorNYI(e->getSourceRange(), "emitAnyExpr: complex type");
return RValue::get(nullptr);
case cir::TEK_Aggregate:
cgm.errorNYI(e->getSourceRange(), "emitAnyExpr: aggregate type");
return RValue::get(nullptr);
case cir::TEK_Aggregate: {
if (aggSlot.isIgnored())
aggSlot = createAggTemp(e->getType(), getLoc(e->getSourceRange()),
getCounterAggTmpAsString());
emitAggExpr(e, aggSlot);
return aggSlot.asRValue();
}
}
llvm_unreachable("bad evaluation kind");
}
Expand Down
82 changes: 82 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenExprAggregate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,15 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
CIRGenFunction &cgf;
AggValueSlot dest;

// Calls `fn` with a valid return value slot, potentially creating a temporary
// to do so. If a temporary is created, an appropriate copy into `Dest` will
// be emitted, as will lifetime markers.
//
// The given function should take a ReturnValueSlot, and return an RValue that
// points to said slot.
void withReturnValueSlot(const Expr *e,
llvm::function_ref<RValue(ReturnValueSlot)> fn);

AggValueSlot ensureSlot(mlir::Location loc, QualType t) {
if (!dest.isIgnored())
return dest;
Expand All @@ -40,16 +49,28 @@ class AggExprEmitter : public StmtVisitor<AggExprEmitter> {
AggExprEmitter(CIRGenFunction &cgf, AggValueSlot dest)
: cgf(cgf), dest(dest) {}

/// Given an expression with aggregate type that represents a value lvalue,
/// this method emits the address of the lvalue, then loads the result into
/// DestPtr.
void emitAggLoadOfLValue(const Expr *e);

void emitArrayInit(Address destPtr, cir::ArrayType arrayTy, QualType arrayQTy,
Expr *exprToVisit, ArrayRef<Expr *> args,
Expr *arrayFiller);

/// Perform the final copy to DestPtr, if desired.
void emitFinalDestCopy(QualType type, const LValue &src);

void emitInitializationToLValue(Expr *e, LValue lv);

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

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

void VisitCallExpr(const CallExpr *e);

void VisitDeclRefExpr(DeclRefExpr *e) { emitAggLoadOfLValue(e); }

void VisitInitListExpr(InitListExpr *e);
void VisitCXXConstructExpr(const CXXConstructExpr *e);

Expand Down Expand Up @@ -80,6 +101,17 @@ static bool isTrivialFiller(Expr *e) {
return false;
}

/// Given an expression with aggregate type that represents a value lvalue, this
/// method emits the address of the lvalue, then loads the result into DestPtr.
void AggExprEmitter::emitAggLoadOfLValue(const Expr *e) {
LValue lv = cgf.emitLValue(e);

// If the type of the l-value is atomic, then do an atomic load.
assert(!cir::MissingFeatures::opLoadStoreAtomic());

emitFinalDestCopy(e->getType(), lv);
}

void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
QualType arrayQTy, Expr *e,
ArrayRef<Expr *> args, Expr *arrayFiller) {
Expand Down Expand Up @@ -182,6 +214,18 @@ void AggExprEmitter::emitArrayInit(Address destPtr, cir::ArrayType arrayTy,
}
}

/// Perform the final copy to destPtr, if desired.
void AggExprEmitter::emitFinalDestCopy(QualType type, const LValue &src) {
// If dest is ignored, then we're evaluating an aggregate expression
// in a context that doesn't care about the result. Note that loads
// from volatile l-values force the existence of a non-ignored
// destination.
if (dest.isIgnored())
return;

cgf.cgm.errorNYI("emitFinalDestCopy: non-ignored dest is NYI");
}

void AggExprEmitter::emitInitializationToLValue(Expr *e, LValue lv) {
const QualType type = lv.getType();

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

void AggExprEmitter::VisitCallExpr(const CallExpr *e) {
if (e->getCallReturnType(cgf.getContext())->isReferenceType()) {
cgf.cgm.errorNYI(e->getSourceRange(), "reference return type");
return;
}

withReturnValueSlot(
e, [&](ReturnValueSlot slot) { return cgf.emitCallExpr(e, slot); });
}

void AggExprEmitter::withReturnValueSlot(
const Expr *e, llvm::function_ref<RValue(ReturnValueSlot)> fn) {
QualType retTy = e->getType();

assert(!cir::MissingFeatures::aggValueSlotDestructedFlag());
bool requiresDestruction =
retTy.isDestructedType() == QualType::DK_nontrivial_c_struct;
if (requiresDestruction)
cgf.cgm.errorNYI(
e->getSourceRange(),
"withReturnValueSlot: return value requiring destruction is NYI");

// If it makes no observable difference, save a memcpy + temporary.
//
// We need to always provide our own temporary if destruction is required.
// Otherwise, fn will emit its own, notice that it's "unused", and end its
// lifetime before we have the chance to emit a proper destructor call.
assert(!cir::MissingFeatures::aggValueSlotAlias());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm adding a couple of these in #143932. We should revisit this after both PRs are merged.

assert(!cir::MissingFeatures::aggValueSlotGC());

Address retAddr = dest.getAddress();
assert(!cir::MissingFeatures::emitLifetimeMarkers());

assert(!cir::MissingFeatures::aggValueSlotVolatile());
assert(!cir::MissingFeatures::aggValueSlotDestructedFlag());
fn(ReturnValueSlot(retAddr));
}

void AggExprEmitter::VisitInitListExpr(InitListExpr *e) {
if (e->hadArrayRangeDesignator())
llvm_unreachable("GNU array range designator extension");
Expand Down
Loading