llvmorg-github-actions[bot] wrote:

<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clangir

Author: Adam Smith (adams381)

<details>
<summary>Changes</summary>

When a record's low eightbyte holds no field, the classifier passes it in one 
register read from byte 8 rather than byte 0. CallConvLowering had no way to 
represent that, so `convertABIArgInfo` rejected the whole signature as NYI.

`ArgClassification` now carries the offset and `emitCoercionToMemory` reads and 
writes the coerced scalar through a `u8` `cir.ptr_stride` at that byte. The 
offset-zero path is untouched.

A register-tuple coercion at an offset is refused rather than read from the 
wrong bytes, a pair SysV cannot currently produce.

Supersedes #<!-- -->203640, which had the coercion mechanism but nothing 
calling it.

Assisted-by: Cursor / claude-opus-5


---

Patch is 28.70 KiB, truncated to 20.00 KiB below, full version: 
https://github.com/llvm/llvm-project/pull/220724.diff


6 Files Affected:

- (modified) clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp (+26-16) 
- (modified) 
clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp 
(+80-33) 
- (modified) clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp (+31) 
- (modified) clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir 
(-10) 
- (added) 
clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct-offset.cir (+123) 
- (modified) mlir/include/mlir/ABI/ABIRewriteContext.h (+15-2) 


