Skip to content

[CIR] Upstream support for emitting ignored statements #130869

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 12, 2025

Conversation

andykaylor
Copy link
Contributor

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.

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.
@llvmbot llvmbot added clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project labels Mar 12, 2025
@llvmbot
Copy link
Member

llvmbot commented Mar 12, 2025

@llvm/pr-subscribers-clangir

Author: Andy Kaylor (andykaylor)

Changes

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

@llvmbot
Copy link
Member

llvmbot commented Mar 12, 2025

@llvm/pr-subscribers-clang

Author: Andy Kaylor (andykaylor)

Changes

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

void CIRGenFunction::emitIgnoredExpr(const Expr *e) {
if (e->isPRValue()) {
assert(!cir::MissingFeatures::aggValueSlot());
return (void)emitAnyExpr(e, true);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hah, this line is perhaps overly cute.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, yeah. I meant to split this out into separate call and return statements. I was actually a bit surprised that this even works.

// 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:.*]])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this llvm from clang itself, or via lowering from CIR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's lowering through clangir to llvm ir.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be neat if we had all 3? WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The more the merrier :)

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

// 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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually curious why these use an intermediary file instead of just a pipe? IIRC this ends up slowing the test down slightly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I don't see any reason for that.

Copy link
Member

@bcardosolopes bcardosolopes Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought the intermediate files got automatically deleted. I can put this back to using the intermediate files.

Copy link
Collaborator

@erichkeane erichkeane left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 nits, else LGTM.

@@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is ignoreResult used anywhere? Might need a cast-to-void here to prevent diagnostics.

/// 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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vastly prefer this not have a default argument

@andykaylor andykaylor merged commit 64b9410 into llvm:main Mar 12, 2025
9 of 10 checks passed
@andykaylor andykaylor deleted the cir-emit-ignore branch March 12, 2025 21:38
frederik-h pushed a commit to frederik-h/llvm-project that referenced this pull request Mar 18, 2025
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang Clang issues not falling into any other category ClangIR Anything related to the ClangIR project
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants