[clang] [CIR] Upstream cir.call with scalar arguments (PR #136810)
via cfe-commits
cfe-commits at lists.llvm.org
Tue Apr 22 21:19:13 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Sirui Mu (Lancern)
<details>
<summary>Changes</summary>
This PR upstreams support for scalar arguments in `cir.call` operation.
Related to #<!-- -->132487 .
---
Patch is 40.42 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/136810.diff
16 Files Affected:
- (modified) clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h (+5-4)
- (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+44-2)
- (modified) clang/include/clang/CIR/Interfaces/CIROpInterfaces.td (+16-1)
- (modified) clang/include/clang/CIR/MissingFeatures.h (+11-1)
- (modified) clang/lib/CIR/CodeGen/CIRGenCall.cpp (+265-12)
- (modified) clang/lib/CIR/CodeGen/CIRGenCall.h (+30-2)
- (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+20-4)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+47-1)
- (modified) clang/lib/CIR/CodeGen/CIRGenFunctionInfo.h (+25-2)
- (modified) clang/lib/CIR/CodeGen/CIRGenTypes.cpp (+10-5)
- (modified) clang/lib/CIR/CodeGen/CIRGenTypes.h (+5-2)
- (modified) clang/lib/CIR/CodeGen/TargetInfo.cpp (+7-1)
- (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+49-9)
- (modified) clang/test/CIR/CodeGen/call.cpp (+12)
- (modified) clang/test/CIR/IR/call.cir (+15)
- (modified) clang/test/CIR/IR/invalid-call.cir (+27)
``````````diff
diff --git a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
index 539268c6270f4..0a6e47ea43a8c 100644
--- a/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
+++ b/clang/include/clang/CIR/Dialect/Builder/CIRBaseBuilder.h
@@ -214,14 +214,15 @@ class CIRBaseBuilderTy : public mlir::OpBuilder {
//===--------------------------------------------------------------------===//
cir::CallOp createCallOp(mlir::Location loc, mlir::SymbolRefAttr callee,
- mlir::Type returnType) {
- auto op = create<cir::CallOp>(loc, callee, returnType);
+ mlir::Type returnType, mlir::ValueRange operands) {
+ auto op = create<cir::CallOp>(loc, callee, returnType, operands);
return op;
}
- cir::CallOp createCallOp(mlir::Location loc, cir::FuncOp callee) {
+ cir::CallOp createCallOp(mlir::Location loc, cir::FuncOp callee,
+ mlir::ValueRange operands) {
return createCallOp(loc, mlir::SymbolRefAttr::get(callee),
- callee.getFunctionType().getReturnType());
+ callee.getFunctionType().getReturnType(), operands);
}
//===--------------------------------------------------------------------===//
diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td
index bb19de31b4fa5..aa7a9b2de664f 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIROps.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td
@@ -1496,6 +1496,10 @@ def FuncOp : CIR_Op<"func", [
return getFunctionType().getReturnTypes();
}
+ // TODO(cir): this should be an operand attribute, but for now we just hard-
+ // wire this as a function. Will later add a $no_proto argument to this op.
+ bool getNoProto() { return false; }
+
//===------------------------------------------------------------------===//
// SymbolOpInterface Methods
//===------------------------------------------------------------------===//
@@ -1516,6 +1520,41 @@ class CIR_CallOpBase<string mnemonic, list<Trait> extra_traits = []>
!listconcat(extra_traits,
[DeclareOpInterfaceMethods<CIRCallOpInterface>,
DeclareOpInterfaceMethods<SymbolUserOpInterface>])> {
+ let extraClassDeclaration = [{
+ /// Get the argument operands to the called function.
+ mlir::OperandRange getArgOperands() {
+ return {arg_operand_begin(), arg_operand_end()};
+ }
+
+ mlir::MutableOperandRange getArgOperandsMutable() {
+ llvm_unreachable("NYI");
+ }
+
+ /// Return the callee of this operation
+ mlir::CallInterfaceCallable getCallableForCallee() {
+ return (*this)->getAttrOfType<mlir::SymbolRefAttr>("callee");
+ }
+
+ /// Set the callee for this operation.
+ void setCalleeFromCallable(::mlir::CallInterfaceCallable callee) {
+ (*this)->setAttr(getCalleeAttrName(),
+ mlir::cast<mlir::SymbolRefAttr>(callee));
+ }
+
+ ::mlir::ArrayAttr getArgAttrsAttr() { return {}; }
+ ::mlir::ArrayAttr getResAttrsAttr() { return {}; }
+
+ void setResAttrsAttr(::mlir::ArrayAttr attrs) {}
+ void setArgAttrsAttr(::mlir::ArrayAttr attrs) {}
+
+ ::mlir::Attribute removeArgAttrsAttr() { return {}; }
+ ::mlir::Attribute removeResAttrsAttr() { return {}; }
+
+ void setArg(unsigned index, mlir::Value value) {
+ setOperand(index, value);
+ }
+ }];
+
let hasCustomAssemblyFormat = 1;
let skipDefaultBuilders = 1;
let hasVerifier = 0;
@@ -1525,7 +1564,8 @@ class CIR_CallOpBase<string mnemonic, list<Trait> extra_traits = []>
// the upstreaming process moves on. The verifiers is also missing for now,
// will add in the future.
- dag commonArgs = (ins FlatSymbolRefAttr:$callee);
+ dag commonArgs = (ins FlatSymbolRefAttr:$callee,
+ Variadic<CIR_AnyType>:$args);
}
def CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
@@ -1546,7 +1586,9 @@ def CallOp : CIR_CallOpBase<"call", [NoRegionArguments]> {
let arguments = commonArgs;
let builders = [OpBuilder<(ins "mlir::SymbolRefAttr":$callee,
- "mlir::Type":$resType), [{
+ "mlir::Type":$resType,
+ "mlir::ValueRange":$operands), [{
+ $_state.addOperands(operands);
$_state.addAttribute("callee", callee);
if (resType && !isa<VoidType>(resType))
$_state.addTypes(resType);
diff --git a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
index c6c6356118ac6..8227ce4bea5a3 100644
--- a/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
+++ b/clang/include/clang/CIR/Interfaces/CIROpInterfaces.td
@@ -21,9 +21,24 @@ let cppNamespace = "::cir" in {
// The CIRCallOpInterface must be used instead of CallOpInterface when looking
// at arguments and other bits of CallOp. This creates a level of abstraction
// that's useful for handling indirect calls and other details.
- def CIRCallOpInterface : OpInterface<"CIRCallOpInterface", []> {
+ def CIRCallOpInterface : OpInterface<"CIRCallOpInterface", [CallOpInterface]> {
// Currently we don't have any methods defined in CIRCallOpInterface. We'll
// add more methods as the upstreaming proceeds.
+ let methods = [
+ InterfaceMethod<"", "mlir::Operation::operand_iterator",
+ "arg_operand_begin", (ins)>,
+ InterfaceMethod<"", "mlir::Operation::operand_iterator",
+ "arg_operand_end", (ins)>,
+ InterfaceMethod<
+ "Return the operand at index 'i', accounts for indirect call or "
+ "exception info",
+ "mlir::Value", "getArgOperand",
+ (ins "unsigned":$i)>,
+ InterfaceMethod<
+ "Return the number of operands, accounts for indirect call or "
+ "exception info",
+ "unsigned", "getNumArgOperands", (ins)>,
+ ];
}
def CIRGlobalValueInterface
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index 6bfc1199aea55..370d82d26ebe7 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -76,7 +76,13 @@ struct MissingFeatures {
// CallOp handling
static bool opCallBuiltinFunc() { return false; }
static bool opCallPseudoDtor() { return false; }
- static bool opCallArgs() { return false; }
+ static bool opCallAggregateArgs() { return false; }
+ static bool opCallPaddingArgs() { return false; }
+ static bool opCallABIExtendArg() { return false; }
+ static bool opCallABIIndirectArg() { return false; }
+ static bool opCallWidenArg() { return false; }
+ static bool opCallBitcastArg() { return false; }
+ static bool opCallImplicitObjectSizeArgs() { return false; }
static bool opCallReturn() { return false; }
static bool opCallArgEvaluationOrder() { return false; }
static bool opCallCallConv() { return false; }
@@ -90,6 +96,9 @@ struct MissingFeatures {
static bool opCallAttrs() { return false; }
static bool opCallSurroundingTry() { return false; }
static bool opCallASTAttr() { return false; }
+ static bool opCallVariadic() { return false; }
+ static bool opCallObjCMethod() { return false; }
+ static bool opCallExtParameterInfo() { return false; }
// ScopeOp handling
static bool opScopeCleanupRegion() { return false; }
@@ -157,6 +166,7 @@ struct MissingFeatures {
static bool emitCheckedInBoundsGEP() { return false; }
static bool preservedAccessIndexRegion() { return false; }
static bool bitfields() { return false; }
+ static bool msabi() { return false; }
static bool typeChecks() { return false; }
static bool lambdaFieldToName() { return false; }
diff --git a/clang/lib/CIR/CodeGen/CIRGenCall.cpp b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
index 69266f79a88a5..bea91f8ec0ec7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenCall.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenCall.cpp
@@ -18,15 +18,110 @@
using namespace clang;
using namespace clang::CIRGen;
-CIRGenFunctionInfo *CIRGenFunctionInfo::create(CanQualType resultType) {
- void *buffer = operator new(totalSizeToAlloc<ArgInfo>(1));
+CIRGenFunctionInfo *
+CIRGenFunctionInfo::create(CanQualType resultType,
+ llvm::ArrayRef<CanQualType> argTypes) {
+ void *buffer = operator new(totalSizeToAlloc<ArgInfo>(argTypes.size() + 1));
CIRGenFunctionInfo *fi = new (buffer) CIRGenFunctionInfo();
+ fi->numArgs = argTypes.size();
fi->getArgsBuffer()[0].type = resultType;
+ for (unsigned i = 0; i < argTypes.size(); ++i)
+ fi->getArgsBuffer()[i + 1].type = argTypes[i];
return fi;
}
+namespace {
+
+/// Encapsulates information about the way function arguments from
+/// CIRGenFunctionInfo should be passed to actual CIR function.
+class ClangToCIRArgMapping {
+ static constexpr unsigned invalidIndex = ~0U;
+ unsigned totalNumCIRArgs;
+
+ /// Arguments of CIR function corresponding to single Clang argument.
+ struct CIRArgs {
+ // Argument is expanded to CIR arguments at positions
+ // [FirstArgIndex, FirstArgIndex + NumberOfArgs).
+ unsigned firstArgIndex = 0;
+ unsigned numberOfArgs = 0;
+
+ CIRArgs() : firstArgIndex(invalidIndex), numberOfArgs(0) {}
+ };
+
+ SmallVector<CIRArgs, 8> argInfo;
+
+public:
+ ClangToCIRArgMapping(const ASTContext &astContext,
+ const CIRGenFunctionInfo &funcInfo)
+ : totalNumCIRArgs(0), argInfo(funcInfo.arg_size()) {
+ construct(astContext, funcInfo);
+ }
+
+ unsigned totalCIRArgs() const { return totalNumCIRArgs; }
+
+ /// Returns index of first CIR argument corresponding to argNo, and their
+ /// quantity.
+ std::pair<unsigned, unsigned> getCIRArgs(unsigned argNo) const {
+ assert(argNo < argInfo.size());
+ return std::make_pair(argInfo[argNo].firstArgIndex,
+ argInfo[argNo].numberOfArgs);
+ }
+
+private:
+ void construct(const ASTContext &astContext,
+ const CIRGenFunctionInfo &funcInfo);
+};
+
+void ClangToCIRArgMapping::construct(const ASTContext &astContext,
+ const CIRGenFunctionInfo &funcInfo) {
+ unsigned cirArgNo = 0;
+
+ assert(!cir::MissingFeatures::opCallABIIndirectArg());
+
+ unsigned argNo = 0;
+ unsigned numArgs = funcInfo.arg_size();
+ for (const auto *i = funcInfo.arg_begin(); argNo < numArgs; ++i, ++argNo) {
+ assert(i != funcInfo.arg_end());
+ const cir::ABIArgInfo &ai = i->info;
+ // Collect data about CIR arguments corresponding to Clang argument ArgNo.
+ auto &cirArgs = argInfo[argNo];
+
+ assert(!cir::MissingFeatures::opCallPaddingArgs());
+
+ switch (ai.getKind()) {
+ default:
+ assert(!cir::MissingFeatures::abiArgInfo());
+ // For now we just fall through. More argument kinds will be added later
+ // as the upstreaming proceeds.
+ [[fallthrough]];
+ case cir::ABIArgInfo::Direct:
+ // Postpone splitting structs into elements since this makes it way
+ // more complicated for analysis to obtain information on the original
+ // arguments.
+ //
+ // TODO(cir): a LLVM lowering prepare pass should break this down into
+ // the appropriated pieces.
+ assert(!cir::MissingFeatures::opCallABIExtendArg());
+ cirArgs.numberOfArgs = 1;
+ break;
+ }
+
+ if (cirArgs.numberOfArgs > 0) {
+ cirArgs.firstArgIndex = cirArgNo;
+ cirArgNo += cirArgs.numberOfArgs;
+ }
+ }
+
+ assert(argNo == argInfo.size());
+ assert(!cir::MissingFeatures::opCallInAlloca());
+
+ totalNumCIRArgs = cirArgNo;
+}
+
+} // namespace
+
CIRGenCallee CIRGenCallee::prepareConcreteCallee(CIRGenFunction &cgf) const {
assert(!cir::MissingFeatures::opCallVirtual());
return *this;
@@ -34,6 +129,7 @@ CIRGenCallee CIRGenCallee::prepareConcreteCallee(CIRGenFunction &cgf) const {
static const CIRGenFunctionInfo &
arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule &cgm,
+ const CallArgList &args,
const FunctionType *fnType) {
if (const auto *proto = dyn_cast<FunctionProtoType>(fnType)) {
if (proto->isVariadic())
@@ -44,22 +140,26 @@ arrangeFreeFunctionLikeCall(CIRGenTypes &cgt, CIRGenModule &cgm,
cast<FunctionNoProtoType>(fnType)))
cgm.errorNYI("call to function without a prototype");
- assert(!cir::MissingFeatures::opCallArgs());
+ SmallVector<CanQualType, 16> argTypes;
+ for (const CallArg &arg : args)
+ argTypes.push_back(cgt.getASTContext().getCanonicalParamType(arg.ty));
CanQualType retType = fnType->getReturnType()
->getCanonicalTypeUnqualified()
.getUnqualifiedType();
- return cgt.arrangeCIRFunctionInfo(retType);
+ return cgt.arrangeCIRFunctionInfo(retType, argTypes);
}
const CIRGenFunctionInfo &
-CIRGenTypes::arrangeFreeFunctionCall(const FunctionType *fnType) {
- return arrangeFreeFunctionLikeCall(*this, cgm, fnType);
+CIRGenTypes::arrangeFreeFunctionCall(const CallArgList &args,
+ const FunctionType *fnType) {
+ return arrangeFreeFunctionLikeCall(*this, cgm, args, fnType);
}
-static cir::CIRCallOpInterface emitCallLikeOp(CIRGenFunction &cgf,
- mlir::Location callLoc,
- cir::FuncOp directFuncOp) {
+static cir::CIRCallOpInterface
+emitCallLikeOp(CIRGenFunction &cgf, mlir::Location callLoc,
+ cir::FuncOp directFuncOp,
+ const SmallVectorImpl<mlir::Value> &cirCallArgs) {
CIRGenBuilderTy &builder = cgf.getBuilder();
assert(!cir::MissingFeatures::opCallSurroundingTry());
@@ -68,20 +168,70 @@ static cir::CIRCallOpInterface emitCallLikeOp(CIRGenFunction &cgf,
assert(builder.getInsertionBlock() && "expected valid basic block");
assert(!cir::MissingFeatures::opCallIndirect());
- return builder.createCallOp(callLoc, directFuncOp);
+ return builder.createCallOp(callLoc, directFuncOp, cirCallArgs);
}
RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
const CIRGenCallee &callee,
ReturnValueSlot returnValue,
+ const CallArgList &args,
cir::CIRCallOpInterface *callOp,
mlir::Location loc) {
QualType retTy = funcInfo.getReturnType();
const cir::ABIArgInfo &retInfo = funcInfo.getReturnInfo();
- assert(!cir::MissingFeatures::opCallArgs());
+ ClangToCIRArgMapping cirFuncArgs(cgm.getASTContext(), funcInfo);
+ SmallVector<mlir::Value, 16> cirCallArgs(cirFuncArgs.totalCIRArgs());
+
assert(!cir::MissingFeatures::emitLifetimeMarkers());
+ // Translate all of the arguments as necessary to match the CIR lowering.
+ assert(funcInfo.arg_size() == args.size() &&
+ "Mismatch between function signature & arguments.");
+ unsigned argNo = 0;
+ const auto *infoIter = funcInfo.arg_begin();
+ for (auto i = args.begin(), e = args.end(); i != e;
+ ++i, ++infoIter, ++argNo) {
+ const cir::ABIArgInfo &argInfo = infoIter->info;
+
+ // Insert a padding argument to ensure proper alignment.
+ assert(!cir::MissingFeatures::opCallPaddingArgs());
+
+ unsigned firstCIRArg;
+ unsigned numCIRArgs;
+ std::tie(firstCIRArg, numCIRArgs) = cirFuncArgs.getCIRArgs(argNo);
+
+ switch (argInfo.getKind()) {
+ case cir::ABIArgInfo::Direct: {
+ if (!mlir::isa<cir::RecordType>(argInfo.getCoerceToType()) &&
+ argInfo.getCoerceToType() == convertType(infoIter->type) &&
+ argInfo.getDirectOffset() == 0) {
+ assert(numCIRArgs == 1);
+ assert(!cir::MissingFeatures::opCallAggregateArgs());
+ mlir::Value v = i->getKnownRValue().getScalarVal();
+
+ assert(!cir::MissingFeatures::opCallExtParameterInfo());
+
+ // We might have to widen integers, but we should never truncate.
+ assert(!cir::MissingFeatures::opCallWidenArg());
+
+ // If the argument doesn't match, perform a bitcast to coerce it. This
+ // can happen due to trivial type mismatches.
+ assert(!cir::MissingFeatures::opCallBitcastArg());
+
+ cirCallArgs[firstCIRArg] = v;
+ break;
+ }
+
+ assert(!cir::MissingFeatures::opCallAggregateArgs());
+ cgm.errorNYI("aggregate function call argument");
+ break;
+ }
+ default:
+ cgm.errorNYI("unsupported argument kind");
+ }
+ }
+
const CIRGenCallee &concreteCallee = callee.prepareConcreteCallee(*this);
mlir::Operation *calleePtr = concreteCallee.getFunctionPointer();
@@ -102,7 +252,8 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
assert(!cir::MissingFeatures::opCallIndirect());
assert(!cir::MissingFeatures::opCallAttrs());
- cir::CIRCallOpInterface theCall = emitCallLikeOp(*this, loc, directFuncOp);
+ cir::CIRCallOpInterface theCall =
+ emitCallLikeOp(*this, loc, directFuncOp, cirCallArgs);
if (callOp)
*callOp = theCall;
@@ -152,3 +303,105 @@ RValue CIRGenFunction::emitCall(const CIRGenFunctionInfo &funcInfo,
return ret;
}
+
+void CIRGenFunction::emitCallArg(CallArgList &args, const clang::Expr *e,
+ clang::QualType argType) {
+ assert(argType->isReferenceType() == e->isGLValue() &&
+ "reference binding to unmaterialized r-value!");
+
+ if (e->isGLValue()) {
+ assert(e->getObjectKind() == OK_Ordinary);
+ args.add(emitReferenceBindingToExpr(e), argType);
+ }
+
+ bool hasAggregateEvalKind = hasAggregateEvaluationKind(argType);
+
+ if (hasAggregateEvalKind) {
+ assert(!cir::MissingFeatures::opCallAggregateArgs());
+ cgm.errorNYI(e->getSourceRange(), "aggregate function call argument");
+ }
+
+ args.add(emitAnyExprToTemp(e), argType);
+}
+
+/// 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());
+
+ if (hasAggregateEvaluationKind(e->getType()))
+ cgm.errorNYI(e->getSourceRange(), "emit aggregate value to temp");
+
+ return emitAnyExpr(e);
+}
+
+void CIRGenFunction::emitCallArgs(
+ CallArgList &args, PrototypeWrapper prototype,
+ llvm::iterator_range<clang::CallExpr::const_arg_iterator> argRange,
+ AbstractCallee callee, unsigned paramsToSkip) {
+ llvm::SmallVector<QualType, 16> argTypes;
+
+ assert(!cir::MissingFeatures::opCallCallConv());
+
+ // First, if a prototype was provided, use those argument types.
+ assert(!cir::MissingFeatures::opCallVariadic());
+ if (prototype.p) {
+ assert(!cir::MissingFeatures::opCallObjCMethod());
+
+ const auto *fpt = cast<const FunctionProtoType *>(prototype.p);
+ argTypes.assign(fpt->param_type_begin() + paramsToSkip,
+ fpt->param_type_end());
+ }
+
+ // If we still have any arguments, emit them using the type of the argument.
+ for (auto *a : llvm::drop_begin(argRange, argTypes.size()))
+ argTypes.push_back(a->getType());
+ assert(argTypes.size() == (size_t)(argRange.end() - argRange.begin()));
+
+ // We must evaluate arguments from right to left in the MS C++ ABI, because
+ // arguments are destroyed left to right in the callee. As a special case,
+ // there are certain language constructs taht require left-to-right
+ // evaluation, and in those cases we consider the evaluation order requirement
+ // to trump the "destruction order is reverse construction order" guarantee.
+ auto leftToRight = true;
+ assert(!cir::MissingFeatures::msabi());
+
+ auto maybeEmitImplicitObjectSize = [&](size_t i, const Expr *arg,
+ RValue emittedArg) {
+ if (callee.hasFunctionDecl() || i >= callee.getNumParams())
+ return;
+ auto *ps = callee.getParamDecl(i)->getAttr<PassObjectSizeAttr>();
+ if (!ps)
+ return;
+
+ assert(!cir::MissingFeatures::opCallImplicitObjectSizeArgs());
+ cgm.errorNYI("emit implicit object size for call arg");
+ };
+
+ // Evaluate each argument in the appropriate order.
+ size_t callArgsStart = args.size();
+ for (size_t i = 0; i != argTypes.size(); ++i) {
+...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/136810
More information about the cfe-commits
mailing list