[clang] [CIR] Upstream support for emitting ignored statements (PR #130869)
Andy Kaylor via cfe-commits
cfe-commits at lists.llvm.org
Wed Mar 12 11:46:49 PDT 2025
https://github.com/andykaylor updated https://github.com/llvm/llvm-project/pull/130869
>From a4e8aa13f97a6c73389822f6fdcf6f5970792462 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Tue, 11 Mar 2025 17:01:44 -0700
Subject: [PATCH 1/2] [CIR] Upstream support for emitting ignored statements
This adds support for emitting ClangIR for statements whose value
is ignored. The test case being added (CIR/CodeGen/basic.c) tests
a few more things. The "f1" test case is the only part that's
immediately relevant to this change, but the other cases were
part of the same test in the incubator and they are supported so
I brought in the entire test.
---
clang/include/clang/CIR/MissingFeatures.h | 1 +
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 27 ++++
clang/lib/CIR/CodeGen/CIRGenFunction.h | 10 ++
clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 152 +++++++++++++++++++++-
clang/test/CIR/CodeGen/basic.c | 53 ++++++++
5 files changed, 239 insertions(+), 4 deletions(-)
create mode 100644 clang/test/CIR/CodeGen/basic.c
diff --git a/clang/include/clang/CIR/MissingFeatures.h b/clang/include/clang/CIR/MissingFeatures.h
index ddfe654009644..e8d3eff79d0b2 100644
--- a/clang/include/clang/CIR/MissingFeatures.h
+++ b/clang/include/clang/CIR/MissingFeatures.h
@@ -83,6 +83,7 @@ struct MissingFeatures {
static bool emitNullabilityCheck() { return false; }
static bool astVarDeclInterface() { return false; }
static bool stackSaveOp() { return false; }
+ static bool aggValueSlot() { return false; }
};
} // namespace cir
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index 07fb4cf8f1513..a33aa45f8a4fc 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -165,6 +165,33 @@ LValue CIRGenFunction::emitDeclRefLValue(const DeclRefExpr *e) {
return LValue();
}
+/// Emit code to compute the specified expression which
+/// can have any type. The result is returned as an RValue struct.
+RValue CIRGenFunction::emitAnyExpr(const Expr *e, bool ignoreResult) {
+ switch (CIRGenFunction::getEvaluationKind(e->getType())) {
+ case cir::TEK_Scalar:
+ return RValue::get(emitScalarExpr(e));
+ case cir::TEK_Complex:
+ cgm.errorNYI(e->getSourceRange(), "emitAnyExpr: complex type");
+ return RValue::get(nullptr);
+ case cir::TEK_Aggregate:
+ cgm.errorNYI(e->getSourceRange(), "emitAnyExpr: aggregate type");
+ return RValue::get(nullptr);
+ }
+ llvm_unreachable("bad evaluation kind");
+}
+
+/// Emit code to compute the specified expression, ignoring the result.
+void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
+ if (e->isPRValue()) {
+ assert(!cir::MissingFeatures::aggValueSlot());
+ return (void)emitAnyExpr(e, true);
+ }
+
+ // Just emit it as an l-value and drop the result.
+ emitLValue(e);
+}
+
mlir::Value CIRGenFunction::emitAlloca(StringRef name, mlir::Type ty,
mlir::Location loc,
CharUnits alignment) {
diff --git a/clang/lib/CIR/CodeGen/CIRGenFunction.h b/clang/lib/CIR/CodeGen/CIRGenFunction.h
index 72445f62232a4..ddb1c1c5dd229 100644
--- a/clang/lib/CIR/CodeGen/CIRGenFunction.h
+++ b/clang/lib/CIR/CodeGen/CIRGenFunction.h
@@ -154,6 +154,12 @@ class CIRGenFunction : public CIRGenTypeCache {
const clang::LangOptions &getLangOpts() const { return cgm.getLangOpts(); }
+ /// Emit code to compute the specified expression which can have any type. The
+ /// result is returned as an RValue struct. If this is an aggregate
+ /// expression, the aggloc/agglocvolatile arguments indicate where the result
+ /// should be returned.
+ RValue emitAnyExpr(const clang::Expr *e, bool ignoreResult = false);
+
void finishFunction(SourceLocation endLoc);
mlir::LogicalResult emitFunctionBody(const clang::Stmt *body);
@@ -170,6 +176,10 @@ class CIRGenFunction : public CIRGenTypeCache {
void emitCompoundStmtWithoutScope(const clang::CompoundStmt &s);
+ /// Emit code to compute the specified expression,
+ /// ignoring the result.
+ void emitIgnoredExpr(const clang::Expr *e);
+
mlir::LogicalResult emitDeclStmt(const clang::DeclStmt &s);
mlir::LogicalResult emitReturnStmt(const clang::ReturnStmt &s);
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index ed5d87a39704a..91627d5d5985a 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -55,10 +55,154 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
if (mlir::succeeded(emitSimpleStmt(s, useCurrentScope)))
return mlir::success();
- // Only a subset of simple statements are supported at the moment. When more
- // kinds of statements are supported, a
- // switch (s->getStmtClass()) {
- // will be added here.
+ switch (s->getStmtClass()) {
+
+#define STMT(Type, Base)
+#define ABSTRACT_STMT(Op)
+#define EXPR(Type, Base) case Stmt::Type##Class:
+#include "clang/AST/StmtNodes.inc"
+ {
+ // Remember the block we came in on.
+ mlir::Block *incoming = builder.getInsertionBlock();
+ assert(incoming && "expression emission must have an insertion point");
+
+ emitIgnoredExpr(cast<Expr>(s));
+
+ mlir::Block *outgoing = builder.getInsertionBlock();
+ assert(outgoing && "expression emission cleared block!");
+ return mlir::success();
+ }
+
+ case Stmt::OMPScopeDirectiveClass:
+ case Stmt::OMPErrorDirectiveClass:
+ case Stmt::NoStmtClass:
+ case Stmt::CXXCatchStmtClass:
+ case Stmt::SEHExceptStmtClass:
+ case Stmt::SEHFinallyStmtClass:
+ case Stmt::MSDependentExistsStmtClass:
+ case Stmt::NullStmtClass:
+ case Stmt::CompoundStmtClass:
+ case Stmt::DeclStmtClass:
+ case Stmt::LabelStmtClass:
+ case Stmt::AttributedStmtClass:
+ case Stmt::GotoStmtClass:
+ case Stmt::BreakStmtClass:
+ case Stmt::ContinueStmtClass:
+ case Stmt::DefaultStmtClass:
+ case Stmt::CaseStmtClass:
+ case Stmt::SEHLeaveStmtClass:
+ case Stmt::SYCLKernelCallStmtClass:
+ case Stmt::IfStmtClass:
+ case Stmt::SwitchStmtClass:
+ case Stmt::ForStmtClass:
+ case Stmt::WhileStmtClass:
+ case Stmt::DoStmtClass:
+ case Stmt::CoroutineBodyStmtClass:
+ case Stmt::CoreturnStmtClass:
+ case Stmt::CXXTryStmtClass:
+ case Stmt::CXXForRangeStmtClass:
+ case Stmt::IndirectGotoStmtClass:
+ case Stmt::ReturnStmtClass:
+ case Stmt::GCCAsmStmtClass:
+ case Stmt::MSAsmStmtClass:
+ case Stmt::OMPParallelDirectiveClass:
+ case Stmt::OMPTaskwaitDirectiveClass:
+ case Stmt::OMPTaskyieldDirectiveClass:
+ case Stmt::OMPBarrierDirectiveClass:
+ case Stmt::CapturedStmtClass:
+ case Stmt::ObjCAtTryStmtClass:
+ case Stmt::ObjCAtThrowStmtClass:
+ case Stmt::ObjCAtSynchronizedStmtClass:
+ case Stmt::ObjCForCollectionStmtClass:
+ case Stmt::ObjCAutoreleasePoolStmtClass:
+ case Stmt::SEHTryStmtClass:
+ case Stmt::OMPMetaDirectiveClass:
+ case Stmt::OMPCanonicalLoopClass:
+ case Stmt::OMPSimdDirectiveClass:
+ case Stmt::OMPTileDirectiveClass:
+ case Stmt::OMPUnrollDirectiveClass:
+ case Stmt::OMPForDirectiveClass:
+ case Stmt::OMPForSimdDirectiveClass:
+ case Stmt::OMPSectionsDirectiveClass:
+ case Stmt::OMPSectionDirectiveClass:
+ case Stmt::OMPSingleDirectiveClass:
+ case Stmt::OMPMasterDirectiveClass:
+ case Stmt::OMPCriticalDirectiveClass:
+ case Stmt::OMPParallelForDirectiveClass:
+ case Stmt::OMPParallelForSimdDirectiveClass:
+ case Stmt::OMPParallelMasterDirectiveClass:
+ case Stmt::OMPParallelSectionsDirectiveClass:
+ case Stmt::OMPTaskDirectiveClass:
+ case Stmt::OMPTaskgroupDirectiveClass:
+ case Stmt::OMPFlushDirectiveClass:
+ case Stmt::OMPDepobjDirectiveClass:
+ case Stmt::OMPScanDirectiveClass:
+ case Stmt::OMPOrderedDirectiveClass:
+ case Stmt::OMPAtomicDirectiveClass:
+ case Stmt::OMPTargetDirectiveClass:
+ case Stmt::OMPTeamsDirectiveClass:
+ case Stmt::OMPCancellationPointDirectiveClass:
+ case Stmt::OMPCancelDirectiveClass:
+ case Stmt::OMPTargetDataDirectiveClass:
+ case Stmt::OMPTargetEnterDataDirectiveClass:
+ case Stmt::OMPTargetExitDataDirectiveClass:
+ case Stmt::OMPTargetParallelDirectiveClass:
+ case Stmt::OMPTargetParallelForDirectiveClass:
+ case Stmt::OMPTaskLoopDirectiveClass:
+ case Stmt::OMPTaskLoopSimdDirectiveClass:
+ case Stmt::OMPMaskedTaskLoopDirectiveClass:
+ case Stmt::OMPMaskedTaskLoopSimdDirectiveClass:
+ case Stmt::OMPMasterTaskLoopDirectiveClass:
+ case Stmt::OMPMasterTaskLoopSimdDirectiveClass:
+ case Stmt::OMPParallelGenericLoopDirectiveClass:
+ case Stmt::OMPParallelMaskedDirectiveClass:
+ case Stmt::OMPParallelMaskedTaskLoopDirectiveClass:
+ case Stmt::OMPParallelMaskedTaskLoopSimdDirectiveClass:
+ case Stmt::OMPParallelMasterTaskLoopDirectiveClass:
+ case Stmt::OMPParallelMasterTaskLoopSimdDirectiveClass:
+ case Stmt::OMPDistributeDirectiveClass:
+ case Stmt::OMPDistributeParallelForDirectiveClass:
+ case Stmt::OMPDistributeParallelForSimdDirectiveClass:
+ case Stmt::OMPDistributeSimdDirectiveClass:
+ case Stmt::OMPTargetParallelGenericLoopDirectiveClass:
+ case Stmt::OMPTargetParallelForSimdDirectiveClass:
+ case Stmt::OMPTargetSimdDirectiveClass:
+ case Stmt::OMPTargetTeamsGenericLoopDirectiveClass:
+ case Stmt::OMPTargetUpdateDirectiveClass:
+ case Stmt::OMPTeamsDistributeDirectiveClass:
+ case Stmt::OMPTeamsDistributeSimdDirectiveClass:
+ case Stmt::OMPTeamsDistributeParallelForSimdDirectiveClass:
+ case Stmt::OMPTeamsDistributeParallelForDirectiveClass:
+ case Stmt::OMPTeamsGenericLoopDirectiveClass:
+ case Stmt::OMPTargetTeamsDirectiveClass:
+ case Stmt::OMPTargetTeamsDistributeDirectiveClass:
+ case Stmt::OMPTargetTeamsDistributeParallelForDirectiveClass:
+ case Stmt::OMPTargetTeamsDistributeParallelForSimdDirectiveClass:
+ case Stmt::OMPTargetTeamsDistributeSimdDirectiveClass:
+ case Stmt::OMPInteropDirectiveClass:
+ case Stmt::OMPDispatchDirectiveClass:
+ case Stmt::OMPGenericLoopDirectiveClass:
+ case Stmt::OMPReverseDirectiveClass:
+ case Stmt::OMPInterchangeDirectiveClass:
+ case Stmt::OMPAssumeDirectiveClass:
+ case Stmt::OMPMaskedDirectiveClass:
+ case Stmt::OpenACCComputeConstructClass:
+ case Stmt::OpenACCLoopConstructClass:
+ case Stmt::OpenACCCombinedConstructClass:
+ case Stmt::OpenACCDataConstructClass:
+ case Stmt::OpenACCEnterDataConstructClass:
+ case Stmt::OpenACCExitDataConstructClass:
+ case Stmt::OpenACCHostDataConstructClass:
+ case Stmt::OpenACCWaitConstructClass:
+ case Stmt::OpenACCInitConstructClass:
+ case Stmt::OpenACCShutdownConstructClass:
+ case Stmt::OpenACCSetConstructClass:
+ case Stmt::OpenACCUpdateConstructClass:
+ case Stmt::ObjCAtCatchStmtClass:
+ case Stmt::ObjCAtFinallyStmtClass:
+ cgm.errorNYI(s->getSourceRange(),
+ std::string("emitStmt: ") + s->getStmtClassName());
+ }
return mlir::failure();
}
diff --git a/clang/test/CIR/CodeGen/basic.c b/clang/test/CIR/CodeGen/basic.c
new file mode 100644
index 0000000000000..8d067aefded66
--- /dev/null
+++ b/clang/test/CIR/CodeGen/basic.c
@@ -0,0 +1,53 @@
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
+// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
+
+int f1(int i);
+
+int f1(int i) {
+ i;
+ return i;
+}
+
+// CIR: module
+// CIR-NEXT: cir.func @f1(%arg0: !cir.int<s, 32> loc({{.*}})) -> !cir.int<s, 32>
+// CIR-NEXT: %[[I_PTR:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["i", init] {alignment = 4 : i64}
+// CIR-NEXT: cir.store %arg0, %[[I_PTR]] : !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>
+// CIR-NEXT: %[[I_IGNORED:.*]] = cir.load %[[I_PTR]] : !cir.ptr<!cir.int<s, 32>>, !cir.int<s, 32>
+// CIR-NEXT: %[[I:.*]] = cir.load %[[I_PTR]] : !cir.ptr<!cir.int<s, 32>>, !cir.int<s, 32>
+// CIR-NEXT: cir.return %[[I]] : !cir.int<s, 32>
+
+// LLVM: define i32 @f1(i32 %[[I:.*]])
+// LLVM-NEXT: %[[I_PTR:.*]] = alloca i32, i64 1, align 4
+// LLVM-NEXT: store i32 %[[I]], ptr %[[I_PTR]], align 4
+// LLVM-NEXT: %[[I_IGNORED:.*]] = load i32, ptr %[[I_PTR]], align 4
+// LLVM-NEXT: %[[I:.*]] = load i32, ptr %[[I_PTR]], align 4
+// LLVM-NEXT: ret i32 %[[I]]
+
+int f2(void) { return 3; }
+
+// CIR: cir.func @f2() -> !cir.int<s, 32>
+// CIR-NEXT: %[[THREE:.*]] = cir.const #cir.int<3> : !cir.int<s, 32>
+// CIR-NEXT: cir.return %[[THREE]] : !cir.int<s, 32>
+
+// LLVM: define i32 @f2()
+// LLVM-NEXT: ret i32 3
+
+int f3(void) {
+ int i = 3;
+ return i;
+}
+
+// CIR: cir.func @f3() -> !cir.int<s, 32>
+// CIR-NEXT: %[[I_PTR:.*]] = cir.alloca !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>, ["i", init] {alignment = 4 : i64}
+// CIR-NEXT: %[[THREE:.*]] = cir.const #cir.int<3> : !cir.int<s, 32>
+// CIR-NEXT: cir.store %[[THREE]], %[[I_PTR]] : !cir.int<s, 32>, !cir.ptr<!cir.int<s, 32>>
+// CIR-NEXT: %[[I:.*]] = cir.load %[[I_PTR]] : !cir.ptr<!cir.int<s, 32>>, !cir.int<s, 32>
+// CIR-NEXT: cir.return %[[I]] : !cir.int<s, 32>
+
+// LLVM: define i32 @f3()
+// LLVM-NEXT: %[[I_PTR:.*]] = alloca i32, i64 1, align 4
+// LLVM-NEXT: store i32 3, ptr %[[I_PTR]], align 4
+// LLVM-NEXT: %[[I:.*]] = load i32, ptr %[[I_PTR]], align 4
+// LLVM-NEXT: ret i32 %[[I]]
>From cf692675df6f83a0b799666e5bd637ee8d7e7188 Mon Sep 17 00:00:00 2001
From: Andy Kaylor <akaylor at nvidia.com>
Date: Wed, 12 Mar 2025 11:45:37 -0700
Subject: [PATCH 2/2] Add test checks for classic codegen, minor cleanup
---
clang/lib/CIR/CodeGen/CIRGenExpr.cpp | 3 ++-
clang/lib/CIR/CodeGen/CIRGenStmt.cpp | 7 ++++++-
clang/test/CIR/CodeGen/basic.c | 25 +++++++++++++++++++++++--
3 files changed, 31 insertions(+), 4 deletions(-)
diff --git a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
index a33aa45f8a4fc..2d9ec41452a9d 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -185,7 +185,8 @@ RValue CIRGenFunction::emitAnyExpr(const Expr *e, bool ignoreResult) {
void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
if (e->isPRValue()) {
assert(!cir::MissingFeatures::aggValueSlot());
- return (void)emitAnyExpr(e, true);
+ emitAnyExpr(e, true);
+ return;
}
// Just emit it as an l-value and drop the result.
diff --git a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
index 91627d5d5985a..d4476a5e9fa17 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -186,6 +186,7 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
case Stmt::OMPInterchangeDirectiveClass:
case Stmt::OMPAssumeDirectiveClass:
case Stmt::OMPMaskedDirectiveClass:
+ case Stmt::OMPStripeDirectiveClass:
case Stmt::OpenACCComputeConstructClass:
case Stmt::OpenACCLoopConstructClass:
case Stmt::OpenACCCombinedConstructClass:
@@ -198,12 +199,16 @@ mlir::LogicalResult CIRGenFunction::emitStmt(const Stmt *s,
case Stmt::OpenACCShutdownConstructClass:
case Stmt::OpenACCSetConstructClass:
case Stmt::OpenACCUpdateConstructClass:
+ case Stmt::OpenACCCacheConstructClass:
+ case Stmt::OpenACCAtomicConstructClass:
case Stmt::ObjCAtCatchStmtClass:
case Stmt::ObjCAtFinallyStmtClass:
cgm.errorNYI(s->getSourceRange(),
std::string("emitStmt: ") + s->getStmtClassName());
+ return mlir::failure();
}
- return mlir::failure();
+
+ llvm_unreachable("Unexpected statement class");
}
mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
diff --git a/clang/test/CIR/CodeGen/basic.c b/clang/test/CIR/CodeGen/basic.c
index 8d067aefded66..754f11f1361ba 100644
--- a/clang/test/CIR/CodeGen/basic.c
+++ b/clang/test/CIR/CodeGen/basic.c
@@ -1,7 +1,9 @@
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-cir %s -o %t.cir
// RUN: FileCheck --input-file=%t.cir %s -check-prefix=CIR
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t.ll
-// RUN: FileCheck --input-file=%t.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -fclangir -emit-llvm %s -o %t-cir.ll
+// RUN: FileCheck --input-file=%t-cir.ll %s -check-prefix=LLVM
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-unused-value -emit-llvm %s -o %t.ll
+// RUN: FileCheck --input-file=%t.ll %s -check-prefix=OGCG
int f1(int i);
@@ -25,6 +27,14 @@ int f1(int i) {
// LLVM-NEXT: %[[I:.*]] = load i32, ptr %[[I_PTR]], align 4
// LLVM-NEXT: ret i32 %[[I]]
+// OGCG: define{{.*}} i32 @f1(i32 noundef %[[I:.*]])
+// OGCG-NEXT: entry:
+// OGCG-NEXT: %[[I_PTR:.*]] = alloca i32, align 4
+// OGCG-NEXT: store i32 %[[I]], ptr %[[I_PTR]], align 4
+// OGCG-NEXT: %[[I_IGNORED:.*]] = load i32, ptr %[[I_PTR]], align 4
+// OGCG-NEXT: %[[I:.*]] = load i32, ptr %[[I_PTR]], align 4
+// OGCG-NEXT: ret i32 %[[I]]
+
int f2(void) { return 3; }
// CIR: cir.func @f2() -> !cir.int<s, 32>
@@ -34,6 +44,10 @@ int f2(void) { return 3; }
// LLVM: define i32 @f2()
// LLVM-NEXT: ret i32 3
+// OGCG: define{{.*}} i32 @f2()
+// OGCG-NEXT: entry:
+// OGCG-NEXT: ret i32 3
+
int f3(void) {
int i = 3;
return i;
@@ -51,3 +65,10 @@ int f3(void) {
// LLVM-NEXT: store i32 3, ptr %[[I_PTR]], align 4
// LLVM-NEXT: %[[I:.*]] = load i32, ptr %[[I_PTR]], align 4
// LLVM-NEXT: ret i32 %[[I]]
+
+// OGCG: define{{.*}} i32 @f3
+// OGCG-NEXT: entry:
+// OGCG-NEXT: %[[I_PTR:.*]] = alloca i32, align 4
+// OGCG-NEXT: store i32 3, ptr %[[I_PTR]], align 4
+// OGCG-NEXT: %[[I:.*]] = load i32, ptr %[[I_PTR]], align 4
+// OGCG-NEXT: ret i32 %[[I]]
More information about the cfe-commits
mailing list