Skip to content

[CIR] Upstream support for C++ member function calls #140290

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 2 commits into from
May 19, 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
14 changes: 13 additions & 1 deletion clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ struct MissingFeatures {
static bool opCallArgEvaluationOrder() { return false; }
static bool opCallCallConv() { return false; }
static bool opCallSideEffect() { return false; }
static bool opCallChainCall() { return false; }
static bool opCallNoPrototypeFunc() { return false; }
static bool opCallMustTail() { return false; }
static bool opCallIndirect() { return false; }
Expand All @@ -107,6 +106,13 @@ struct MissingFeatures {
static bool opCallLandingPad() { return false; }
static bool opCallContinueBlock() { return false; }

// FnInfoOpts -- This is used to track whether calls are chain calls or
// instance methods. Classic codegen uses chain call to track and extra free
// register for x86 and uses instance method as a condition for a thunk
// generation special case. It's not clear that we need either of these in
// pre-lowering CIR codegen.
static bool opCallFnInfoOpts() { return false; }

// ScopeOp handling
static bool opScopeCleanupRegion() { return false; }

Expand Down Expand Up @@ -189,6 +195,12 @@ struct MissingFeatures {
static bool constEmitterArrayILE() { return false; }
static bool constEmitterVectorILE() { return false; }
static bool needsGlobalCtorDtor() { return false; }
static bool emitTypeCheck() { return false; }
static bool cxxabiThisDecl() { return false; }
static bool cxxabiThisAlignment() { return false; }
static bool writebacks() { return false; }
static bool cleanupsToDeactivate() { return false; }
static bool stackBase() { return false; }

// Missing types
static bool dataMemberType() { return false; }
Expand Down
45 changes: 45 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXABI.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This provides an abstract class for C++ code generation. Concrete subclasses
// of this implement code generation for specific C++ ABIs.
//
//===----------------------------------------------------------------------===//

#include "CIRGenCXXABI.h"
#include "CIRGenFunction.h"

#include "clang/AST/Decl.h"
#include "clang/AST/GlobalDecl.h"

using namespace clang;
using namespace clang::CIRGen;

CIRGenCXXABI::~CIRGenCXXABI() {}

void CIRGenCXXABI::buildThisParam(CIRGenFunction &cgf,
FunctionArgList &params) {
const auto *md = cast<CXXMethodDecl>(cgf.curGD.getDecl());

// FIXME: I'm not entirely sure I like using a fake decl just for code
Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah, this seems pretty awful. we should probably figure out what info we need from the decl, and just save THAT instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That comment has been in the code since 2010. I'm starting to think no one is looking for a better way to do it. 😄

Copy link
Collaborator

Choose a reason for hiding this comment

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

Ah, shoot :D Welp! IMO CIR should probably model 'member function' some day, but if this is that old/preexisting, we can just live with it.

I'm generally happy with this patch, but hoping the ot hers can do a more thorough review.

Copy link
Member

Choose a reason for hiding this comment

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

Ugh, this isn't great, and it's not the only instance in CodeGen/CIRGen where clang current creates AST nodes on the fly - one neat side effect of this is to rely on the pre-existing ImplicitParamDecl codegen, so it doesn't needed to be duplicated here (which usually involves handling the scalar/agg/complex versions), but in theory we could hoist the implementation of visitors into common helpers that could be used directly here.

I'm fine leaving this as-is for now.

// generation. Maybe we can come up with a better way?
auto *thisDecl =
ImplicitParamDecl::Create(cgm.getASTContext(), nullptr, md->getLocation(),
&cgm.getASTContext().Idents.get("this"),
md->getThisType(), ImplicitParamKind::CXXThis);
params.push_back(thisDecl);

// Classic codegen save thisDecl in CodeGenFunction::CXXABIThisDecl, but it
// doesn't seem to be needed in CIRGen.
assert(!cir::MissingFeatures::cxxabiThisDecl());

// Classic codegen computes the alignment of thisDecl and saves it in
// CodeGenFunction::CXXABIThisAlignment, but it doesn't seem to be needed in
// CIRGen.
assert(!cir::MissingFeatures::cxxabiThisAlignment());
}
27 changes: 26 additions & 1 deletion clang/lib/CIR/CodeGen/CIRGenCXXABI.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#ifndef LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H
#define LLVM_CLANG_LIB_CIR_CIRGENCXXABI_H

#include "CIRGenCall.h"
#include "CIRGenModule.h"

#include "clang/AST/Mangle.h"
Expand All @@ -31,9 +32,33 @@ class CIRGenCXXABI {
// implemented.
CIRGenCXXABI(CIRGenModule &cgm)
: cgm(cgm), mangleContext(cgm.getASTContext().createMangleContext()) {}
~CIRGenCXXABI();
virtual ~CIRGenCXXABI();

public:
/// Get the type of the implicit "this" parameter used by a method. May return
/// zero if no specific type is applicable, e.g. if the ABI expects the "this"
/// parameter to point to some artificial offset in a complete object due to
/// vbases being reordered.
virtual const clang::CXXRecordDecl *
getThisArgumentTypeForMethod(const clang::CXXMethodDecl *md) {
return md->getParent();
}

/// Build a parameter variable suitable for 'this'.
void buildThisParam(CIRGenFunction &cgf, FunctionArgList &params);

/// Returns true if the given constructor or destructor is one of the kinds
/// that the ABI says returns 'this' (only applies when called non-virtually
/// for destructors).
///
/// There currently is no way to indicate if a destructor returns 'this' when
/// called virtually, and CIR generation does not support this case.
virtual bool hasThisReturn(clang::GlobalDecl gd) const { return false; }

virtual bool hasMostDerivedReturn(clang::GlobalDecl gd) const {
return false;
}

/// Gets the mangle context.
clang::MangleContext &getMangleContext() { return *mangleContext; }
};
Expand Down
186 changes: 186 additions & 0 deletions clang/lib/CIR/CodeGen/CIRGenCXXExpr.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
//===--- CIRGenExprCXX.cpp - Emit CIR Code for C++ expressions ------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This contains code dealing with code generation of C++ expressions
//
//===----------------------------------------------------------------------===//

#include "CIRGenCXXABI.h"
#include "CIRGenFunction.h"

#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprCXX.h"
#include "clang/CIR/MissingFeatures.h"

using namespace clang;
using namespace clang::CIRGen;

namespace {
struct MemberCallInfo {
RequiredArgs reqArgs;
// Number of prefix arguments for the call. Ignores the `this` pointer.
unsigned prefixSize;
};
} // namespace

static MemberCallInfo commonBuildCXXMemberOrOperatorCall(
CIRGenFunction &cgf, const CXXMethodDecl *md, mlir::Value thisPtr,
mlir::Value implicitParam, QualType implicitParamTy, const CallExpr *ce,
CallArgList &args, CallArgList *rtlArgs) {
assert(ce == nullptr || isa<CXXMemberCallExpr>(ce) ||
isa<CXXOperatorCallExpr>(ce));
assert(md->isInstance() &&
"Trying to emit a member or operator call expr on a static method!");

// Push the this ptr.
const CXXRecordDecl *rd =
cgf.cgm.getCXXABI().getThisArgumentTypeForMethod(md);
args.add(RValue::get(thisPtr), cgf.getTypes().deriveThisType(rd, md));

// If there is an implicit parameter (e.g. VTT), emit it.
if (implicitParam) {
args.add(RValue::get(implicitParam), implicitParamTy);
}

const auto *fpt = md->getType()->castAs<FunctionProtoType>();
RequiredArgs required =
RequiredArgs::getFromProtoWithExtraSlots(fpt, args.size());
unsigned prefixSize = args.size() - 1;

// Add the rest of the call args
if (rtlArgs) {
// Special case: if the caller emitted the arguments right-to-left already
// (prior to emitting the *this argument), we're done. This happens for
// assignment operators.
args.addFrom(*rtlArgs);
} else if (ce) {
// Special case: skip first argument of CXXOperatorCall (it is "this").
unsigned argsToSkip = isa<CXXOperatorCallExpr>(ce) ? 1 : 0;
cgf.emitCallArgs(args, fpt, drop_begin(ce->arguments(), argsToSkip),
ce->getDirectCallee());
} else {
assert(
fpt->getNumParams() == 0 &&
"No CallExpr specified for function with non-zero number of arguments");
}

// return {required, prefixSize};
return {required, prefixSize};
}

RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
const CallExpr *ce, const CXXMethodDecl *md, ReturnValueSlot returnValue,
bool hasQualifier, NestedNameSpecifier *qualifier, bool isArrow,
const Expr *base) {
assert(isa<CXXMemberCallExpr>(ce) || isa<CXXOperatorCallExpr>(ce));

if (md->isVirtual()) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: virtual call");
return RValue::get(nullptr);
}

bool trivialForCodegen =
md->isTrivial() || (md->isDefaulted() && md->getParent()->isUnion());
bool trivialAssignment =
trivialForCodegen &&
(md->isCopyAssignmentOperator() || md->isMoveAssignmentOperator()) &&
!md->getParent()->mayInsertExtraPadding();
(void)trivialAssignment;

// C++17 demands that we evaluate the RHS of a (possibly-compound) assignment
// operator before the LHS.
CallArgList rtlArgStorage;
CallArgList *rtlArgs = nullptr;
if (auto *oce = dyn_cast<CXXOperatorCallExpr>(ce)) {
cgm.errorNYI(oce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: operator call");
return RValue::get(nullptr);
}

LValue thisPtr;
if (isArrow) {
LValueBaseInfo baseInfo;
assert(!cir::MissingFeatures::opTBAA());
Address thisValue = emitPointerWithAlignment(base, &baseInfo);
thisPtr = makeAddrLValue(thisValue, base->getType(), baseInfo);
} else {
thisPtr = emitLValue(base);
}

if (const CXXConstructorDecl *ctor = dyn_cast<CXXConstructorDecl>(md)) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: constructor call");
return RValue::get(nullptr);
}

if (trivialForCodegen) {
if (isa<CXXDestructorDecl>(md))
return RValue::get(nullptr);

if (trivialAssignment) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: trivial assignment");
return RValue::get(nullptr);
} else {
assert(md->getParent()->mayInsertExtraPadding() &&
"unknown trivial member function");
}
}

// Compute the function type we're calling
const CXXMethodDecl *calleeDecl = md;
const CIRGenFunctionInfo *fInfo = nullptr;
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(calleeDecl)) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: destructor call");
return RValue::get(nullptr);
} else {
fInfo = &cgm.getTypes().arrangeCXXMethodDeclaration(calleeDecl);
}

