llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang @llvm/pr-subscribers-clang-codegen Author: Mariya Podchishchaeva (Fznamznon) <details> <summary>Changes</summary> --- Patch is 27.51 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/140230.diff 13 Files Affected: - (modified) clang/include/clang/Basic/Builtins.h (+1) - (modified) clang/include/clang/Basic/Builtins.td (+7) - (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+6) - (modified) clang/lib/AST/ExprConstant.cpp (+38) - (modified) clang/lib/Basic/Builtins.cpp (+3) - (modified) clang/lib/CodeGen/CGBuiltin.cpp (+77) - (modified) clang/lib/CodeGen/CodeGenModule.cpp (+7-3) - (modified) clang/lib/CodeGen/CodeGenModule.h (+11) - (modified) clang/lib/CodeGen/CodeGenSYCL.cpp (+53-6) - (modified) clang/lib/Sema/SemaChecking.cpp (+36) - (added) clang/test/CodeGenSYCL/builtin-sycl-kernel-name.cpp (+102) - (added) clang/test/SemaSYCL/builtin-sycl-kernel-name.cpp (+59) - (added) clang/test/SemaSYCL/builtin-sycl-kernel-param-count.cpp (+44) ``````````diff diff --git a/clang/include/clang/Basic/Builtins.h b/clang/include/clang/Basic/Builtins.h index 3a5e31de2bc50..6d8914d7074d4 100644 --- a/clang/include/clang/Basic/Builtins.h +++ b/clang/include/clang/Basic/Builtins.h @@ -45,6 +45,7 @@ enum LanguageID : uint16_t { ALL_OCL_LANGUAGES = 0x800, // builtin for OCL languages. HLSL_LANG = 0x1000, // builtin requires HLSL. C23_LANG = 0x2000, // builtin requires C23 or later. + SYCL_LANG = 0x4000, // builtin requires SYCL. ALL_LANGUAGES = C_LANG | CXX_LANG | OBJC_LANG, // builtin for all languages. ALL_GNU_LANGUAGES = ALL_LANGUAGES | GNU_LANG, // builtin requires GNU mode. ALL_MS_LANGUAGES = ALL_LANGUAGES | MS_LANG // builtin requires MS mode. diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 11b1e247237a7..632dc8538a2a9 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -4794,6 +4794,13 @@ def GetDeviceSideMangledName : LangBuiltin<"CUDA_LANG"> { let Prototype = "char const*(...)"; } +// SYCL +def SYCLKernelName : LangBuiltin<"SYCL_LANG"> { + let Spellings = ["__builtin_sycl_kernel_name"]; + let Attributes = [NoThrow, Const, Constexpr, CustomTypeChecking]; + let Prototype = "char const*(...)"; +} + // HLSL def HLSLAddUint64: LangBuiltin<"HLSL_LANG"> { let Spellings = ["__builtin_hlsl_adduint64"]; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 6e940a318b61d..2da2045415596 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -815,6 +815,9 @@ def warn_unreachable_association : Warning< InGroup<UnreachableCodeGenericAssoc>; /// Built-in functions. +def err_builtin_invalid_argument_count : Error< + "builtin %plural{0:takes no arguments|1:takes one argument|" + ":requires exactly %0 arguments}0">; def ext_implicit_lib_function_decl : ExtWarn< "implicitly declaring library function '%0' with type %1">, InGroup<ImplicitFunctionDeclare>; @@ -12789,6 +12792,9 @@ def err_sycl_entry_point_deduced_return_type : Error< def warn_cuda_maxclusterrank_sm_90 : Warning< "maxclusterrank requires sm_90 or higher, CUDA arch provided: %0, ignoring " "%1 attribute">, InGroup<IgnoredAttributes>; +def err_sycl_kernel_name_invalid_arg : Error<"invalid argument; expected a class " + "or structure with a member typedef " + "or type alias alias named 'type'">; // VTable pointer authentication errors def err_non_polymorphic_vtable_pointer_auth : Error< diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 86dbb349fd1a7..5be35c93f0aa1 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -52,6 +52,7 @@ #include "clang/Basic/Builtins.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/TargetBuiltins.h" +#include "clang/Basic/IdentifierTable.h" #include "clang/Basic/TargetInfo.h" #include "llvm/ADT/APFixedPoint.h" #include "llvm/ADT/Sequence.h" @@ -9918,6 +9919,26 @@ static bool isOneByteCharacterType(QualType T) { return T->isCharType() || T->isChar8Type(); } +static const SYCLKernelInfo *GetSYCLKernelInfo(ASTContext &Ctx, + const CallExpr *E) { + // Argument to the builtin is a type trait which is used to retrieve the + // kernel name type. + // FIXME: Improve the comment. + const Expr *NameExpr = E->getArg(0); + // FIXME: Implement diagnostic instead of assert. + assert(NameExpr->isEvaluatable(Ctx) && + "KernelNameType should be evaluatable"); + RecordDecl *RD = NameExpr->getType()->castAs<RecordType>()->getDecl(); + IdentifierTable &IdentTable = Ctx.Idents; + auto Name = DeclarationName(&(IdentTable.get("type"))); + NamedDecl *ND = (RD->lookup(Name)).front(); + TypedefNameDecl *TD = cast<TypedefNameDecl>(ND); + CanQualType KernelNameType = Ctx.getCanonicalType(TD->getUnderlyingType()); + + // Retrieve KernelInfo using the kernel name. + return Ctx.findSYCLKernelInfo(KernelNameType); +} + bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, unsigned BuiltinOp) { if (IsOpaqueConstantCall(E)) @@ -10273,6 +10294,23 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, return false; } } + case Builtin::BI__builtin_sycl_kernel_name: { + const SYCLKernelInfo *KernelInfo = GetSYCLKernelInfo(Info.Ctx, E); + assert(KernelInfo && "Type does not correspond to a SYCL kernel name."); + // Retrieve the mangled name corresponding to kernel name type. + std::string ResultStr = KernelInfo->GetKernelName(); + APInt Size(Info.Ctx.getTypeSize(Info.Ctx.getSizeType()), + ResultStr.size() + 1); + QualType StrTy = + Info.Ctx.getConstantArrayType(Info.Ctx.CharTy.withConst(), Size, + nullptr, ArraySizeModifier::Normal, 0); + StringLiteral *SL = + StringLiteral::Create(Info.Ctx, ResultStr, StringLiteralKind::Ordinary, + /*Pascal*/ false, StrTy, SourceLocation()); + evaluateLValue(SL, Result); + Result.addArray(Info, E, cast<ConstantArrayType>(StrTy)); + return true; + } default: return false; diff --git a/clang/lib/Basic/Builtins.cpp b/clang/lib/Basic/Builtins.cpp index 885abdc152e3a..f8275656c5d0c 100644 --- a/clang/lib/Basic/Builtins.cpp +++ b/clang/lib/Basic/Builtins.cpp @@ -185,6 +185,9 @@ static bool builtinIsSupported(const llvm::StringTable &Strings, /* CUDA Unsupported */ if (!LangOpts.CUDA && BuiltinInfo.Langs == CUDA_LANG) return false; + /* SYCL Unsupported */ + if (!LangOpts.isSYCL() && BuiltinInfo.Langs == SYCL_LANG) + return false; /* CPlusPlus Unsupported */ if (!LangOpts.CPlusPlus && BuiltinInfo.Langs == CXX_LANG) return false; diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 4fdf2113cb9dc..2fbe6ae23b837 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -29,6 +29,7 @@ #include "clang/Basic/TargetBuiltins.h" #include "clang/Basic/TargetInfo.h" #include "clang/Basic/TargetOptions.h" +#include "clang/Basic/IdentifierTable.h" #include "clang/Frontend/FrontendDiagnostic.h" #include "llvm/IR/InlineAsm.h" #include "llvm/IR/Intrinsics.h" @@ -2526,6 +2527,66 @@ static RValue EmitHipStdParUnsupportedBuiltin(CodeGenFunction *CGF, return RValue::get(CGF->Builder.CreateCall(UBF, Args)); } +static const CanQualType GetKernelNameType(ASTContext &Ctx, const CallExpr *E) { + // The first argument to the builtin is an object that designates + // the SYCL kernel. The argument is evaluated and its value is + // discarded; the SYCL kernel is identified based on the argument + // type. The argument type is required to be a class or structure + // with a member typedef or type alias named 'type'. The target + // type of the 'type' member is the SYCL kernel name type. + RecordDecl *RD = E->getArg(0)->getType()->castAs<RecordType>()->getDecl(); + IdentifierTable &IdentTable = Ctx.Idents; + auto Name = DeclarationName(&(IdentTable.get("type"))); + NamedDecl *ND = (RD->lookup(Name)).front(); + TypedefNameDecl *TD = cast<TypedefNameDecl>(ND); + return Ctx.getCanonicalType(TD->getUnderlyingType()); +} + +static llvm::GlobalVariable * +EmitKernelNameGlobal(CodeGenModule &CGM, ASTContext &Ctx, const CallExpr *E) { + CanQualType KernelNameType = GetKernelNameType(Ctx, E); + + // SmallString<256> KernelNameSymbol; + // llvm::raw_svector_ostream Out(KernelNameSymbol); + // The mangling used for the name of the global variable storing the offload + // kernel name is identical to the mangling of the offload kernel name. + // CGM.getCXXABI().getMangleContext().mangleSYCLKernelCallerName(KernelNameType, + // Out); + // + + auto DeviceDiscriminatorOverrider = + [](ASTContext &Ctx, const NamedDecl *ND) -> UnsignedOrNone { + if (const auto *RD = dyn_cast<CXXRecordDecl>(ND)) + if (RD->isLambda()) + return RD->getDeviceLambdaManglingNumber(); + return std::nullopt; + }; + std::unique_ptr<MangleContext> MC{ItaniumMangleContext::create( + Ctx, Ctx.getDiagnostics(), DeviceDiscriminatorOverrider)}; + + SmallString<256> KernelNameSymbol; + llvm::raw_svector_ostream Out(KernelNameSymbol); + //llvm::raw_string_ostream Out(KernelNameSymbol); + MC->mangleCanonicalTypeName(KernelNameType, Out); + + llvm::GlobalVariable *GV = new llvm::GlobalVariable( + CGM.getModule(), CGM.GlobalsInt8PtrTy, + /*isConstant=*/true, llvm::GlobalValue::ExternalLinkage, nullptr, + KernelNameSymbol); + + CGM.AddSYCLKernelNameSymbol(KernelNameType, GV); + + return GV; +} + +static const SYCLKernelInfo *GetSYCLKernelInfo(ASTContext &Ctx, + const CallExpr *E) { + CanQualType KernelNameType = GetKernelNameType(Ctx, E); + + // Retrieve KernelInfo using the kernel name. + return Ctx.findSYCLKernelInfo(KernelNameType); +} + RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, const CallExpr *E, ReturnValueSlot ReturnValue) { @@ -6219,6 +6280,22 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, auto Str = CGM.GetAddrOfConstantCString(Name, ""); return RValue::get(Str.getPointer()); } + case Builtin::BI__builtin_sycl_kernel_name: { + // Retrieve the kernel info corresponding to kernel name type. + const SYCLKernelInfo *KernelInfo = GetSYCLKernelInfo(getContext(), E); + + // This indicates that the builtin was called before the kernel was + // invoked. In this case, a global variable is declared, and returned + // as the result of the call to the builtin. This global variable is + // later initialized to hold the name of the offload kernel when kernel + // invocation is processed. + if (!KernelInfo) + return RValue::get(EmitKernelNameGlobal(CGM, getContext(), E)); + + // Emit the mangled name from KernelInfo if available. + auto Str = CGM.GetAddrOfConstantCString(KernelInfo->GetKernelName(), ""); + return RValue::get(Str.getPointer()); + } } // If this is an alias for a lib function (e.g. __builtin_sin), emit diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index 50041f883cfe5..f21e09387922e 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -3319,16 +3319,20 @@ void CodeGenModule::EmitDeferred() { // a SYCL kernel caller offload entry point function is generated and // emitted in place of each of these functions. if (const auto *FD = D.getDecl()->getAsFunction()) { - if (LangOpts.SYCLIsDevice && FD->hasAttr<SYCLKernelEntryPointAttr>() && - FD->isDefined()) { + if (FD->hasAttr<SYCLKernelEntryPointAttr>() && FD->isDefined()) { // Functions with an invalid sycl_kernel_entry_point attribute are // ignored during device compilation. - if (!FD->getAttr<SYCLKernelEntryPointAttr>()->isInvalidAttr()) { + if (LangOpts.SYCLIsDevice && + !FD->getAttr<SYCLKernelEntryPointAttr>()->isInvalidAttr()) { // Generate and emit the SYCL kernel caller function. EmitSYCLKernelCaller(FD, getContext()); // Recurse to emit any symbols directly or indirectly referenced // by the SYCL kernel caller function. EmitDeferred(); + } else { + // Initialize the global variables corresponding to SYCL Builtins + // used to obtain information about the offload kernel. + InitSYCLKernelInfoSymbolsForBuiltins(FD, getContext()); } // Do not emit the sycl_kernel_entry_point attributed function. continue; diff --git a/clang/lib/CodeGen/CodeGenModule.h b/clang/lib/CodeGen/CodeGenModule.h index 1db5c3bc4e4ef..3f5f11def5648 100644 --- a/clang/lib/CodeGen/CodeGenModule.h +++ b/clang/lib/CodeGen/CodeGenModule.h @@ -677,6 +677,7 @@ class CodeGenModule : public CodeGenTypeCache { computeVTPointerAuthentication(const CXXRecordDecl *ThisClass); AtomicOptions AtomicOpts; + llvm::DenseMap<CanQualType, llvm::GlobalVariable *> SYCLKernelNameSymbols; public: CodeGenModule(ASTContext &C, IntrusiveRefCntPtr<llvm::vfs::FileSystem> FS, @@ -1505,6 +1506,10 @@ class CodeGenModule : public CodeGenTypeCache { /// annotations are emitted during finalization of the LLVM code. void AddGlobalAnnotations(const ValueDecl *D, llvm::GlobalValue *GV); + void AddSYCLKernelNameSymbol(CanQualType, llvm::GlobalVariable *); + + llvm::GlobalVariable *GetSYCLKernelNameSymbol(CanQualType); + bool isInNoSanitizeList(SanitizerMask Kind, llvm::Function *Fn, SourceLocation Loc) const; @@ -1975,6 +1980,12 @@ class CodeGenModule : public CodeGenTypeCache { void EmitSYCLKernelCaller(const FunctionDecl *KernelEntryPointFn, ASTContext &Ctx); + /// Initialize the global variables corresponding to SYCL Builtins used to + /// obtain information about the offload kernel. + void + InitSYCLKernelInfoSymbolsForBuiltins(const FunctionDecl *KernelEntryPointFn, + ASTContext &Ctx); + /// Determine whether the definition must be emitted; if this returns \c /// false, the definition can be emitted lazily if it's used. bool MustBeEmitted(const ValueDecl *D); diff --git a/clang/lib/CodeGen/CodeGenSYCL.cpp b/clang/lib/CodeGen/CodeGenSYCL.cpp index b9a96fe8ab838..9f5efd126ba08 100644 --- a/clang/lib/CodeGen/CodeGenSYCL.cpp +++ b/clang/lib/CodeGen/CodeGenSYCL.cpp @@ -25,6 +25,24 @@ static void SetSYCLKernelAttributes(llvm::Function *Fn, CodeGenFunction &CGF) { Fn->addFnAttr(llvm::Attribute::MustProgress); } +static CanQualType GetKernelNameType(const FunctionDecl *KernelEntryPointFn, + ASTContext &Ctx) { + const auto *KernelEntryPointAttr = + KernelEntryPointFn->getAttr<SYCLKernelEntryPointAttr>(); + assert(KernelEntryPointAttr && "Missing sycl_kernel_entry_point attribute"); + CanQualType KernelNameType = + Ctx.getCanonicalType(KernelEntryPointAttr->getKernelName()); + return KernelNameType; +} + +static const SYCLKernelInfo * +GetKernelInfo(const FunctionDecl *KernelEntryPointFn, ASTContext &Ctx) { + CanQualType KernelNameType = GetKernelNameType(KernelEntryPointFn, Ctx); + const SYCLKernelInfo *KernelInfo = Ctx.findSYCLKernelInfo(KernelNameType); + assert(KernelInfo && "Type does not correspond to a kernel name"); + return KernelInfo; +} + void CodeGenModule::EmitSYCLKernelCaller(const FunctionDecl *KernelEntryPointFn, ASTContext &Ctx) { assert(Ctx.getLangOpts().SYCLIsDevice && @@ -52,12 +70,10 @@ void CodeGenModule::EmitSYCLKernelCaller(const FunctionDecl *KernelEntryPointFn, getTypes().arrangeSYCLKernelCallerDeclaration(Ctx.VoidTy, Args); llvm::FunctionType *FnTy = getTypes().GetFunctionType(FnInfo); - // Retrieve the generated name for the SYCL kernel caller function. - CanQualType KernelNameType = - Ctx.getCanonicalType(KernelEntryPointAttr->getKernelName()); - const SYCLKernelInfo &KernelInfo = Ctx.getSYCLKernelInfo(KernelNameType); - auto *Fn = llvm::Function::Create(FnTy, llvm::Function::ExternalLinkage, - KernelInfo.GetKernelName(), &getModule()); + // Retrieve the generated name for the SYCL kernel caller function + const SYCLKernelInfo *KernelInfo = GetKernelInfo(KernelEntryPointFn, Ctx); + auto *Fn = llvm::Function::Create(FnTy, llvm::GlobalVariable::ExternalLinkage, + KernelInfo->GetKernelName(), &getModule()); // Emit the SYCL kernel caller function. CodeGenFunction CGF(*this); @@ -70,3 +86,34 @@ void CodeGenModule::EmitSYCLKernelCaller(const FunctionDecl *KernelEntryPointFn, SetLLVMFunctionAttributesForDefinition(cast<Decl>(OutlinedFnDecl), Fn); CGF.FinishFunction(); } + +void CodeGenModule::AddSYCLKernelNameSymbol(CanQualType KernelNameType, + llvm::GlobalVariable *GV) { + SYCLKernelNameSymbols[KernelNameType] = GV; +} + +llvm::GlobalVariable * +CodeGenModule::GetSYCLKernelNameSymbol(CanQualType KernelNameType) { + auto it = SYCLKernelNameSymbols.find(KernelNameType); + if (it != SYCLKernelNameSymbols.end()) + return it->second; + return nullptr; +} + +void CodeGenModule::InitSYCLKernelInfoSymbolsForBuiltins( + const FunctionDecl *KernelEntryPointFn, ASTContext &Ctx) { + CanQualType KernelNameType = GetKernelNameType(KernelEntryPointFn, Ctx); + llvm::GlobalVariable *GV = GetSYCLKernelNameSymbol(KernelNameType); + if (GV && !GV->hasInitializer()) { + const SYCLKernelInfo *KernelInfo = GetKernelInfo(KernelEntryPointFn, Ctx); + ConstantAddress KernelNameStrConstantAddr = + GetAddrOfConstantCString(KernelInfo->GetKernelName(), ""); + llvm::Constant *KernelNameStr = KernelNameStrConstantAddr.getPointer(); + // FIXME: It is unclear to me whether the right API here is + // replaceInitializer or setInitializer. Unfortunately since the branch I am + // working on is outdated, my workspace does not have replaceInitializer + // Hopefully the person continuing to work on builtins can check this out. + GV->setInitializer(KernelNameStr); + GV->setLinkage(llvm::GlobalValue::PrivateLinkage); + } +} diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index d7c62b44a5c50..34786d3372bf4 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2219,6 +2219,26 @@ static bool BuiltinCountZeroBitsGeneric(Sema &S, CallExpr *TheCall) { return false; } +// The argument must be a class or struct with a member +// named type. +static bool CheckBuiltinSyclKernelName(Sema &S, CallExpr *TheCall) { + QualType ArgTy = TheCall->getArg(0)->getType(); + const auto *RT = ArgTy->getAs<RecordType>(); + + if(!RT) + return true; + + RecordDecl *RD = RT->getDecl(); + IdentifierTable &IdentTable = S.Context.Idents; + auto Name = DeclarationName(&(IdentTable.get("type"))); + DeclContext::lookup_result Lookup = RD->lookup(Name); + if (Lookup.empty() || !Lookup.isSingleResult() || + !isa<TypedefNameDecl>(Lookup.front())) + return true; + + return false; +} + ExprResult Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, CallExpr *TheCall) { @@ -3030,6 +3050,22 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, } break; } + case Builtin::BI__builtin_sycl_kernel_name: { + // Builtin takes 1 argument + if (TheCall->getNumArgs() != 1) { + Diag(TheCall->getBeginLoc(), diag::err_builtin_invalid_argument_count) + << 1; + return ExprError(); + } + + if (CheckBuiltinSyclKernelName(*this, TheCall)) { + Diag(TheCall->getArg(0)->getBeginLoc(), + diag::err_sycl_kernel_name_invalid_arg); + return ExprError(); + } + + break; + } case Builtin::BI__builtin_popcountg: if (BuiltinPopcountg(*this, TheCall)) return ExprError(); diff --git a/clang/test/CodeGenSYCL/builtin-sycl-kernel-name.cpp b/clang/test/CodeGenSYCL/builtin-sycl-kernel-name.cpp new file mode 100644 index 0000000000000..d5d596aea2d3f --- /dev/null +++ b/clang/test/CodeGenSYCL/builtin-sycl-kernel-name.cpp @@ -0,0 +1,102 @@ +// RUN: %clang_cc1 -fsycl-is-host -emit-llvm -triple x86_64 %s -o - | FileCheck %s + +// Test IR generated by __builtin_sycl_kernel_name(). This builtin accepts a SYCL +// kernel name type and returns it's mangled name. + +class kernel_name_1; +class kernel_name_2; +typedef kernel_name_2 kernel_name_TD; +class kernel_name_3; +class kernel_name_4; +class kernel_name_5; +typedef kernel_name_4 kernel_name_TD2; +class kernel_name_6; + +struct constexpr_kernel_name; + +template<typename KN> +struct kernel_id_t { + using type = KN; +}; + +struct kernel_id_nt { + using type = kernel_name_3; +}; + +template <typename name... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/140230 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits