Repository: llvm/llvm-project
Pull request #130869: [CIR] Upstream support for emitting ignored statements
Author: login: andykaylor
Labels: name: clang
description: Clang issues not falling into any other category
name: ClangIR
description: Anything related to the ClangIR project
Created: 2025-03-12T00:17:35Z | Merged: 2025-03-12T21:38:19Z

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.


Diff:
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..5b81fe172e645 100644
--- a/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenExpr.cpp
@@ -165,6 +165,34 @@ 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) {
+  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());
+    emitAnyExpr(e);
+    return;
+  }
+
+  // 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..5ab882666f3e0 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);
+
   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..7a38f9838b290 100644
--- a/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenStmt.cpp
@@ -55,11 +55,160 @@ 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.
-  return mlir::failure();
+  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::OMPStripeDirectiveClass:
+  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::OpenACCCacheConstructClass:
+  case Stmt::OpenACCAtomicConstructClass:
+  case Stmt::ObjCAtCatchStmtClass:
+  case Stmt::ObjCAtFinallyStmtClass:
+    cgm.errorNYI(s->getSourceRange(),
+                 std::string("emitStmt: ") + s->getStmtClassName());
+    return mlir::failure();
+  }
+
+  llvm_unreachable("Unexpected statement class");
 }
 
 mlir::LogicalResult CIRGenFunction::emitSimpleStmt(const Stmt *s,
@@ -106,16 +255,11 @@ mlir::LogicalResult CIRGenFunction::emitReturnStmt(const ReturnStmt &s) {
     // this section will do nothing.  But for now a ReturnOp is necessary.
     builder.create<ReturnOp>(loc);
   } else if (rv->getType()->isVoidType()) {
-    // No return value. Emit the return expression for its side effects.
-    // TODO(CIR): Once emitAnyExpr(e) has been upstreamed, get rid of the check
-    // and just call emitAnyExpr(rv) here.
-    if (CIRGenFunction::hasScalarEvaluationKind(rv->getType())) {
-      emitScalarExpr(rv);
-    } else {
-      getCIRGenModule().errorNYI(s.getSourceRange(),
-                                 "non-scalar function return type");
+    // Make sure not to return anything, but evaluate the expression
+    // for side effects.
+    if (rv) {
+      emitAnyExpr(rv);
     }
-    builder.create<ReturnOp>(loc);
   } else if (fnRetTy->isReferenceType()) {
     getCIRGenModule().errorNYI(s.getSourceRange(),
                                "function return type that is a reference");
diff --git a/clang/test/CIR/CodeGen/basic.c b/clang/test/CIR/CodeGen/basic.c
new file mode 100644
index 0000000000000..754f11f1361ba
--- /dev/null
+++ b/clang/test/CIR/CodeGen/basic.c
@@ -0,0 +1,74 @@
+// 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-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);
+
+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]]
+
+//      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>
+// 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
+
+//      OGCG: define{{.*}} i32 @f2()
+// OGCG-NEXT: entry:
+// OGCG-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]]
+
+//      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]]


Reviews:
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T14:56:50Z
user: login: andykaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T16:45:36Z
user: login: andykaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:07:44Z
user: login: andykaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T17:08:30Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T17:15:52Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T17:20:27Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T17:21:24Z
user: login: erichkeane
body: Would be neat if the IR test was:
->CIR
->CIR->LLVM
AND
->LLVM

it sets the precedent for our 'future' and makes it easier to have a test form of the "converted" tests later, and makes it easier to review for correctness, but I'm willing ot let the group discuss it.

Else, I'm good with this.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-12T17:22:45Z
user: login: andykaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T18:09:03Z
user: login: erichkeane
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-12T18:48:44Z
user: login: andykaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T18:51:07Z
user: login: erichkeane
body: 2 nits,  else LGTM.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-12T19:03:45Z
user: login: bcardosolopes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T19:03:56Z
user: login: andykaylor
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-12T19:56:35Z
user: login: bcardosolopes
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-03-12T21:00:51Z
user: login: bcardosolopes
state: APPROVED
author_association: MEMBER
submitted_at: 2025-03-12T21:07:59Z

Review comments:
diff_hunk: @@ -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);
path: clang/lib/CIR/CodeGen/CIRGenExpr.cpp
user: login: erichkeane
body: hah, this line is perhaps overly cute.
created_at: 2025-03-12T14:54:31Z
author_association: COLLABORATOR
diff_hunk: @@ -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));
path: clang/lib/CIR/CodeGen/CIRGenStmt.cpp
user: login: erichkeane
body: Can you explain what is happening here?  We are emitting EVERY expression type as an ignored expression?
created_at: 2025-03-12T14:55:40Z
author_association: COLLABORATOR
diff_hunk: @@ -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:.*]])
path: clang/test/CIR/CodeGen/basic.c
user: login: erichkeane
body: Is this llvm from clang itself, or via lowering from CIR? 
created_at: 2025-03-12T14:56:45Z
author_association: COLLABORATOR
diff_hunk: @@ -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);
path: clang/lib/CIR/CodeGen/CIRGenExpr.cpp
user: login: andykaylor
body: Oh, yeah. I meant to split this out into separate call and return statements. I was actually a bit surprised that this even works.
created_at: 2025-03-12T16:45:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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));
path: clang/lib/CIR/CodeGen/CIRGenStmt.cpp
user: login: andykaylor
body: Yes, this is far from obvious. StmtNodes.inc uses a system of macros and preprocessors statements, so when it is included what you get for each one is somewhat determined by which of the macros the client has defined. In this case, we're getting expressions that are neither statements or abstract statements (because we define STMT and ABSTRACT_STMT but do nothing with them). The exact list, of course, depends on what's in StmtNodes.td and how things are defined there.

FWIW, this matches the handling in the classic codegen (https://github.com/llvm/llvm-project/blob/1a626e63b5a009075eea87c01f0661144b1ec010/clang/lib/CodeGen/CGStmt.cpp#L130).
created_at: 2025-03-12T17:07:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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:.*]])
path: clang/test/CIR/CodeGen/basic.c
user: login: andykaylor
body: That's lowering through clangir to llvm ir.
created_at: 2025-03-12T17:08:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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));
path: clang/lib/CIR/CodeGen/CIRGenStmt.cpp
user: login: erichkeane
body: StmtNodes.inc makes anything that inherits from `Expr` fall to this too.  So it looks to me that `UnaryOperator` would hit this, plus just about everything else there.  Perhaps we do a good job making sure we don't call `EmitStmt` with any other sort of expression?  
created_at: 2025-03-12T17:15:52Z
author_association: COLLABORATOR
diff_hunk: @@ -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:.*]])
path: clang/test/CIR/CodeGen/basic.c
user: login: erichkeane
body: would be neat if we had all 3?  WDYT?
created_at: 2025-03-12T17:20:26Z
author_association: COLLABORATOR
diff_hunk: @@ -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));
path: clang/lib/CIR/CodeGen/CIRGenStmt.cpp
user: login: erichkeane
body: OH, wait, I see it now.  The point is that this is an expression that _IS_ ignored, meaning we're not doing anything with the result.  So this is appropriate for any expression where its return value/result/etc isn't dealt with.
created_at: 2025-03-12T17:21:24Z
author_association: COLLABORATOR
diff_hunk: @@ -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:.*]])
path: clang/test/CIR/CodeGen/basic.c
user: login: andykaylor
body: Recording our discussion here for the benefit of those who weren't present, we agreed that although this will require a bit of extra work for each test, it will have at least two significant benefits: (1) it will allow reviewers to easily compare the LLVM IR generated via CIR to that generated by classic codegen, and (2) it will alert us via a failed test when something changes in the classic codegen that we might also want to consider in the CIR path.

Conclusion: yes, this is worth doing.
created_at: 2025-03-12T18:09:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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
path: clang/test/CIR/CodeGen/basic.c
user: login: erichkeane
body: Actually curious why these use an intermediary file instead of just a pipe?  IIRC this ends up slowing the test down slightly.
created_at: 2025-03-12T18:48:27Z
author_association: COLLABORATOR
diff_hunk: @@ -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
path: clang/test/CIR/CodeGen/basic.c
user: login: andykaylor
body: Good point. I don't see any reason for that.
created_at: 2025-03-12T18:51:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -165,6 +165,34 @@ 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) {
path: clang/lib/CIR/CodeGen/CIRGenExpr.cpp
user: login: erichkeane
body: is `ignoreResult` used anywhere?    Might need a cast-to-void here to prevent diagnostics.
created_at: 2025-03-12T19:02:08Z
author_association: COLLABORATOR
diff_hunk: @@ -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);
path: clang/lib/CIR/CodeGen/CIRGenFunction.h
user: login: erichkeane
body: I'd vastly prefer this not have a default argument
created_at: 2025-03-12T19:03:29Z
author_association: COLLABORATOR
diff_hunk: @@ -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
path: clang/test/CIR/CodeGen/basic.c
user: login: bcardosolopes
body: The reason we've been doing that is solely making our lives to develop more efficiently. I guess YMMV but whenever I'm investigating a test failure, it's very convenient not to have to pipe the output myself in a file and open in the editor, output is already there and gets updated automatically, etc - in VSCode it's specially handy, cause you click the output file name in the terminal and voila. 

Our idea is that whenever we hit a certain maturity state we'll probably bulk change those to use pipes instead, but they are still extremely useful on a daily basis.
created_at: 2025-03-12T19:03:56Z
author_association: MEMBER
diff_hunk: @@ -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
path: clang/test/CIR/CodeGen/basic.c
user: login: andykaylor
body: I thought the intermediate files got automatically deleted. I can put this back to using the intermediate files.
created_at: 2025-03-12T19:56:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -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:.*]])
path: clang/test/CIR/CodeGen/basic.c
user: login: bcardosolopes
body: The more the merrier :) 
created_at: 2025-03-12T21:00:51Z
author_association: MEMBER

PR comments:
user: login: llvmbot
created_at: 2025-03-12T00:18:08Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/130869.diff


5 Files Affected:

- (modified) clang/include/clang/CIR/MissingFeatures.h (+1) 
- (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+27) 
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+10) 
- (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+148-4) 
- (added) clang/test/CIR/CodeGen/basic.c (+53) 


``````````diff
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]]

``````````

</details>

author_association: MEMBER
user: login: llvmbot
created_at: 2025-03-12T00:18:09Z
body: 
<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/130869.diff


5 Files Affected:

- (modified) clang/include/clang/CIR/MissingFeatures.h (+1) 
- (modified) clang/lib/CIR/CodeGen/CIRGenExpr.cpp (+27) 
- (modified) clang/lib/CIR/CodeGen/CIRGenFunction.h (+10) 
- (modified) clang/lib/CIR/CodeGen/CIRGenStmt.cpp (+148-4) 
- (added) clang/test/CIR/CodeGen/basic.c (+53) 


``````````diff
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]]

``````````

</details>

author_association: MEMBER