mlir::Type ty = cgm.getTypes().getFunctionType(*fInfo);

assert(!cir::MissingFeatures::sanitizers());
assert(!cir::MissingFeatures::emitTypeCheck());

if (const auto *dtor = dyn_cast<CXXDestructorDecl>(calleeDecl)) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: destructor call");
return RValue::get(nullptr);
}

assert(!cir::MissingFeatures::sanitizers());
if (getLangOpts().AppleKext) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: AppleKext");
return RValue::get(nullptr);
}
CIRGenCallee callee =
CIRGenCallee::forDirect(cgm.getAddrOfFunction(md, ty), GlobalDecl(md));

return emitCXXMemberOrOperatorCall(
calleeDecl, callee, returnValue, thisPtr.getPointer(),
/*ImplicitParam=*/nullptr, QualType(), ce, rtlArgs);
}

RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
const CXXMethodDecl *md, const CIRGenCallee &callee,
ReturnValueSlot returnValue, mlir::Value thisPtr, mlir::Value implicitParam,
QualType implicitParamTy, const CallExpr *ce, CallArgList *rtlArgs) {
const auto *fpt = md->getType()->castAs<FunctionProtoType>();
CallArgList args;
MemberCallInfo callInfo = commonBuildCXXMemberOrOperatorCall(
*this, md, thisPtr, implicitParam, implicitParamTy, ce, args, rtlArgs);
auto &fnInfo = cgm.getTypes().arrangeCXXMethodCall(
args, fpt, callInfo.reqArgs, callInfo.prefixSize);
assert((ce || currSrcLoc) && "expected source location");
mlir::Location loc = ce ? getLoc(ce->getExprLoc()) : *currSrcLoc;
assert(!cir::MissingFeatures::opCallMustTail());
return emitCall(fnInfo, callee, returnValue, args, nullptr, loc);
}
Loading