Skip to content

Commit

Permalink
[CIR][ABI][NFC] Add CC lowering for void FuncOps (llvm#678)
Browse files Browse the repository at this point in the history
This patch implements the lowering of function definitions with no
arguments and returns. In pratice, nothing has to be done (at least for
the x86 ABI), so this case is used as a primer for the target lowering
library since it helps populate the base logic for handling calling
convention lowering of function definitions.
  • Loading branch information
sitio-couto authored Jun 11, 2024
1 parent 66bb15b commit 901f532
Show file tree
Hide file tree
Showing 9 changed files with 445 additions and 7 deletions.
11 changes: 11 additions & 0 deletions clang/include/clang/CIR/MissingFeatures.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,22 @@ struct MissingFeatures {
//-- Missing AST queries

static bool recordDeclCanPassInRegisters() { return false; }
static bool funcDeclIsCXXConstructorDecl() { return false; }
static bool funcDeclIsCXXDestructorDecl() { return false; }
static bool funcDeclIsCXXMethodDecl() { return false; }
static bool funcDeclIsInlineBuiltinDeclaration() { return false; }
static bool funcDeclIsReplaceableGlobalAllocationFunction() { return false; }
static bool qualTypeIsReferenceType() { return false; }

//-- Missing types

static bool vectorType() { return false; }

//-- Missing LLVM attributes

static bool noReturn() { return false; }
static bool csmeCall() { return false; }

//-- Other missing features

// Calls with a static chain pointer argument may be optimized (p.e. freeing
Expand Down
8 changes: 5 additions & 3 deletions clang/lib/CIR/Dialect/Transforms/CallConvLowering.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,11 @@ struct CallConvLoweringPattern : public OpRewritePattern<FuncOp> {
}
}

// Rewrite function definition.
// FIXME(cir): This is a workaround to avoid an infinite loop in the driver.
rewriter.replaceOp(op, rewriter.clone(*op));
// TODO(cir): Instead of re-emmiting every load and store, bitcast arguments
// and return values to their ABI-specific counterparts when possible.
if (lowerModule.rewriteFunctionDefinition(op).failed())
return failure();

return success();
}
};
Expand Down
166 changes: 166 additions & 0 deletions clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerCall.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
#include "LowerCall.h"
#include "CIRToCIRArgMapping.h"
#include "LowerFunctionInfo.h"
#include "LowerModule.h"
#include "LowerTypes.h"
#include "clang/CIR/FnInfoOpts.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/Support/ErrorHandling.h"

using namespace mlir;
using namespace mlir::cir;

using ABIArgInfo = ::cir::ABIArgInfo;
using FnInfoOpts = ::cir::FnInfoOpts;
using MissingFeatures = ::cir::MissingFeatures;

namespace {

Expand Down Expand Up @@ -44,8 +50,150 @@ arrangeFreeFunctionLikeCall(LowerTypes &LT, LowerModule &LM,
fnType.getInputs(), required);
}

/// Adds the formal parameters in FPT to the given prefix. If any parameter in
/// FPT has pass_object_size attrs, then we'll add parameters for those, too.
static void appendParameterTypes(SmallVectorImpl<Type> &prefix, FuncType fnTy) {
// Fast path: don't touch param info if we don't need to.
if (/*!fnTy->hasExtParameterInfos()=*/true) {
prefix.append(fnTy.getInputs().begin(), fnTy.getInputs().end());
return;
}

assert(MissingFeatures::extParamInfo());
llvm_unreachable("NYI");
}

/// Arrange the LLVM function layout for a value of the given function
/// type, on top of any implicit parameters already stored.
///
/// \param CGT - Abstraction for lowering CIR types.
/// \param instanceMethod - Whether the function is an instance method.
/// \param prefix - List of implicit parameters to be prepended (e.g. 'this').
/// \param FTP - ABI-agnostic function type.
static const LowerFunctionInfo &
arrangeCIRFunctionInfo(LowerTypes &CGT, bool instanceMethod,
SmallVectorImpl<mlir::Type> &prefix, FuncType fnTy) {
assert(!MissingFeatures::extParamInfo());
RequiredArgs Required = RequiredArgs::forPrototypePlus(fnTy, prefix.size());
// FIXME: Kill copy.
appendParameterTypes(prefix, fnTy);
assert(!MissingFeatures::qualifiedTypes());
Type resultType = fnTy.getReturnType();

FnInfoOpts opts =
instanceMethod ? FnInfoOpts::IsInstanceMethod : FnInfoOpts::None;
return CGT.arrangeLLVMFunctionInfo(resultType, opts, prefix, Required);
}

} // namespace