``````````diff
diff --git a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp 
b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
index 233b826853f76..242fda30343a1 100644
--- a/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/CallConvLoweringPass.cpp
@@ -473,14 +473,16 @@ static const llvm::abi::Type *mapCIRType(mlir::Type type,
 /// CIRABIRewriteContext.
 ///
 /// Direct: the value passes in register(s).  A coercion is forwarded in the
-/// three cases where the value has to be rebuilt on the wire: an aggregate
+/// four cases where the value has to be rebuilt on the wire: an aggregate
 /// unpacked into the register(s) holding it, a scalar too wide for one 
register
-/// split into a tuple of them, and a scalar the classifier widens to fill its
-/// eightbyte.  getDirect keeps canFlatten set so the rewriter can split a
-/// multi-field coerced struct into individual wire arguments.  Any other 
scalar
-/// passes in its natural CIR type, which a null coercion denotes.  A coercion
-/// this bridge cannot represent yields std::nullopt so the caller reports NYI
-/// rather than silently passing the value unchanged.
+/// split into a tuple of them, a scalar the classifier widens to fill its
+/// eightbyte, and a value whose live bytes start partway into its storage
+/// because a leading eightbyte holds no field (getDirectOffset).  getDirect
+/// keeps canFlatten set so the rewriter can split a multi-field coerced
+/// struct into individual wire arguments.  Any other scalar passes in its
+/// natural CIR type, which a null coercion denotes.  A coercion this bridge
+/// cannot represent yields std::nullopt so the caller reports NYI rather than
+/// silently passing the value unchanged.
 ///
 /// Extend: bool or a sub-register integer needs a signext/zeroext attribute.
 /// The x86_64 classifier (llvm/lib/ABI/Targets/X86.cpp) only returns Extend
@@ -495,11 +497,7 @@ static std::optional<ArgClassification>
 convertABIArgInfo(const llvm::abi::ArgInfo &info, MLIRContext *ctx,
                   mlir::Type origTy) {
   if (info.isDirect()) {
-    // The rewriter reads a coercion from the start of the value's storage, so 
a
-    // classification naming an offset into it has no representation here.  The
-    // classifier names one when the low eightbyte holds no field.
-    if (info.getDirectOffset())
-      return std::nullopt;
+    unsigned offset = info.getDirectOffset();
     // The classifier names a coerce type even where it matches the natural
     // type, so a non-null coerce does not by itself mean a rewrite is needed.
     const llvm::abi::Type *coerceAbi = info.getCoerceToType();
@@ -510,6 +508,10 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, 
MLIRContext *ctx,
         coerceAbi && isa_and_present<cir::ComplexType, 
cir::VectorType>(origTy);
     bool coerceIsRegisterTuple =
         isa_and_present<llvm::abi::RecordType>(coerceAbi);
+    // A tuple coercion cannot be read at an offset, so refuse the pair rather
+    // than emit a read of the wrong bytes.
+    if (offset && coerceIsRegisterTuple)
+      return std::nullopt;
     // Compare widths rather than identity: a coerce no wider than the natural
     // type carries the same value and needs no rewrite.
     auto origInt = dyn_cast_if_present<cir::IntType>(origTy);
@@ -520,19 +522,27 @@ convertABIArgInfo(const llvm::abi::ArgInfo &info, 
MLIRContext *ctx,
         coerceInt->getSizeInBits().getFixedValue() > origInt.getWidth();
     // Leaving the rest alone also avoids a lossy round trip: abiTypeToCIR
     // drops the LongDoubleType wrapper and a pointer's pointee, so comparing a
-    // scalar against its own coerce would report a difference that is not one.
-    if (!isAggregate && !comparesAgainstCoerce && !coerceIsRegisterTuple &&
-        !coerceWidensScalar)
+    // scalar against its own coerce would report a difference that is not
+    // one.  Passing a value through reads it from byte 0, so one living at
+    // an offset cannot take this path.
+    if (!offset && !isAggregate && !comparesAgainstCoerce &&
+        !coerceIsRegisterTuple && !coerceWidensScalar)
       return ArgClassification::getDirect(nullptr);
     mlir::Type coerced = abiTypeToCIR(coerceAbi, ctx);
     if (!coerced)
       return std::nullopt;
+    // An offset would be lost here, since a coercion to the value's own type
+    // is indistinguishable from no coercion at all.
+    if (offset && coerced == origTy)
+      return std::nullopt;
     // Coercing a value to the type it already has would add a memory round
     // trip for nothing.
     if (comparesAgainstCoerce && coerced == origTy)
       return ArgClassification::getDirect(nullptr);
-    return ArgClassification::getDirect(coerced);
+    return ArgClassification::getDirect(coerced, offset);
   }
+  // An extended value is always read from byte 0 of its own storage, so
+  // there is no offset to honor here.
   if (info.isExtend()) {
     if (isa_and_present<cir::BoolType>(origTy))
       return ArgClassification::getExtend(nullptr, info.isSignExt());
diff --git 
a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp 
b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
index 8cf332bbbefa3..91efb28625f0f 100644
--- a/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
+++ b/clang/lib/CIR/Dialect/Transforms/TargetLowering/CIRABIRewriteContext.cpp
@@ -311,14 +311,21 @@ static uint64_t coercionByteSize(mlir::Type ty, const 
mlir::DataLayout &dl) {
 /// dominate every use of the coerced value and must be a block that ends up
 /// inside the enclosing function's entry block after any later outlining.
 ///
+/// \p offset is where the coerced value sits within the larger of the two
+/// types, non-zero when the ABI passes a value in a register read from
+/// partway into its storage.  The coerced type must be the smaller of the
+/// two, so one that can exceed the value it coerces, such as a multi-field
+/// register tuple, must not be given an offset.
+///
 /// Any operations the helper creates are appended to \p createdOps so the
 /// caller can pass them to replaceAllUsesExcept and avoid clobbering the
 /// store's value operand when later rewiring the source value.
-mlir::Value
-emitCoercionToMemory(mlir::OpBuilder &builder, mlir::Location loc,
-                     mlir::Type dstTy, mlir::Value src, mlir::Block *slotBlock,
-                     const mlir::DataLayout &dl,
-                     SmallPtrSetImpl<mlir::Operation *> &createdOps) {
+mlir::Value emitCoercionToMemory(mlir::OpBuilder &builder, mlir::Location loc,
+                                 mlir::Type dstTy, mlir::Value src,
+                                 mlir::Block *slotBlock,
+                                 const mlir::DataLayout &dl,
+                                 SmallPtrSetImpl<mlir::Operation *> 
&createdOps,
+                                 unsigned offset = 0) {
   mlir::Type srcTy = src.getType();
   assert(srcTy != dstTy &&
          "emitCoercion callers must pre-check that the types differ");
@@ -330,6 +337,19 @@ emitCoercionToMemory(mlir::OpBuilder &builder, 
mlir::Location loc,
                           ? srcTy
                           : dstTy;
 
+  // Sizes are compared two ways on purpose: relative size in
+  // coercionByteSize terms, which is how slotTy was picked, and capacity in
+  // getTypeSize terms, which is what the alloca is given.
+  [[maybe_unused]] mlir::Type coercedTy = slotTy == srcTy ? dstTy : srcTy;
+  assert((offset == 0 ||
+          coercionByteSize(coercedTy, dl) < coercionByteSize(slotTy, dl)) &&
+         "a direct offset must land on the coerced side, the smaller one");
+  assert((offset == 0 ||
+          offset + dl.getTypeSize(coercedTy) <= dl.getTypeSize(slotTy)) &&
+         "coerce slot too small for offset access");
+  assert((offset == 0 || offset % dl.getTypeABIAlignment(coercedTy) == 0) &&
+         "a direct offset must be aligned for the coerced access");
+
   auto slotPtrTy = cir::PointerType::get(slotTy);
   auto srcPtrTy = cir::PointerType::get(srcTy);
   auto dstPtrTy = cir::PointerType::get(dstTy);
@@ -344,25 +364,42 @@ emitCoercionToMemory(mlir::OpBuilder &builder, 
mlir::Location loc,
   }
   createdOps.insert(alloca);
 
+  // The alloca already has slotTy, so asking for that type returns it
+  // unchanged.  Any other type is reached by a bitcast, preceded by a byte
+  // stride when the coerced value lives at an offset.
+  auto slotView = [&](mlir::Type wantTy,
+                      cir::PointerType wantPtrTy) -> mlir::Value {
+    if (wantTy == slotTy)
+      return alloca;
+    mlir::Value base = alloca;
+    if (offset != 0) {
+      auto u8Ty =
+          cir::IntType::get(builder.getContext(), 8, /*isSigned=*/false);
+      auto u8PtrTy = cir::PointerType::get(u8Ty);
+      auto u8Base = cir::CastOp::create(builder, loc, u8PtrTy,
+                                        cir::CastKind::bitcast, alloca);
+      createdOps.insert(u8Base);
+      auto strideTy =
+          cir::IntType::get(builder.getContext(), 64, /*isSigned=*/true);
+      auto strideVal = cir::ConstantOp::create(
+          builder, loc, cir::IntAttr::get(strideTy, offset));
+      createdOps.insert(strideVal);
+      base = cir::PtrStrideOp::create(builder, loc, u8PtrTy, u8Base, 
strideVal);
+      createdOps.insert(base.getDefiningOp());
+    }
+    auto cast = cir::CastOp::create(builder, loc, wantPtrTy,
+                                    cir::CastKind::bitcast, base);
+    createdOps.insert(cast);
+    return cast;
+  };
+
   // Store through a source-typed view of the slot.
-  mlir::Value srcSlot = alloca;
-  if (slotTy != srcTy) {
-    auto srcCast = cir::CastOp::create(builder, loc, srcPtrTy,
-                                       cir::CastKind::bitcast, alloca);
-    createdOps.insert(srcCast);
-    srcSlot = srcCast;
-  }
+  mlir::Value srcSlot = slotView(srcTy, srcPtrTy);
   auto store = cir::StoreOp::create(builder, loc, src, srcSlot);
   createdOps.insert(store);
 
   // Return a destination-typed view of the slot.
-  if (slotTy != dstTy) {
-    auto dstCast = cir::CastOp::create(builder, loc, dstPtrTy,
-                                       cir::CastKind::bitcast, alloca);
-    createdOps.insert(dstCast);
-    return dstCast;
-  }
-  return alloca;
+  return slotView(dstTy, dstPtrTy);
 }
 
 /// Coerce \p src to type \p dstTy by going through memory and load the whole
@@ -371,9 +408,10 @@ emitCoercionToMemory(mlir::OpBuilder &builder, 
mlir::Location loc,
 mlir::Value emitCoercion(mlir::OpBuilder &builder, mlir::Location loc,
                          mlir::Type dstTy, mlir::Value src,
                          mlir::Block *slotBlock, const mlir::DataLayout &dl,
-                         SmallPtrSetImpl<mlir::Operation *> &createdOps) {
-  mlir::Value dstSlot =
-      emitCoercionToMemory(builder, loc, dstTy, src, slotBlock, dl, 
createdOps);
+                         SmallPtrSetImpl<mlir::Operation *> &createdOps,
+                         unsigned offset = 0) {
+  mlir::Value dstSlot = emitCoercionToMemory(builder, loc, dstTy, src,
+                                             slotBlock, dl, createdOps, 
offset);
   auto load = cir::LoadOp::create(builder, loc, dstSlot);
   createdOps.insert(load);
   return load;
@@ -383,9 +421,10 @@ mlir::Value emitCoercion(mlir::OpBuilder &builder, 
mlir::Location loc,
 /// (e.g. call-site coercion where we don't replaceAllUsesExcept).
 mlir::Value emitCoercion(mlir::OpBuilder &builder, mlir::Location loc,
                          mlir::Type dstTy, mlir::Value src,
-                         mlir::Block *slotBlock, const mlir::DataLayout &dl) {
+                         mlir::Block *slotBlock, const mlir::DataLayout &dl,
+                         unsigned offset = 0) {
   SmallPtrSet<mlir::Operation *, 4> ignored;
-  return emitCoercion(builder, loc, dstTy, src, slotBlock, dl, ignored);
+  return emitCoercion(builder, loc, dstTy, src, slotBlock, dl, ignored, 
offset);
 }
 
 /// The block a coercion slot's alloca belongs at the start of.
@@ -414,8 +453,8 @@ mlir::Block *coercionSlotBlock(mlir::Operation *op) {
 /// new (coerced) return type.
 void insertReturnCoercion(mlir::FunctionOpInterface funcOp,
                           mlir::Type origRetTy, mlir::Type coercedRetTy,
-                          mlir::OpBuilder &builder,
-                          const mlir::DataLayout &dl) {
+                          mlir::OpBuilder &builder, const mlir::DataLayout &dl,
+                          unsigned offset) {
   SmallVector<cir::ReturnOp> returns;
   funcOp.walk([&](cir::ReturnOp r) { returns.push_back(r); });
   for (cir::ReturnOp r : returns) {
@@ -427,7 +466,7 @@ void insertReturnCoercion(mlir::FunctionOpInterface funcOp,
     builder.setInsertionPoint(r);
     mlir::Value coerced =
         emitCoercion(builder, r.getLoc(), coercedRetTy, origVal,
-                     &funcOp->getRegion(0).front(), dl);
+                     &funcOp->getRegion(0).front(), dl, offset);
     r->setOperand(0, coerced);
   }
 }
@@ -650,6 +689,9 @@ void insertArgCoercion(mlir::FunctionOpInterface funcOp,
       Value finalVal = flatLoaded;
       if (origTy != flatTy) {
         SmallPtrSet<Operation *, 4> coercionOps;
+        assert(!ac.directOffset &&
+               "each field is read from slot offset 0 here, so a flattened "
+               "coercion cannot honor a direct offset");
         finalVal = emitCoercion(builder, loc, origTy, flatLoaded, &entry, dl,
                                 coercionOps);
         flattenOps.insert(coercionOps.begin(), coercionOps.end());
@@ -674,8 +716,9 @@ void insertArgCoercion(mlir::FunctionOpInterface funcOp,
 
       builder.setInsertionPointToStart(&entry);
       SmallPtrSet<mlir::Operation *, 4> coercionOps;
-      mlir::Value adapted = emitCoercion(builder, funcOp.getLoc(), oldArgTy,
-                                         blockArg, &entry, dl, coercionOps);
+      mlir::Value adapted =
+          emitCoercion(builder, funcOp.getLoc(), oldArgTy, blockArg, &entry, 
dl,
+                       coercionOps, ac.directOffset);
 
       // Replace blockArg uses with the adapted value, except inside the
       // helper ops we just created.  This is critical: the StoreOp's value
@@ -1062,7 +1105,7 @@ mlir::LogicalResult 
CIRABIRewriteContext::rewriteFunctionDefinition(
       if (fc.returnInfo.kind == ArgKind::Direct && fc.returnInfo.coercedType &&
           !oldResultTypes.empty() && fc.returnInfo.coercedType != origRetTy)
         insertReturnCoercion(funcOp, origRetTy, fc.returnInfo.coercedType,
-                             builder, dl);
+                             builder, dl, fc.returnInfo.directOffset);
 
       mlir::Block &entry = body.front();
 
@@ -1224,6 +1267,9 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation 
*callOp,
       // alloca when possible).
       if (arg.getType() != flatTy) {
         SmallPtrSet<mlir::Operation *, 4> coercionOps;
+        assert(!ac.directOffset &&
+               "each field is read from slot offset 0 here, so a flattened "
+               "coercion cannot honor a direct offset");
         mlir::Value coercedPtr = emitCoercionToMemory(
             builder, call.getLoc(), flatTy, arg, slotBlock, dl, coercionOps);
         for (auto [f, fieldTy] : llvm::enumerate(flatTy.getMembers())) {
@@ -1249,7 +1295,7 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation 
*callOp,
     } else if (ac.kind == ArgKind::Direct && ac.coercedType &&
                arg.getType() != ac.coercedType) {
       arg = emitCoercion(builder, call.getLoc(), ac.coercedType, arg, 
slotBlock,
-                         dl);
+                         dl, ac.directOffset);
       newArgs.push_back(arg);
     } else if (ac.kind == ArgKind::Indirect) {
       // byval hands the callee its own copy.  byref must name the caller's
@@ -1317,8 +1363,9 @@ CIRABIRewriteContext::rewriteCallSite(mlir::Operation 
*callOp,
   // emit a coercion back to the original type for the call's existing uses.
   if (returnNeedsCoercion) {
     builder.setInsertionPointAfter(newCall);
-    mlir::Value coercedBack = emitCoercion(builder, call.getLoc(), origRetTy,
-                                           newCall.getResult(), slotBlock, dl);
+    mlir::Value coercedBack =
+        emitCoercion(builder, call.getLoc(), origRetTy, newCall.getResult(),
+                     slotBlock, dl, fc.returnInfo.directOffset);
     call.getResult().replaceAllUsesWith(coercedBack);
   }
 
diff --git a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp 
b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
index 30999935ef9d0..8fa7fefec41f6 100644
--- a/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
+++ b/clang/test/CIR/CodeGen/call-conv-lowering-x86_64-empty.cpp
@@ -24,6 +24,7 @@ struct EmptySecond { long a; Empty e; };
 struct EmptySSE { double a; Empty e; };
 struct FloatEmpty { float a; Empty e; };
 struct FloatEmptyFirst { Empty e; float a; };
+struct HiWord { Empty e; long hi; };
 struct alignas(32) Big32 {};
 union UBits { unsigned : 3; };
 union UNone {};
@@ -145,6 +146,36 @@ float takeFloatEmptyFirst(FloatEmptyFirst v) { return v.a; 
}
 // CIR: cir.func {{.*}}@_Z19takeFloatEmptyFirst15FloatEmptyFirst(%arg0: 
!cir.double {{.*}}) -> (!cir.float
 // LLVM: define dso_local noundef float 
@_Z19takeFloatEmptyFirst15FloatEmptyFirst(double %{{[^,]+}})
 
+// Here the empty member owns eightbyte 0 rather than eightbyte 1, so NoClass
+// cannot just be dropped: the coercion has to start at byte 8.
+long takeHiWord(HiWord v) { return v.hi; }
+
+// CIR: cir.func {{.*}}@_Z10takeHiWord6HiWord(%arg0: !s64i {{.*}}) -> (!s64i
+// LLVM: define dso_local noundef i64 @_Z10takeHiWord6HiWord(i64 %{{[^,]+}})
+// LLVM:   %{{.+}} = getelementptr{{( inbounds)?}} i8, ptr %{{.+}}, i64 8
+// LLVM:   store i64 %{{.+}}, ptr %{{.+}}, align 8
+
+// The same offset on the return side.
+HiWord giveHiWord(long hi) {
+  HiWord w;
+  w.hi = hi;
+  return w;
+}
+
+// CIR: cir.func {{.*}}@_Z10giveHiWordl(%arg0: !s64i {llvm.noundef} {{.*}}) -> 
!s64i
+// LLVM: define dso_local i64 @_Z10giveHiWordl(i64 noundef %{{[^,]+}})
+// LLVM:   %[[RGEP:.+]] = getelementptr{{( inbounds)?}} i8, ptr %{{.+}}, i64 8
+// LLVM:   %[[RVAL:.+]] = load i64, ptr %[[RGEP]], align 8
+// LLVM:   ret i64 %[[RVAL]]
+
+// The caller's own signature is unaffected by the callee's coercion.
+long callerHiWord(long hi) {
+  return takeHiWord(giveHiWord(hi));
+}
+
+// CIR: cir.func {{.*}}@_Z12callerHiWordl(%arg0: !s64i {{.*}}) -> (!s64i
+// LLVM: define dso_local noundef i64 @_Z12callerHiWordl(i64 noundef 
%{{[^,]+}})
+
 // Past two eightbytes SysV says memory whatever the content, so an empty class
 // this size is passed indirectly at its declared alignment.
 int takeBig32(Big32 v, int k) { return k; }
diff --git a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
index 7da3a76abc494..b9e2d795e70b4 100644
--- a/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
+++ b/clang/test/CIR/Transforms/abi-lowering/x86_64-aggregate-nyi.cir
@@ -33,7 +33,6 @@
 !rec_HoldsAllPad = !cir.struct<"HoldsAllPad" {data !s32i, empty !rec_E}>
 !rec_EBits = !cir.struct<"EBits" {empty !u32i}>
 !rec_HoldsEmptyBits = !cir.struct<"HoldsEmptyBits" {empty !rec_EBits, data 
!s32i}>
-!rec_LeadPad = !cir.struct<"LeadPad" {pad !cir.array<!u8i x 8>, data !s32i}>
 !rec_Incomplete = !cir.struct<"Incomplete" incomplete>
 
 module attributes {
@@ -196,15 +195,6 @@ module attributes {
 
   // CHECK: not yet implemented for type '!cir.struct<"HoldsEmptyBits" {empty
 
-  // With no field in the low eightbyte the classifier coerces the high one and
-  // names an offset of 8 into the record, which the rewriter cannot express: 
it
-  // reads a coercion from offset 0, so the argument would land in the padding.
-  cir.func @take_lead_pad(%arg0: !rec_LeadPad) {
-    cir.return
-  }
-
-  // CHECK: not yet implemented for the ABI coercion of type 
'!cir.struct<"LeadPad"
-
   // A scalable vector has no size the eightbyte rules can read.
   cir.func @take_scalable(%arg0: !cir.vector<[4] x !cir.float>) {
     cir.return
diff --git 
a/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct-offset.cir 
b/clang/test/CIR/Transforms/abi-lowering/x86_64-struct-direct-offset.cir
new file mode 100644
index 0000000000000..07132c8c...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/220724
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to