https://github.com/mgcarrasco updated https://github.com/llvm/llvm-project/pull/183122
>From 3ac724d898c7e250989a8f270b9588f16cecb54d Mon Sep 17 00:00:00 2001 From: Manuel Carrasco <[email protected]> Date: Tue, 24 Feb 2026 12:00:13 -0600 Subject: [PATCH 1/4] [SPIRV] Add support for emitting DebugFunction debug info instructions This commit adds support for emitting SPIRV DebugFunction and DebugFunctionDefinition instructions for function definitions. --- .../Target/SPIRV/SPIRVEmitNonSemanticDI.cpp | 218 ++++++++++++++++++ .../SPIRV/debug-info/debug-function.ll | 40 ++++ 2 files changed, 258 insertions(+) create mode 100644 llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp index 6cbe15f5fa429..3dbff31b26644 100644 --- a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp @@ -62,6 +62,7 @@ struct SPIRVEmitNonSemanticDI : public MachineFunctionPass { private: bool emitGlobalDI(MachineFunction &MF); + bool emitFunctionDI(MachineFunction &MF); static SourceLanguage convertDWARFToSPIRVSourceLanguage(int64_t LLVMSourceLanguage); static Register emitOpString(MachineRegisterInfo &MRI, @@ -73,6 +74,14 @@ struct SPIRVEmitNonSemanticDI : public MachineFunctionPass { const RegisterBankInfo *RBI, MachineFunction &MF, SPIRV::NonSemanticExtInst::NonSemanticExtInst Inst, ArrayRef<Register> Registers); + static Register + emitDebugTypeFunction(MachineRegisterInfo &MRI, MachineIRBuilder &MIRBuilder, + SPIRVGlobalRegistry *GR, const SPIRVTypeInst &VoidTy, + const SPIRVTypeInst &I32Ty, const SPIRVInstrInfo *TII, + const SPIRVRegisterInfo *TRI, + const RegisterBankInfo *RBI, MachineFunction &MF, + DISubroutineType *FuncType, LLVMContext *Context); + static uint64_t getDebugFunctionFlags(const DISubprogram *SP); }; } // anonymous namespace @@ -156,6 +165,63 @@ Register SPIRVEmitNonSemanticDI::emitDIInstruction( return InstReg; } +// Emit a DebugTypeFunction instruction for the given DISubroutineType. +// This creates a SPIRV debug type function that represents the function +// signature, including type flags, return type, and parameter types. Currently +// only handles void functions with no parameters; type flags are not translated +// (using 0 as a placeholder), and full parameter and return type support is +// TODO. +Register SPIRVEmitNonSemanticDI::emitDebugTypeFunction( + MachineRegisterInfo &MRI, MachineIRBuilder &MIRBuilder, + SPIRVGlobalRegistry *GR, const SPIRVTypeInst &VoidTy, + const SPIRVTypeInst &I32Ty, const SPIRVInstrInfo *TII, + const SPIRVRegisterInfo *TRI, const RegisterBankInfo *RBI, + MachineFunction &MF, DISubroutineType *FuncType, LLVMContext *Context) { + // TODO: Translate flags from the function type. Currently not translating + // flags, using 0 as a placeholder. + const Register TypeFlagsReg = + GR->buildConstantInt(0, MIRBuilder, I32Ty, false); + + SmallVector<Register> TypeRegs; + TypeRegs.push_back(TypeFlagsReg); + + // TODO: Handle parameters and return types + // void function with no parameters - use OpTypeVoid for return type + const SPIRVTypeInst OpTypeVoidInst = + GR->getOrCreateSPIRVType(Type::getVoidTy(*Context), MIRBuilder, + SPIRV::AccessQualifier::ReadWrite, false); + const Register VoidTypeReg = GR->getSPIRVTypeID(OpTypeVoidInst); + TypeRegs.push_back(VoidTypeReg); + + // Emit DebugTypeFunction instruction + const Register InstReg = MRI.createVirtualRegister(&SPIRV::IDRegClass); + MRI.setType(InstReg, LLT::scalar(32)); + MachineInstrBuilder MIB = + MIRBuilder.buildInstr(SPIRV::OpExtInst) + .addDef(InstReg) + .addUse(GR->getSPIRVTypeID(VoidTy)) + .addImm(static_cast<int64_t>( + SPIRV::InstructionSet::NonSemantic_Shader_DebugInfo_100)) + .addImm(SPIRV::NonSemanticExtInst::DebugTypeFunction); + for (auto Reg : TypeRegs) { + MIB.addUse(Reg); + } + MIB.constrainAllUses(*TII, *TRI, *RBI); + GR->assignSPIRVTypeToVReg(VoidTy, InstReg, MF); + return InstReg; +} + +// TODO: Support additional DebugFunction flags. Currently only FlagIsDefinition +// is handled. +uint64_t SPIRVEmitNonSemanticDI::getDebugFunctionFlags(const DISubprogram *SP) { + // Map flags - minimal implementation + // FlagIsDefinition = 1 << 3 + uint64_t Flags = 0; + if (SP->isDefinition()) + Flags |= (1 << 3); // FlagIsDefinition + return Flags; +} + bool SPIRVEmitNonSemanticDI::emitGlobalDI(MachineFunction &MF) { // If this MachineFunction doesn't have any BB repeat procedure // for the next @@ -373,8 +439,160 @@ bool SPIRVEmitNonSemanticDI::emitGlobalDI(MachineFunction &MF) { return true; } +// Emits the SPIRV DebugFunction instruction for a given MachineFunction. +bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { + const Function &F = MF.getFunction(); + + DISubprogram *SP = F.getSubprogram(); + // DISubProgram is not available, don't translate + if (!SP) { + return false; + } + + // TODO: Support declarations + // Only process function definitions, skip declarations. + // Function declarations require an optional operand in the DebugFunction + // instruction that is not yet supported. + if (!SP->isDefinition()) { + return false; + } + + // We insert at the first basic block available + if (MF.begin() == MF.end()) { + return false; + } + + // Get the scope from DISubProgram + DIScope *Scope = SP->getScope(); + if (!Scope) { + return false; + } + + // TODO: Support additional DIScope types beyond DIFile. + // Only translate when scope is DIFile + const DIFile *FileScope = dyn_cast<DIFile>(Scope); + if (!FileScope) { + return false; + } + + // Use SP->getUnit() as the scope for DebugSource. + // In SPIRV, the DebugSource scope cannot be a File, so we use the + // CompilationUnit instead. This matches what the translator does when + // handling DIFile scopes. + const DICompileUnit *CU = SP->getUnit(); + if (!CU) { + return false; + } + + // Check for function type - required for DebugTypeFunction + DISubroutineType *FuncType = SP->getType(); + if (!FuncType) { + return false; + } + + // TODO: Support functions with return types and parameters. + // Check that the function type array has exactly one element and it is null. + // This corresponds to void functions with no parameters. + DITypeArray TypeArray = FuncType->getTypeArray(); + if (TypeArray.size() != 1 || TypeArray[0] != nullptr) { + return false; + } + + const Module *M = getModule(MF); + LLVMContext *Context = &M->getContext(); + + // Emit DebugCompilationUnit and DebugFunction + { + const SPIRVInstrInfo *TII = TM->getSubtargetImpl()->getInstrInfo(); + const SPIRVRegisterInfo *TRI = TM->getSubtargetImpl()->getRegisterInfo(); + const RegisterBankInfo *RBI = TM->getSubtargetImpl()->getRegBankInfo(); + SPIRVGlobalRegistry *GR = TM->getSubtargetImpl()->getSPIRVGlobalRegistry(); + MachineRegisterInfo &MRI = MF.getRegInfo(); + MachineBasicBlock &MBB = *MF.begin(); + + MachineIRBuilder MIRBuilder(MBB, MBB.getFirstTerminator()); + + const SPIRVTypeInst VoidTy = + GR->getOrCreateSPIRVType(Type::getVoidTy(*Context), MIRBuilder, + SPIRV::AccessQualifier::ReadWrite, false); + + const SPIRVTypeInst I32Ty = + GR->getOrCreateSPIRVType(Type::getInt32Ty(*Context), MIRBuilder, + SPIRV::AccessQualifier::ReadWrite, false); + + // Get file path from DICompileUnit for DebugSource (needed for + // DebugFunction) + DIFile *File = CU->getFile(); + SmallString<128> FilePath; + sys::path::append(FilePath, File->getDirectory(), File->getFilename()); + + // Emit DebugSource (needed for DebugFunction) + const Register FilePathStrReg = emitOpString(MRI, MIRBuilder, FilePath); + const Register DebugSourceReg = emitDIInstruction( + MRI, MIRBuilder, GR, VoidTy, TII, TRI, RBI, MF, + SPIRV::NonSemanticExtInst::DebugSource, {FilePathStrReg}); + + // Look up the DebugCompilationUnit register from emitGlobalDI + auto It = CompileUnitRegMap.find(CU); + assert(It != CompileUnitRegMap.end() && + "DebugCompilationUnit register should have been created in " + "emitGlobalDI"); + const Register DebugCompUnitReg = It->second; + + // Emit DebugFunction + // Get function metadata + StringRef FuncName = SP->getName(); + StringRef LinkageName = SP->getLinkageName(); + unsigned Line = SP->getLine(); + unsigned ScopeLine = SP->getScopeLine(); + + uint64_t Flags = getDebugFunctionFlags(SP); + + const Register NameStrReg = emitOpString(MRI, MIRBuilder, FuncName); + const Register LinkageNameStrReg = + emitOpString(MRI, MIRBuilder, LinkageName); + + // Emit DebugTypeFunction + const Register DebugTypeFunctionReg = + emitDebugTypeFunction(MRI, MIRBuilder, GR, VoidTy, I32Ty, TII, TRI, RBI, + MF, FuncType, Context); + + const Register LineReg = + GR->buildConstantInt(Line, MIRBuilder, I32Ty, false); + const Register ColumnReg = + GR->buildConstantInt(0, MIRBuilder, I32Ty, false); + const Register FlagsReg = + GR->buildConstantInt(Flags, MIRBuilder, I32Ty, false); + const Register ScopeLineReg = + GR->buildConstantInt(ScopeLine, MIRBuilder, I32Ty, false); + + // TODO: Handle function declarations. Declarations require an optional + // operand in the DebugFunction instruction that specifies the declaration's + // location. + const Register DebugFuncReg = emitDIInstruction( + MRI, MIRBuilder, GR, VoidTy, TII, TRI, RBI, MF, + SPIRV::NonSemanticExtInst::DebugFunction, + {NameStrReg, DebugTypeFunctionReg, DebugSourceReg, LineReg, ColumnReg, + DebugCompUnitReg, LinkageNameStrReg, FlagsReg, ScopeLineReg}); + + // Emit DebugFunctionDefinition to link the DebugFunction to the actual + // OpFunction. + const MachineInstr *OpFunctionMI = GR->getFunctionDefinition(&F); + if (OpFunctionMI && OpFunctionMI->getOpcode() == SPIRV::OpFunction) { + Register FuncReg = OpFunctionMI->getOperand(0).getReg(); + emitDIInstruction(MRI, MIRBuilder, GR, VoidTy, TII, TRI, RBI, MF, + SPIRV::NonSemanticExtInst::DebugFunctionDefinition, + {DebugFuncReg, FuncReg}); + } + + return true; + } +} + +// TODO: Deduplicate instructions bool SPIRVEmitNonSemanticDI::runOnMachineFunction(MachineFunction &MF) { CompileUnitRegMap.clear(); bool Res = emitGlobalDI(MF); + Res |= emitFunctionDI(MF); return Res; } diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll new file mode 100644 index 0000000000000..d9bbbe950515f --- /dev/null +++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll @@ -0,0 +1,40 @@ +; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s +; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %} + +; The path separator may be '/' on Unix or '\' on Windows, so we use a flexible pattern +; CHECK: OpString "{{[/\]}}A{{[/\]}}B{{[/\]}}C{{[/\]}}example.cpp" +; CHECK: [[FILE_STRING:%[0-9]+]] = OpString "{{[/\]}}A{{[/\]}}B{{[/\]}}C{{[/\]}}example.cpp" +; CHECK: [[NAME:%[0-9]+]] = OpString "test1" +; CHECK: [[LINKAGE:%[0-9]+]] = OpString "XXXX" +; CHECK: [[VOID_TYPE:%[0-9]+]] = OpTypeVoid +; CHECK: [[ZERO:%[0-9]+]] = OpConstant {{.*}} 0 +; CHECK: [[LINE:%[0-9]+]] = OpConstant {{.*}} 1 +; CHECK: [[FLAGS:%[0-9]+]] = OpConstant {{.*}} 8 +; CHECK: OpExtInst {{.*}} DebugSource {{.*}} +; CHECK: [[PARENT:%[0-9]+]] = OpExtInst {{.*}} DebugCompilationUnit {{.*}} +; CHECK: [[SOURCE:%[0-9]+]] = OpExtInst {{.*}} DebugSource [[FILE_STRING]] +; CHECK: [[TYPE:%[0-9]+]] = OpExtInst {{.*}} DebugTypeFunction [[ZERO]] [[VOID_TYPE]] +; CHECK: [[DEBUG_FUNC:%[0-9]+]] = OpExtInst {{.*}} DebugFunction [[NAME]] [[TYPE]] [[SOURCE]] [[LINE]] [[ZERO]] [[PARENT]] [[LINKAGE]] [[FLAGS]] [[LINE]] +; CHECK: [[FUNC:%[0-9]+]] = OpFunction {{.*}} +; DebugFunctionDefinition must be after OpFunction +; CHECK: OpExtInst {{.*}} DebugFunctionDefinition [[DEBUG_FUNC]] [[FUNC]] + +target triple = "spirv64-unknown-unknown" + +define spir_func void @test1() !dbg !9 { +entry: + ret void +} + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3, !4, !5} + +!0 = distinct !DICompileUnit(language: DW_LANG_Zig, file: !1, producer: "clang version XX.X.XXXX (F F)", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "example.cpp", directory: "/A/B/C") +!2 = !{i32 7, !"Dwarf Version", i32 5} +!3 = !{i32 2, !"Debug Info Version", i32 3} +!4 = !{i32 1, !"wchar_size", i32 4} +!5 = !{i32 7, !"frame-pointer", i32 2} +!9 = distinct !DISubprogram(name: "test1", linkageName: "XXXX", scope: !1, file: !1, line: 1, type: !10, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0) +!10 = !DISubroutineType(types: !11) +!11 = !{null} >From 8427a8a7537e0caf0f6907ce1472d252f5fa37c6 Mon Sep 17 00:00:00 2001 From: Manuel Carrasco <[email protected]> Date: Fri, 6 Mar 2026 07:57:45 -0600 Subject: [PATCH 2/4] [review] improve comments. --- .../Target/SPIRV/SPIRVEmitNonSemanticDI.cpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp index 3dbff31b26644..43aab9f6494dc 100644 --- a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp @@ -444,7 +444,7 @@ bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { const Function &F = MF.getFunction(); DISubprogram *SP = F.getSubprogram(); - // DISubProgram is not available, don't translate + // DISubProgram is not available, don't translate. if (!SP) { return false; } @@ -457,19 +457,19 @@ bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { return false; } - // We insert at the first basic block available + // We insert at the first basic block available. if (MF.begin() == MF.end()) { return false; } - // Get the scope from DISubProgram + // Get the scope from DISubProgram. DIScope *Scope = SP->getScope(); if (!Scope) { return false; } // TODO: Support additional DIScope types beyond DIFile. - // Only translate when scope is DIFile + // Only translate when scope is DIFile. const DIFile *FileScope = dyn_cast<DIFile>(Scope); if (!FileScope) { return false; @@ -484,7 +484,7 @@ bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { return false; } - // Check for function type - required for DebugTypeFunction + // Check for function type - required for DebugTypeFunction. DISubroutineType *FuncType = SP->getType(); if (!FuncType) { return false; @@ -501,7 +501,7 @@ bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { const Module *M = getModule(MF); LLVMContext *Context = &M->getContext(); - // Emit DebugCompilationUnit and DebugFunction + // Emit DebugCompilationUnit and DebugFunction. { const SPIRVInstrInfo *TII = TM->getSubtargetImpl()->getInstrInfo(); const SPIRVRegisterInfo *TRI = TM->getSubtargetImpl()->getRegisterInfo(); @@ -521,7 +521,7 @@ bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { SPIRV::AccessQualifier::ReadWrite, false); // Get file path from DICompileUnit for DebugSource (needed for - // DebugFunction) + // DebugFunction). DIFile *File = CU->getFile(); SmallString<128> FilePath; sys::path::append(FilePath, File->getDirectory(), File->getFilename()); @@ -532,15 +532,15 @@ bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { MRI, MIRBuilder, GR, VoidTy, TII, TRI, RBI, MF, SPIRV::NonSemanticExtInst::DebugSource, {FilePathStrReg}); - // Look up the DebugCompilationUnit register from emitGlobalDI + // Look up the DebugCompilationUnit register from emitGlobalDI. auto It = CompileUnitRegMap.find(CU); assert(It != CompileUnitRegMap.end() && "DebugCompilationUnit register should have been created in " "emitGlobalDI"); const Register DebugCompUnitReg = It->second; - // Emit DebugFunction - // Get function metadata + // Emit DebugFunction. + // Get function metadata. StringRef FuncName = SP->getName(); StringRef LinkageName = SP->getLinkageName(); unsigned Line = SP->getLine(); @@ -552,7 +552,7 @@ bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { const Register LinkageNameStrReg = emitOpString(MRI, MIRBuilder, LinkageName); - // Emit DebugTypeFunction + // Emit DebugTypeFunction. const Register DebugTypeFunctionReg = emitDebugTypeFunction(MRI, MIRBuilder, GR, VoidTy, I32Ty, TII, TRI, RBI, MF, FuncType, Context); >From e0358763a507f3b681bcfb0d7a5fa27b1724e899 Mon Sep 17 00:00:00 2001 From: Manuel Carrasco <[email protected]> Date: Fri, 6 Mar 2026 08:00:31 -0600 Subject: [PATCH 3/4] [review] fix comment. --- llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll b/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll index d9bbbe950515f..e88df5ba6ee3e 100644 --- a/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll +++ b/llvm/test/CodeGen/SPIRV/debug-info/debug-function.ll @@ -1,7 +1,7 @@ ; RUN: llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - | FileCheck %s ; RUN: %if spirv-tools %{ llc --verify-machineinstrs --spv-emit-nonsemantic-debug-info --spirv-ext=+SPV_KHR_non_semantic_info -O0 -mtriple=spirv64-unknown-unknown %s -o - -filetype=obj | spirv-val %} -; The path separator may be '/' on Unix or '\' on Windows, so we use a flexible pattern +; The path separator may be '/' on Unix or '\' on Windows, so we use a flexible pattern. ; CHECK: OpString "{{[/\]}}A{{[/\]}}B{{[/\]}}C{{[/\]}}example.cpp" ; CHECK: [[FILE_STRING:%[0-9]+]] = OpString "{{[/\]}}A{{[/\]}}B{{[/\]}}C{{[/\]}}example.cpp" ; CHECK: [[NAME:%[0-9]+]] = OpString "test1" >From 65f2890f283d3cc82a2ae946f4ad5807dd83ceda Mon Sep 17 00:00:00 2001 From: Manuel Carrasco <[email protected]> Date: Fri, 6 Mar 2026 08:03:08 -0600 Subject: [PATCH 4/4] [reviews] simplify code. --- .../Target/SPIRV/SPIRVEmitNonSemanticDI.cpp | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp index 43aab9f6494dc..54bf384039db6 100644 --- a/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp +++ b/llvm/lib/Target/SPIRV/SPIRVEmitNonSemanticDI.cpp @@ -444,51 +444,43 @@ bool SPIRVEmitNonSemanticDI::emitFunctionDI(MachineFunction &MF) { const Function &F = MF.getFunction(); DISubprogram *SP = F.getSubprogram(); - // DISubProgram is not available, don't translate. - if (!SP) { + if (!SP) return false; - } // TODO: Support declarations // Only process function definitions, skip declarations. // Function declarations require an optional operand in the DebugFunction // instruction that is not yet supported. - if (!SP->isDefinition()) { + if (!SP->isDefinition()) return false; - } // We insert at the first basic block available. - if (MF.begin() == MF.end()) { + if (MF.begin() == MF.end()) return false; - } // Get the scope from DISubProgram. DIScope *Scope = SP->getScope(); - if (!Scope) { + if (!Scope) return false; - } // TODO: Support additional DIScope types beyond DIFile. // Only translate when scope is DIFile. const DIFile *FileScope = dyn_cast<DIFile>(Scope); - if (!FileScope) { + if (!FileScope) return false; - } // Use SP->getUnit() as the scope for DebugSource. // In SPIRV, the DebugSource scope cannot be a File, so we use the // CompilationUnit instead. This matches what the translator does when // handling DIFile scopes. const DICompileUnit *CU = SP->getUnit(); - if (!CU) { + if (!CU) return false; - } - // Check for function type - required for DebugTypeFunction. + // Check for function type (required for emitting DebugTypeFunction). DISubroutineType *FuncType = SP->getType(); - if (!FuncType) { + if (!FuncType) return false; - } // TODO: Support functions with return types and parameters. // Check that the function type array has exactly one element and it is null. _______________________________________________ llvm-branch-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-branch-commits