/// Update function with ABI-specific attributes.
///
/// NOTE(cir): Partially copies CodeGenModule::ConstructAttributeList, but
/// focuses on ABI/Target-related attributes.
void LowerModule::constructAttributeList(StringRef Name,
const LowerFunctionInfo &FI,
FuncOp CalleeInfo, FuncOp newFn,
unsigned &CallingConv,
bool AttrOnCallSite, bool IsThunk) {
// Collect function IR attributes from the CC lowering.
// We'll collect the paramete and result attributes later.
// FIXME(cir): Codegen differentiates between CallConv and EffectiveCallConv,
// but I don't think we need to do this here.
CallingConv = FI.getCallingConvention();
// FIXME(cir): No-return should probably be set in CIRGen (ABI-agnostic).
if (MissingFeatures::noReturn())
llvm_unreachable("NYI");
if (MissingFeatures::csmeCall())
llvm_unreachable("NYI");

// TODO(cir): Implement AddAttributesFromFunctionProtoType here.
// TODO(cir): Implement AddAttributesFromOMPAssumes here.
assert(!MissingFeatures::openMP());

// TODO(cir): Skipping a bunch of AST queries here. We will need to partially
// implement some of them as this section sets target-specific attributes
// too.
// if (TargetDecl) {
// [...]
// }

// NOTE(cir): The original code adds default and no-builtin attributes here as
// well. AFAIK, these are ABI/Target-agnostic, so it would be better handled
// in CIRGen. Regardless, I'm leaving this comment here as a heads up.

// Override some default IR attributes based on declaration-specific
// information.
// NOTE(cir): Skipping another set of AST queries here.

// Collect attributes from arguments and return values.
CIRToCIRArgMapping IRFunctionArgs(getContext(), FI);

const ABIArgInfo &RetAI = FI.getReturnInfo();

// TODO(cir): No-undef attribute for return values partially depends on
// ABI-specific information. Maybe we should include it here.

switch (RetAI.getKind()) {
case ABIArgInfo::Ignore:
break;
default:
llvm_unreachable("Missing ABIArgInfo::Kind");
}

if (!IsThunk) {
if (MissingFeatures::qualTypeIsReferenceType()) {
llvm_unreachable("NYI");
}
}

// Attach attributes to sret.
if (MissingFeatures::sretArgs()) {
llvm_unreachable("sret is NYI");
}

// Attach attributes to inalloca arguments.
if (MissingFeatures::inallocaArgs()) {
llvm_unreachable("inalloca is NYI");
}

// Apply `nonnull`, `dereferencable(N)` and `align N` to the `this` argument,
// unless this is a thunk function.
// FIXME: fix this properly, https://reviews.llvm.org/D100388
if (MissingFeatures::funcDeclIsCXXMethodDecl() ||
MissingFeatures::inallocaArgs()) {
llvm_unreachable("`this` argument attributes are NYI");
}

unsigned ArgNo = 0;
for (LowerFunctionInfo::const_arg_iterator I = FI.arg_begin(),
E = FI.arg_end();
I != E; ++I, ++ArgNo) {
llvm_unreachable("NYI");
}
assert(ArgNo == FI.arg_size());
}

/// Arrange the argument and result information for the declaration or
/// definition of the given function.
const LowerFunctionInfo &LowerTypes::arrangeFunctionDeclaration(FuncOp fnOp) {
if (MissingFeatures::funcDeclIsCXXMethodDecl())
llvm_unreachable("NYI");

assert(!MissingFeatures::qualifiedTypes());
FuncType FTy = fnOp.getFunctionType();

assert(!MissingFeatures::CUDA());

// When declaring a function without a prototype, always use a
// non-variadic type.
if (fnOp.getNoProto()) {
llvm_unreachable("NYI");
}

return arrangeFreeFunctionType(FTy);
}

/// Figure out the rules for calling a function with the given formal
/// type using the given arguments. The arguments are necessary
/// because the function might be unprototyped, in which case it's
Expand All @@ -57,6 +205,24 @@ LowerTypes::arrangeFreeFunctionCall(const OperandRange args,
chainCall);
}

/// Arrange the argument and result information for the declaration or
/// definition of the given function.
const LowerFunctionInfo &LowerTypes::arrangeFreeFunctionType(FuncType FTy) {
SmallVector<mlir::Type, 16> argTypes;
return ::arrangeCIRFunctionInfo(*this, /*instanceMethod=*/false, argTypes,
FTy);
}

/// Arrange the argument and result information for the declaration or
/// definition of the given function.
const LowerFunctionInfo &LowerTypes::arrangeGlobalDeclaration(FuncOp fnOp) {
if (MissingFeatures::funcDeclIsCXXConstructorDecl() ||
MissingFeatures::funcDeclIsCXXDestructorDecl())
llvm_unreachable("NYI");

return arrangeFunctionDeclaration(fnOp);
}

/// Arrange the argument and result information for an abstract value
/// of a given function type. This is the method which all of the
/// above functions ultimately defer to.
Expand Down
106 changes: 106 additions & 0 deletions clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include "clang/CIR/MissingFeatures.h"
#include "llvm/Support/ErrorHandling.h"

using ABIArgInfo = ::cir::ABIArgInfo;

namespace mlir {
namespace cir {

Expand All @@ -37,6 +39,110 @@ LowerFunction::LowerFunction(LowerModule &LM, PatternRewriter &rewriter,
: Target(LM.getTarget()), rewriter(rewriter), SrcFn(srcFn), callOp(callOp),
LM(LM) {}

/// This method has partial parity with CodeGenFunction::EmitFunctionProlog from
/// the original codegen. However, it focuses on the ABI-specific details. On
/// top of that, it is also responsible for rewriting the original function.
LogicalResult
LowerFunction::buildFunctionProlog(const LowerFunctionInfo &FI, FuncOp Fn,
MutableArrayRef<BlockArgument> Args) {
// NOTE(cir): Skipping naked and implicit-return-zero functions here. These
// are dealt with in CIRGen.

CIRToCIRArgMapping IRFunctionArgs(LM.getContext(), FI);
assert(Fn.getNumArguments() == IRFunctionArgs.totalIRArgs());

// If we're using inalloca, all the memory arguments are GEPs off of the last
// parameter, which is a pointer to the complete memory area.
assert(!::cir::MissingFeatures::inallocaArgs());

// Name the struct return parameter.
assert(!::cir::MissingFeatures::sretArgs());

// Track if we received the parameter as a pointer (indirect, byval, or
// inalloca). If already have a pointer, EmitParmDecl doesn't need to copy it
// into a local alloca for us.
SmallVector<Value, 8> ArgVals;
ArgVals.reserve(Args.size());

// Create a pointer value for every parameter declaration. This usually
// entails copying one or more LLVM IR arguments into an alloca. Don't push
// any cleanups or do anything that might unwind. We do that separately, so
// we can push the cleanups in the correct order for the ABI.
assert(FI.arg_size() == Args.size());
unsigned ArgNo = 0;
LowerFunctionInfo::const_arg_iterator info_it = FI.arg_begin();
for (MutableArrayRef<BlockArgument>::const_iterator i = Args.begin(),
e = Args.end();
i != e; ++i, ++info_it, ++ArgNo) {
llvm_unreachable("NYI");
}

if (getTarget().getCXXABI().areArgsDestroyedLeftToRightInCallee()) {
llvm_unreachable("NYI");
} else {
// FIXME(cir): In the original codegen, EmitParamDecl is called here. It is
// likely that said function considers ABI details during emission, so we
// migth have to add a counter part here. Currently, it is not needed.
}

return success();
}

LogicalResult LowerFunction::buildFunctionEpilog(const LowerFunctionInfo &FI) {
const ABIArgInfo &RetAI = FI.getReturnInfo();

switch (RetAI.getKind()) {

case ABIArgInfo::Ignore:
break;

default:
llvm_unreachable("Unhandled ABIArgInfo::Kind");
}

return success();
}

/// Generate code for a function based on the ABI-specific information.
///
/// This method has partial parity with CodeGenFunction::GenerateCode, but it
/// focuses on the ABI-specific details. So a lot of codegen stuff is removed.
LogicalResult LowerFunction::generateCode(FuncOp oldFn, FuncOp newFn,
const LowerFunctionInfo &FnInfo) {
assert(newFn && "generating code for null Function");
auto Args = oldFn.getArguments();

// Emit the ABI-specific function prologue.
assert(newFn.empty() && "Function already has a body");
rewriter.setInsertionPointToEnd(newFn.addEntryBlock());
if (buildFunctionProlog(FnInfo, newFn, oldFn.getArguments()).failed())
return failure();

// Ensure that old ABI-agnostic arguments uses were replaced.
const auto hasNoUses = [](Value val) { return val.getUses().empty(); };
assert(std::all_of(Args.begin(), Args.end(), hasNoUses) && "Missing RAUW?");

// Migrate function body to new ABI-aware function.
assert(oldFn.getBody().hasOneBlock() &&
"Multiple blocks in original function not supported");

// Move old function body to new function.
// FIXME(cir): The merge below is not very good: will not work if SrcFn has
// multiple blocks and it mixes the new and old prologues.
rewriter.mergeBlocks(&oldFn.getBody().front(), &newFn.getBody().front(),
newFn.getArguments());

// FIXME(cir): What about saving parameters for corotines? Should we do
// something about it in this pass? If the change with the calling
// convention, we might have to handle this here.

// Emit the standard function epilogue.
if (buildFunctionEpilog(FnInfo).failed())
return failure();

return success();
}

/// Rewrite a call operation to abide to the ABI calling convention.
///
/// FIXME(cir): This method has partial parity to CodeGenFunction's
Expand Down
15 changes: 15 additions & 0 deletions clang/lib/CIR/Dialect/Transforms/TargetLowering/LowerFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,21 @@ class LowerFunction {

LowerModule &LM; // Per-module state.

const clang::TargetInfo &getTarget() const { return Target; }

// Build ABI/Target-specific function prologue.
LogicalResult buildFunctionProlog(const LowerFunctionInfo &FI, FuncOp Fn,
MutableArrayRef<BlockArgument> Args);

// Build ABI/Target-specific function epilogue.
LogicalResult buildFunctionEpilog(const LowerFunctionInfo &FI);

// Parity with CodeGenFunction::GenerateCode. Keep in mind that several
// sections in the original function are focused on codegen unrelated to the
// ABI. Such sections are handled in CIR's codegen, not here.
LogicalResult generateCode(FuncOp oldFn, FuncOp newFn,
const LowerFunctionInfo &FnInfo);

/// Rewrite a call operation to abide to the ABI calling convention.
LogicalResult rewriteCallOp(CallOp op,
ReturnValueSlot retValSlot = ReturnValueSlot());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@ class RequiredArgs {
RequiredArgs(All_t _) : NumRequired(~0U) {}
explicit RequiredArgs(unsigned n) : NumRequired(n) { assert(n != ~0U); }

/// Compute the arguments required by the given formal prototype,
/// given that there may be some additional, non-formal arguments
/// in play.
///
/// If FD is not null, this will consider pass_object_size params in FD.
static RequiredArgs forPrototypePlus(const FuncType prototype,
unsigned additional) {
if (!prototype.isVarArg())
return All;

llvm_unreachable("Variadic function is NYI");
}

bool allowsOptionalArgs() const { return NumRequired != ~0U; }
};

Expand Down
Loading

0 comments on commit 901f532

Please sign in to comment.