llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clangir @llvm/pr-subscribers-clang Author: None (Andres-Salamanca) <details> <summary>Changes</summary> This PR upstreams support for the `cir.indirectBr` operation, which is used to implement GCC’s labels-as-values `indirect goto`. To ensure correct lowering, we introduce precise bookkeeping to associate each `block_address` operation with its corresponding `label` op. This is required because a `block_address` may be emitted before the `label` it refers to. In such cases, the reference is deferred and later resolved by `resolveBlockAddresses`, which guarantees that all `indirectBr` successors are emitted in the correct and fully resolved order. --- Patch is 25.35 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/169967.diff 12 Files Affected: - (modified) clang/include/clang/CIR/Dialect/IR/CIROps.td (+61) - (modified) clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp (+15-4) - (modified) clang/lib/CIR/CodeGen/CIRGenFunction.cpp (+53) - (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+12) - (modified) clang/lib/CIR/CodeGen/CIRGenModule.cpp (+38) - (modified) clang/lib/CIR/CodeGen/CIRGenModule.h (+17-1) - (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+19-2) - (modified) clang/lib/CIR/Dialect/IR/CIRDialect.cpp (+59) - (modified) clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp (+1-1) - (modified) clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp (+6) - (modified) clang/test/CIR/CodeGen/label-values.c (+73-14) - (added) clang/test/CIR/IR/indirect-br.cir (+46) ``````````diff diff --git a/clang/include/clang/CIR/Dialect/IR/CIROps.td b/clang/include/clang/CIR/Dialect/IR/CIROps.td index a19c4f951fff9..76d44c29b7da8 100644 --- a/clang/include/clang/CIR/Dialect/IR/CIROps.td +++ b/clang/include/clang/CIR/Dialect/IR/CIROps.td @@ -1433,6 +1433,67 @@ def CIR_BrCondOp : CIR_Op<"brcond", [ }]; } +//===----------------------------------------------------------------------===// +// IndirectBrOp +//===----------------------------------------------------------------------===// + +def CIR_IndirectBrOp : CIR_Op<"indirectbr", [ + DeclareOpInterfaceMethods<BranchOpInterface> + , SameVariadicOperandSize, Terminator, Pure]> { + let summary = "Indirect branch"; + let description = [{ + The `cir.indirectbr` operation represents an indirect branch to one of + several possible successor blocks. The target block is computed from + the value of the given address operand. + + This operation is typically generated when handling constructs like + the GCC extension `&&label` combined with an indirect `goto *ptr;`. + + The `poison` attribute is used to mark an `indirectbr` that was created + but is known to be invalid for instance, when a label address was + taken but no indirect branch was ever emitted. + + Example: + + ```mlir + %0 = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] + %1 = cir.block_address <@A, "A"> : !cir.ptr<!void> + cir.store align(8) %1, %0 : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>> + %2 = cir.load align(8) %0 : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void> + cir.br ^bb1(%2 : !cir.ptr<!void>) + ^bb1(%3: !cir.ptr<!void>): + cir.indirectbr %3 : <!void>, [ + ^bb2 + ] + ``` + or with a poison: + + ```mlir + cir.indirectbr %0 poison : <!void>, [ + ^bb3, + ^bb2 + ] + ``` + }]; + + let arguments = (ins + CIR_VoidPtrType:$addr, + UnitAttr:$poison, + VariadicOfVariadic<AnyType, "indbr_operand_segments">:$succOperands, + DenseI32ArrayAttr:$indbr_operand_segments + ); + + let successors = (successor VariadicSuccessor<AnySuccessor>:$successors); + let assemblyFormat = [{ + $addr ( `poison` $poison^ )? `:` type($addr) `,` + custom<IndirectBrOpSucessors>(ref(type($addr)), + $successors, + $succOperands, + type($succOperands)) + attr-dict + }]; +} + //===----------------------------------------------------------------------===// // Common loop op definitions //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp index 7c94743d5ffc6..d5bcafc9c81f4 100644 --- a/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenExprScalar.cpp @@ -174,11 +174,22 @@ class ScalarExprEmitter : public StmtVisitor<ScalarExprEmitter, mlir::Value> { mlir::Value VisitAddrLabelExpr(const AddrLabelExpr *e) { auto func = cast<cir::FuncOp>(cgf.curFn); - auto blockInfoAttr = cir::BlockAddrInfoAttr::get( + cir::BlockAddrInfoAttr blockInfoAttr = cir::BlockAddrInfoAttr::get( &cgf.getMLIRContext(), func.getSymName(), e->getLabel()->getName()); - return cir::BlockAddressOp::create(builder, cgf.getLoc(e->getSourceRange()), - cgf.convertType(e->getType()), - blockInfoAttr); + cir::BlockAddressOp blockAddressOp = cir::BlockAddressOp::create( + builder, cgf.getLoc(e->getSourceRange()), cgf.convertType(e->getType()), + blockInfoAttr); + cir::LabelOp resolvedLabel = cgf.cgm.lookupBlockAddressInfo(blockInfoAttr); + if (!resolvedLabel) { + cgf.cgm.mapUnresolvedBlockAddress(blockAddressOp); + // Still add the op to maintain insertion order it will be resolved in + // resolveBlockAddresses + cgf.cgm.mapResolvedBlockAddress(blockAddressOp, nullptr); + } else { + cgf.cgm.mapResolvedBlockAddress(blockAddressOp, resolvedLabel); + } + cgf.getIndirectGotoBlock(); + return blockAddressOp; } mlir::Value VisitIntegerLiteral(const IntegerLiteral *e) { diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp index 33bdfa315a9ea..ed8663d51aa10 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.cpp @@ -531,7 +531,49 @@ void CIRGenFunction::startFunction(GlobalDecl gd, QualType returnType, } } +void CIRGenFunction::resolveBlockAddresses() { + + for (cir::BlockAddressOp &blockAddress : cgm.unresolvedBlockAddressToLabel) { + cir::LabelOp labelOp = + cgm.lookupBlockAddressInfo(blockAddress.getBlockAddrInfo()); + assert(labelOp && "expected cir.labelOp to already be emitted"); + cgm.updateResolvedBlockAddress(blockAddress, labelOp); + } + cgm.unresolvedBlockAddressToLabel.clear(); +} + +void CIRGenFunction::finishIndirectBranch() { + if (!indirectGotoBlock) + return; + llvm::SmallVector<mlir::Block *> succesors; + llvm::SmallVector<mlir::ValueRange> rangeOperands; + mlir::OpBuilder::InsertionGuard guard(builder); + builder.setInsertionPointToEnd(indirectGotoBlock); + for (auto &[blockAdd, labelOp] : cgm.blockAddressToLabel) { + succesors.push_back(labelOp->getBlock()); + rangeOperands.push_back(labelOp->getBlock()->getArguments()); + } + cir::IndirectBrOp::create(builder, builder.getUnknownLoc(), + indirectGotoBlock->getArgument(0), false, + rangeOperands, succesors); + cgm.blockAddressToLabel.clear(); +} + void CIRGenFunction::finishFunction(SourceLocation endLoc) { + // Resolve block address-to-label mappings, then emit the indirect branch + // with the corresponding targets. + resolveBlockAddresses(); + finishIndirectBranch(); + + // If a label address was taken but no indirect goto was used, we can't remove + // the block argument here. Instead, we mark the 'indirectbr' op + // as poison so that the cleanup can be deferred to lowering, since the + // verifier doesn't allow the 'indirectbr' target address to be null. + if (indirectGotoBlock && indirectGotoBlock->hasNoPredecessors()) { + auto indrBr = cast<cir::IndirectBrOp>(indirectGotoBlock->front()); + indrBr.setPoison(true); + } + // Pop any cleanups that might have been associated with the // parameters. Do this in whatever block we're currently in; it's // important to do this before we enter the return block or return @@ -1086,6 +1128,17 @@ CIRGenFunction::emitArrayLength(const clang::ArrayType *origArrayType, return builder.getConstInt(*currSrcLoc, sizeTy, countFromCLAs); } +void CIRGenFunction::getIndirectGotoBlock() { + // If we already made the indirect branch for indirect goto, return its block. + if (indirectGotoBlock) + return; + + mlir::OpBuilder::InsertionGuard guard(builder); + indirectGotoBlock = + builder.createBlock(builder.getBlock()->getParent(), {}, {voidPtrTy}, + {builder.getUnknownLoc()}); +} + mlir::Value CIRGenFunction::emitAlignmentAssumption( mlir::Value ptrValue, QualType ty, SourceLocation loc, SourceLocation assumptionLoc, int64_t alignment, mlir::Value offsetValue) { diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h index 9adac089ea28b..f4b7cceef5f85 100644 --- a/clang/lib/CIR/CodeGen/CIRGenFunction.h +++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h @@ -634,6 +634,14 @@ class CIRGenFunction : public CIRGenTypeCache { return JumpDest(target, ehStack.getInnermostNormalCleanup(), nextCleanupDestIndex++); } + /// IndirectBranch - The first time an indirect goto is seen we create a block + /// reserved for the indirect branch. Unlike before,the actual 'indirectbr' + /// is emitted at the end of the function, once all block destinations have + /// been resolved. + mlir::Block *indirectGotoBlock = nullptr; + + void resolveBlockAddresses(); + void finishIndirectBranch(); /// Perform the usual unary conversions on the specified expression and /// compare the result against zero, returning an Int1Ty value. @@ -1360,6 +1368,8 @@ class CIRGenFunction : public CIRGenTypeCache { int64_t getAccessedFieldNo(unsigned idx, mlir::ArrayAttr elts); + void getIndirectGotoBlock(); + RValue emitCall(const CIRGenFunctionInfo &funcInfo, const CIRGenCallee &callee, ReturnValueSlot returnValue, const CallArgList &args, cir::CIRCallOpInterface *callOp, @@ -1543,6 +1553,8 @@ class CIRGenFunction : public CIRGenTypeCache { mlir::LogicalResult emitGotoStmt(const clang::GotoStmt &s); + mlir::LogicalResult emitIndirectGotoStmt(const IndirectGotoStmt &s); + void emitImplicitAssignmentOperatorBody(FunctionArgList &args); void emitInitializerForField(clang::FieldDecl *field, LValue lhs, diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp index 809c24f8aa670..a9a1b300a79dd 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp @@ -2496,3 +2496,41 @@ DiagnosticBuilder CIRGenModule::errorNYI(SourceRange loc, llvm::StringRef feature) { return errorNYI(loc.getBegin(), feature) << loc; } + +void CIRGenModule::mapBlockAddress(cir::BlockAddrInfoAttr blockInfo, + cir::LabelOp label) { + auto result = blockAddressInfoToLabel.try_emplace(blockInfo, label); + (void)result; + assert(result.second && + "attempting to map a blockaddress info that is already mapped"); +} + +void CIRGenModule::mapUnresolvedBlockAddress(cir::BlockAddressOp op) { + auto result = unresolvedBlockAddressToLabel.insert(op); + (void)result; + assert(result.second && + "attempting to map a blockaddress operation that is already mapped"); +} + +void CIRGenModule::mapResolvedBlockAddress(cir::BlockAddressOp op, + cir::LabelOp label) { + auto result = blockAddressToLabel.try_emplace(op, label); + (void)result; + assert(result.second && + "attempting to map a blockaddress operation that is already mapped"); +} + +void CIRGenModule::updateResolvedBlockAddress(cir::BlockAddressOp op, + cir::LabelOp newLabel) { + auto *it = blockAddressToLabel.find(op); + assert(it != blockAddressToLabel.end() && + "trying to update a blockaddress not previously mapped"); + assert(!it->second && "blockaddress already has a resolved label"); + + it->second = newLabel; +} + +cir::LabelOp +CIRGenModule::lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo) { + return blockAddressInfoToLabel.lookup(blockInfo); +} diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.h b/clang/lib/CIR/CodeGen/CIRGenModule.h index 6600d086f8f61..fb1993c933cf2 100644 --- a/clang/lib/CIR/CodeGen/CIRGenModule.h +++ b/clang/lib/CIR/CodeGen/CIRGenModule.h @@ -126,7 +126,23 @@ class CIRGenModule : public CIRGenTypeCache { /// the pointers are supposed to be uniqued, should be fine. Revisit this if /// it ends up taking too much memory. llvm::DenseMap<const clang::FieldDecl *, llvm::StringRef> lambdaFieldToName; - + /// Map BlockAddrInfoAttr (function name, label name) to the corresponding CIR + /// LabelOp. This provides the main lookup table used to resolve block + /// addresses into their label operations. + llvm::DenseMap<cir::BlockAddrInfoAttr, cir::LabelOp> blockAddressInfoToLabel; + /// Map CIR BlockAddressOps directly to their resolved LabelOps. + /// Used once a block address has been successfully lowered to a label. + llvm::MapVector<cir::BlockAddressOp, cir::LabelOp> blockAddressToLabel; + /// Track CIR BlockAddressOps that cannot be resolved immediately + /// because their LabelOp has not yet been emitted. These entries + /// are solved later once the corresponding label is available. + llvm::DenseSet<cir::BlockAddressOp> unresolvedBlockAddressToLabel; + cir::LabelOp lookupBlockAddressInfo(cir::BlockAddrInfoAttr blockInfo); + void mapBlockAddress(cir::BlockAddrInfoAttr blockInfo, cir::LabelOp label); + void mapUnresolvedBlockAddress(cir::BlockAddressOp op); + void mapResolvedBlockAddress(cir::BlockAddressOp op, cir::LabelOp); + void updateResolvedBlockAddress(cir::BlockAddressOp op, + cir::LabelOp newLabel); /// Tell the consumer that this variable has been instantiated. void handleCXXStaticMemberVarInstantiation(VarDecl *vd); diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp index 7bb8c2153056a..09801bd7f1888 100644 --- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp +++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp @@ -203,6 +203,7 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s, return emitCoroutineBody(cast<CoroutineBodyStmt>(*s)); case Stmt::CoreturnStmtClass: case Stmt::IndirectGotoStmtClass: + return emitIndirectGotoStmt(cast<IndirectGotoStmt>(*s)); case Stmt::OMPParallelDirectiveClass: case Stmt::OMPTaskwaitDirectiveClass: case Stmt::OMPTaskyieldDirectiveClass: @@ -555,6 +556,17 @@ mlir::LogicalResult CIRGenFunction::emitGotoStmt(const clang::GotoStmt &s) { return mlir::success(); } +mlir::LogicalResult +CIRGenFunction::emitIndirectGotoStmt(const IndirectGotoStmt &s) { + mlir::Value val = emitScalarExpr(s.getTarget()); + assert(indirectGotoBlock && + "If you jumping to a indirect branch should be alareadye emitted"); + cir::BrOp::create(builder, getLoc(s.getSourceRange()), indirectGotoBlock, + val); + builder.createBlock(builder.getBlock()->getParent()); + return mlir::success(); +} + mlir::LogicalResult CIRGenFunction::emitContinueStmt(const clang::ContinueStmt &s) { builder.createContinue(getLoc(s.getKwLoc())); @@ -581,9 +593,14 @@ mlir::LogicalResult CIRGenFunction::emitLabel(const clang::LabelDecl &d) { } builder.setInsertionPointToEnd(labelBlock); - cir::LabelOp::create(builder, getLoc(d.getSourceRange()), d.getName()); + cir::LabelOp label = + cir::LabelOp::create(builder, getLoc(d.getSourceRange()), d.getName()); builder.setInsertionPointToEnd(labelBlock); - + auto func = cast<cir::FuncOp>(curFn); + cgm.mapBlockAddress(cir::BlockAddrInfoAttr::get(builder.getContext(), + func.getSymNameAttr(), + label.getLabelAttr()), + label); // FIXME: emit debug info for labels, incrementProfileCounter assert(!cir::MissingFeatures::ehstackBranches()); assert(!cir::MissingFeatures::incrementProfileCounter()); diff --git a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp index 6bf543cf794b7..b7d1a97185879 100644 --- a/clang/lib/CIR/Dialect/IR/CIRDialect.cpp +++ b/clang/lib/CIR/Dialect/IR/CIRDialect.cpp @@ -1116,6 +1116,65 @@ Block *cir::BrOp::getSuccessorForOperands(ArrayRef<Attribute>) { return getDest(); } +//===----------------------------------------------------------------------===// +// IndirectBrCondOp +//===----------------------------------------------------------------------===// + +mlir::SuccessorOperands +cir::IndirectBrOp::getSuccessorOperands(unsigned index) { + assert(index < getNumSuccessors() && "invalid successor index"); + return mlir::SuccessorOperands(getSuccOperandsMutable()[index]); +} + +ParseResult parseIndirectBrOpSucessors( + OpAsmParser &parser, Type &flagType, + SmallVectorImpl<Block *> &succOperandBlocks, + SmallVectorImpl<SmallVector<OpAsmParser::UnresolvedOperand>> &succOperands, + SmallVectorImpl<SmallVector<Type>> &succOperandsTypes) { + if (failed(parser.parseCommaSeparatedList( + OpAsmParser::Delimiter::Square, + [&]() { + Block *destination = nullptr; + SmallVector<OpAsmParser::UnresolvedOperand> operands; + SmallVector<Type> operandTypes; + + if (parser.parseSuccessor(destination).failed()) + return failure(); + + if (succeeded(parser.parseOptionalLParen())) { + if (failed(parser.parseOperandList( + operands, OpAsmParser::Delimiter::None)) || + failed(parser.parseColonTypeList(operandTypes)) || + failed(parser.parseRParen())) + return failure(); + } + succOperandBlocks.push_back(destination); + succOperands.emplace_back(operands); + succOperandsTypes.emplace_back(operandTypes); + return success(); + }, + "successor blocks"))) + return failure(); + return success(); +} + +void printIndirectBrOpSucessors(OpAsmPrinter &p, cir::IndirectBrOp op, + Type flagType, SuccessorRange succs, + OperandRangeRange succOperands, + const TypeRangeRange &succOperandsTypes) { + p << "["; + llvm::interleave( + llvm::zip(succs, succOperands), + [&](auto i) { + p.printNewline(); + p.printSuccessorAndUseList(std::get<0>(i), std::get<1>(i)); + }, + [&] { p << ','; }); + if (!succOperands.empty()) + p.printNewline(); + p << "]"; +} + //===----------------------------------------------------------------------===// // BrCondOp //===----------------------------------------------------------------------===// diff --git a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp index 2ef09b74dc968..0749c5e79e3ff 100644 --- a/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp +++ b/clang/lib/CIR/Dialect/Transforms/CIRCanonicalize.cpp @@ -52,7 +52,7 @@ struct RemoveRedundantBranches : public OpRewritePattern<BrOp> { Block *block = op.getOperation()->getBlock(); Block *dest = op.getDest(); - if (isa<cir::LabelOp>(dest->front())) + if (isa<cir::LabelOp, cir::IndirectBrOp>(dest->front())) return failure(); // Single edge between blocks: merge it. if (block->getNumSuccessors() == 1 && diff --git a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp index 6136d48204e0c..76ddc360206b5 100644 --- a/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp +++ b/clang/lib/CIR/Lowering/DirectToLLVM/LowerToLLVM.cpp @@ -4057,6 +4057,12 @@ mlir::LogicalResult CIRToLLVMBlockAddressOpLowering::matchAndRewrite( return mlir::failure(); } +mlir::LogicalResult CIRToLLVMIndirectBrOpLowering::matchAndRewrite( + cir::IndirectBrOp op, OpAdaptor adaptor, + mlir::ConversionPatternRewriter &rewriter) const { + return mlir::failure(); +} + mlir::LogicalResult CIRToLLVMAwaitOpLowering::matchAndRewrite( cir::AwaitOp op, OpAdaptor adaptor, mlir::ConversionPatternRewriter &rewriter) const { diff --git a/clang/test/CIR/CodeGen/label-values.c b/clang/test/CIR/CodeGen/label-values.c index 41178e3f62f20..2d773770910fe 100644 --- a/clang/test/CIR/CodeGen/label-values.c +++ b/clang/test/CIR/CodeGen/label-values.c @@ -3,38 +3,52 @@ void A(void) { void *ptr = &&LABEL_A; + goto *ptr; LABEL_A: return; } + // CIR: cir.func dso_local @A // CIR: [[PTR:%.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] {alignment = 8 : i64} // CIR: [[BLOCK:%.*]] = cir.block_address <@A, "LABEL_A"> : !cir.ptr<!void> // CIR: cir.store align(8) [[BLOCK]], [[PTR]] : !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>> -// CIR: cir.br ^bb1 -// CIR: ^bb1: // pred: ^bb0 +// CIR: [[BLOCKADD:%.*]] = cir.load align(8) [[PTR]] : !cir.ptr<!cir.ptr<!void>>, !cir.ptr<!void> +// CIR: cir.br ^bb1([[BLOCKADD]] : !cir.ptr<!void>) +// CIR: ^bb1([[PHI:%.*]]: !cir.ptr<!void> {{.*}}): // pred: ^bb0 +// CIR: cir.indirectbr [[PHI]] : <!void>, [ +// CIR: ^bb2 +// CIR: ] +// CIR: ^bb2: // pred: ^bb1 // CIR: cir.label "LABEL_A" // CIR: cir.return void B(void) { LABEL_B: void *ptr = &&LABEL_B; + goto *ptr; } // CIR: cir.func dso_local @B() // CIR: [[PTR:%.*]] = cir.alloca !cir.ptr<!void>, !cir.ptr<!cir.ptr<!void>>, ["ptr", init] {alignment = 8 : i64} // CIR: cir.br ^bb1 -// CIR: ^bb1: +// CIR: ^bb... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/169967 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
