[clang] [CIR] Parse .cir cc1 input and support -emit-cir from it (PR #228288)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 1 17:49:50 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang
Author: Konstantinos Parasyris (koparasy)
<details>
<summary>Changes</summary>
Fill in the Language::CIR path of CIRGenAction::ExecuteAction: register the ClangIR dialects, parse and verify the main file into a ModuleOp with MLIR diagnostics routed through CIRDiagnosticHandler, and require the module's cir.triple to match the target triple. A mismatch is an error rather than an override, because CIR emitted by -emit-cir has already been lowered for the target ABI of its own triple.
It is still unclear whether we should disallow any overrides of module CIRGen attribues. At this point in time, I opt to go with a conservative approach.
The CIR-to-CIR pipeline is not run, because -emit-cir output has already been through it and its lowering passes are not idempotent; recording the pipeline stage on the module is left for a follow-up. Other outputs still report that ClangIR input is not yet supported.
`registerAllDialects`` now registers the LLVM dialect. The data layout spec that CIRGen attaches to every module uses LLVM pointer types as keys, so parsing CIR needs it. Until now every CIR tool registered it on top of registerAllDialects, and the CIRGen context only got it as a dependency of the eagerly loaded OpenACC and OpenMP dialects.
Note: Parse errors point into the .cir input. Verifier errors on CIR emitted by CIRGen carry locations in the original source file, which is not loaded in the SourceManager, so they are reported without a location for now; a TODO records the open question of where they should point and how to best handle this.
The round-trip tests for C and C++ check that the re-emitted CIR is byte-identical to the original.
Part of https://github.com/llvm/llvm-project/issues/227917.
---
Full diff: https://github.com/llvm/llvm-project/pull/228288.diff
9 Files Affected:
- (modified) clang/include/clang/Basic/DiagnosticFrontendKinds.td (+5)
- (modified) clang/lib/CIR/CMakeLists.txt (+1)
- (modified) clang/lib/CIR/FrontendAction/CIRGenAction.cpp (+84-8)
- (modified) clang/lib/CIR/FrontendAction/CMakeLists.txt (+2)
- (modified) clang/lib/CIR/RegisterAllDialects.cpp (+5-1)
- (added) clang/test/CIR/Input/cir-input-emit-cir.c (+29)
- (added) clang/test/CIR/Input/cir-input-emit-cir.cpp (+30)
- (added) clang/test/CIR/Input/cir-input-invalid.cir (+53)
- (modified) clang/test/CIR/Input/cir-input-unsupported.cir (+3-5)
``````````diff
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 840bade066430..2980d42561675 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -148,6 +148,11 @@ def err_fe_cir_not_built : Error<"clang IR support not available, rebuild "
"clang with -DCLANG_ENABLE_CIR=ON">;
def err_fe_cir_input_unsupported : Error<
"support for ClangIR input is not yet implemented">;
+def err_invalid_cir : Error<"invalid ClangIR input">;
+def err_cir_input_missing_triple : Error<
+ "missing target triple in ClangIR input">;
+def err_cir_input_triple_mismatch : Error<
+ "target triple '%0' of ClangIR input does not match the target '%1'">;
def err_fe_invalid_multiple_actions : Error<
"'%0' action ignored; '%1' action specified previously">;
def err_fe_invalid_alignment : Error<
diff --git a/clang/lib/CIR/CMakeLists.txt b/clang/lib/CIR/CMakeLists.txt
index eebb1131f914c..6f9f4bb941aad 100644
--- a/clang/lib/CIR/CMakeLists.txt
+++ b/clang/lib/CIR/CMakeLists.txt
@@ -15,4 +15,5 @@ add_clang_library(CIRRegisterAllDialects
CIROpenACCSupport
CIROpenMPSupport
MLIRDLTIDialect
+ MLIRLLVMDialect
)
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index 840095c1d0533..5c8b8e0be6a75 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -8,13 +8,17 @@
#include "clang/CIR/FrontendAction/CIRGenAction.h"
#include "CIRDiagnosticHandler.h"
+#include "mlir/IR/BuiltinOps.h"
#include "mlir/IR/MLIRContext.h"
#include "mlir/IR/OwningOpRef.h"
+#include "mlir/Parser/Parser.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/DiagnosticCodeGen.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/CIR/CIRGenerator.h"
#include "clang/CIR/CIRToCIRPasses.h"
+#include "clang/CIR/Dialect/IR/CIRDialect.h"
+#include "clang/CIR/InitAllDialects.h"
#include "clang/CIR/LowerToLLVM.h"
#include "clang/CodeGen/BackendUtil.h"
#include "clang/CodeGen/ModuleLinker.h"
@@ -70,6 +74,14 @@ lowerFromCIRToLLVMIR(mlir::ModuleOp MLIRModule, llvm::LLVMContext &LLVMCtx,
mlirSaveTempsOutFile, fs);
}
+// Print \p MLIRModule the way -emit-cir does, so that CIR emitted from source
+// and CIR printed back from ClangIR input use the same form.
+static void printCIRModule(mlir::ModuleOp MLIRModule, raw_ostream &OS) {
+ mlir::OpPrintingFlags Flags;
+ Flags.enableDebugInfo(/*enable=*/true, /*prettyForm=*/false);
+ MLIRModule->print(OS, Flags);
+}
+
class CIRGenConsumer : public clang::ASTConsumer {
virtual void anchor();
@@ -179,11 +191,8 @@ class CIRGenConsumer : public clang::ASTConsumer {
switch (Action) {
case CIRGenAction::OutputType::EmitCIR:
- if (OutputStream && MlirModule) {
- mlir::OpPrintingFlags Flags;
- Flags.enableDebugInfo(/*enable=*/true, /*prettyForm=*/false);
- MlirModule->print(*OutputStream, Flags);
- }
+ if (OutputStream && MlirModule)
+ printCIRModule(MlirModule, *OutputStream);
break;
case CIRGenAction::OutputType::EmitLLVM:
case CIRGenAction::OutputType::EmitBC:
@@ -355,15 +364,82 @@ bool CIRGenAction::BeginSourceFileAction(CompilerInstance &CI) {
return ASTFrontendAction::BeginSourceFileAction(CI);
}
+static std::unique_ptr<raw_pwrite_stream>
+getOutputStream(CompilerInstance &CI, StringRef InFile,
+ CIRGenAction::OutputType Action);
+
void CIRGenAction::ExecuteAction() {
if (getCurrentFileKind().getLanguage() != Language::CIR) {
ASTFrontendAction::ExecuteAction();
return;
}
- // TODO: Parse the ClangIR input and emit the requested output.
- getCompilerInstance().getDiagnostics().Report(
- diag::err_fe_cir_input_unsupported);
+ CompilerInstance &CI = getCompilerInstance();
+ DiagnosticsEngine &Diags = CI.getDiagnostics();
+ SourceManager &SM = CI.getSourceManager();
+
+ std::unique_ptr<raw_pwrite_stream> OS = CI.takeOutputStream();
+ if (!OS)
+ OS = getOutputStream(CI, getCurrentFileOrBufferName(), Action);
+ if (!OS)
+ return;
+
+ std::optional<llvm::MemoryBufferRef> MainFile =
+ SM.getBufferOrNone(SM.getMainFileID());
+ if (!MainFile)
+ return;
+
+ mlir::MLIRContext MLIRContext;
+ cir::registerAllDialects(MLIRContext);
+
+ // Route parser and verifier errors through clang's diagnostics. Parse
+ // errors point into the .cir input. Verifier errors use the location of
+ // the failing op, which for CIR emitted by CIRGen is a location in the
+ // original source file; that file is not loaded in the SourceManager, so
+ // such errors are currently reported without a location.
+ // TODO: Decide where errors in CIRGen-produced input should point: at the
+ // .cir text, or at the original source.
+ CIRDiagnosticHandler DiagHandler(&MLIRContext, Diags, SM,
+ CI.getFileManager());
+
+ mlir::OwningOpRef<mlir::ModuleOp> Module =
+ mlir::parseSourceString<mlir::ModuleOp>(MainFile->getBuffer(),
+ mlir::ParserConfig(&MLIRContext),
+ MainFile->getBufferIdentifier());
+ if (!Module) {
+ if (!Diags.hasErrorOccurred())
+ Diags.Report(diag::err_invalid_cir);
+ return;
+ }
+
+ // CIR is lowered for the target ABI of its own triple, so a module emitted
+ // for a different target cannot be retargeted by overriding its triple.
+ auto ModuleTriple = mlir::dyn_cast_if_present<mlir::StringAttr>(
+ (*Module)->getAttr(cir::CIRDialect::getTripleAttrName()));
+ if (!ModuleTriple) {
+ Diags.Report(diag::err_cir_input_missing_triple);
+ return;
+ }
+ const std::string &TargetTriple = CI.getTarget().getTriple().str();
+ if (ModuleTriple.getValue() != TargetTriple) {
+ Diags.Report(diag::err_cir_input_triple_mismatch)
+ << ModuleTriple.getValue() << TargetTriple;
+ return;
+ }
+
+ switch (Action) {
+ case OutputType::EmitCIR:
+ // The CIR-to-CIR pipeline is not run: CIR printed by -emit-cir has already
+ // been through it, and its lowering passes are not idempotent.
+ printCIRModule(*Module, *OS);
+ break;
+ case OutputType::EmitLLVM:
+ case OutputType::EmitBC:
+ case OutputType::EmitObj:
+ case OutputType::EmitAssembly:
+ Diags.Report(diag::err_fe_cir_input_unsupported);
+ break;
+ }
}
static std::unique_ptr<raw_pwrite_stream>
diff --git a/clang/lib/CIR/FrontendAction/CMakeLists.txt b/clang/lib/CIR/FrontendAction/CMakeLists.txt
index 4fd0e01f71ed0..be46ce6965931 100644
--- a/clang/lib/CIR/FrontendAction/CMakeLists.txt
+++ b/clang/lib/CIR/FrontendAction/CMakeLists.txt
@@ -24,6 +24,8 @@ add_clang_library(clangCIRFrontendAction
clangCIRLoweringDirectToLLVM
clangCodeGen
clangCodeGenUtils
+ CIRRegisterAllDialects
MLIRCIR
MLIRIR
+ MLIRParser
)
diff --git a/clang/lib/CIR/RegisterAllDialects.cpp b/clang/lib/CIR/RegisterAllDialects.cpp
index 8c2961bdebbaa..73effa12b3192 100644
--- a/clang/lib/CIR/RegisterAllDialects.cpp
+++ b/clang/lib/CIR/RegisterAllDialects.cpp
@@ -9,6 +9,7 @@
#include "clang/CIR/InitAllDialects.h"
#include "mlir/Dialect/DLTI/DLTI.h"
+#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/OpenACC/OpenACC.h"
#include "mlir/Dialect/OpenMP/OpenMPDialect.h"
#include "mlir/IR/BuiltinDialect.h"
@@ -21,8 +22,11 @@
namespace cir {
void registerAllDialects(mlir::DialectRegistry ®istry) {
+ // The LLVM dialect is needed to parse CIR: data layout specs use LLVM
+ // pointer types as keys.
registry.insert<mlir::BuiltinDialect, cir::CIRDialect, mlir::DLTIDialect,
- mlir::omp::OpenMPDialect, mlir::acc::OpenACCDialect>();
+ mlir::LLVM::LLVMDialect, mlir::omp::OpenMPDialect,
+ mlir::acc::OpenACCDialect>();
// Register extensions to integrate CIR types with OpenACC and OpenMP.
cir::omp::registerOpenMPExtensions(registry);
cir::acc::registerOpenACCExtensions(registry);
diff --git a/clang/test/CIR/Input/cir-input-emit-cir.c b/clang/test/CIR/Input/cir-input-emit-cir.c
new file mode 100644
index 0000000000000..29a78b553ebe8
--- /dev/null
+++ b/clang/test/CIR/Input/cir-input-emit-cir.c
@@ -0,0 +1,29 @@
+// Printing parsed ClangIR input with -emit-cir reproduces the CIR it was
+// emitted as.
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir %t.cir -o %t2.cir
+// RUN: diff %t.cir %t2.cir
+
+// Without -o, the output is named after the input with a .cir extension.
+// RUN: rm -rf %t.dir && mkdir -p %t.dir && cd %t.dir
+// RUN: cp %t.cir input.txt
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir -x cir input.txt
+// RUN: diff %t.cir input.cir
+
+struct Point {
+ int x;
+ int y;
+};
+
+struct Point origin = {1, 2};
+int counter;
+
+static int square(int v) { return v * v; }
+
+int add(int a, int b) { return a + b; }
+
+int sumSquares(struct Point *p) {
+ counter++;
+ return add(square(p->x), square(p->y));
+}
diff --git a/clang/test/CIR/Input/cir-input-emit-cir.cpp b/clang/test/CIR/Input/cir-input-emit-cir.cpp
new file mode 100644
index 0000000000000..b13c46f879a18
--- /dev/null
+++ b/clang/test/CIR/Input/cir-input-emit-cir.cpp
@@ -0,0 +1,30 @@
+// Printing parsed ClangIR input with -emit-cir reproduces the CIR it was
+// emitted as, including C++ constructs such as vtables, dynamic and
+// thread-local initialization, and guarded static locals.
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir %s -o %t.cir
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir %t.cir -o %t2.cir
+// RUN: diff %t.cir %t2.cir
+
+struct Base {
+ virtual ~Base();
+ virtual int get() const { return 1; }
+};
+
+struct Derived : Base {
+ int v;
+ Derived(int v) : v(v) {}
+ ~Derived() override {}
+ int get() const override { return v; }
+};
+
+int compute();
+int dynInit = compute();
+thread_local int tls = 7;
+
+int useStatic() {
+ static Derived d(compute());
+ return d.get() + tls;
+}
+
+Base *make() { return new Derived(3); }
diff --git a/clang/test/CIR/Input/cir-input-invalid.cir b/clang/test/CIR/Input/cir-input-invalid.cir
new file mode 100644
index 0000000000000..bfd8fd14c7d4f
--- /dev/null
+++ b/clang/test/CIR/Input/cir-input-invalid.cir
@@ -0,0 +1,53 @@
+// Invalid ClangIR input is diagnosed, not crashed on, before any output is
+// produced. Backend outputs, which are not supported from ClangIR input yet,
+// report the same errors.
+
+// RUN: rm -rf %t && split-file %s %t
+
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir %t/syntax.cir -o - 2>&1 \
+// RUN: | FileCheck %s --check-prefix=SYNTAX --implicit-check-not=error:
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %t/syntax.cir -o - 2>&1 \
+// RUN: | FileCheck %s --check-prefix=SYNTAX --implicit-check-not=error:
+// SYNTAX: syntax.cir:2:15: error: expected non-function type
+
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir %t/verifier.cir -o - 2>&1 \
+// RUN: | FileCheck %s --check-prefix=VERIFIER --implicit-check-not=error:
+// VERIFIER: verifier.cir:5:5: error: 'cir.return' op returns '!cir.int<s, 64>' but enclosing function returns '!cir.int<s, 32>'
+
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir %t/no-triple.cir -o - 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-TRIPLE --implicit-check-not=error:
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir %t/int-triple.cir -o - 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-TRIPLE --implicit-check-not=error:
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-obj %t/no-triple.cir -o /dev/null 2>&1 \
+// RUN: | FileCheck %s --check-prefix=NO-TRIPLE --implicit-check-not=error:
+// NO-TRIPLE: error: missing target triple in ClangIR input
+
+// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-cir %t/mismatch.cir -o - 2>&1 \
+// RUN: | FileCheck %s --check-prefix=MISMATCH --implicit-check-not=error:
+// MISMATCH: error: target triple 'aarch64-unknown-linux-gnu' of ClangIR input does not match the target 'x86_64-unknown-linux-gnu'
+
+//--- syntax.cir
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+ cir.func @f( {
+}
+
+//--- verifier.cir
+!s32i = !cir.int<s, 32>
+!s64i = !cir.int<s, 64>
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
+ cir.func @f(%arg0: !s64i) -> !s32i {
+ cir.return %arg0 : !s64i
+ }
+}
+
+//--- no-triple.cir
+module {
+}
+
+//--- int-triple.cir
+module attributes {cir.triple = 42} {
+}
+
+//--- mismatch.cir
+module attributes {cir.triple = "aarch64-unknown-linux-gnu"} {
+}
diff --git a/clang/test/CIR/Input/cir-input-unsupported.cir b/clang/test/CIR/Input/cir-input-unsupported.cir
index 2d632d893d564..b087871af1d04 100644
--- a/clang/test/CIR/Input/cir-input-unsupported.cir
+++ b/clang/test/CIR/Input/cir-input-unsupported.cir
@@ -1,6 +1,6 @@
// A .cir input is routed to the ClangIR frontend action instead of being
-// parsed as C. Consuming ClangIR input is not implemented yet, so every CIR
-// action reports a single diagnostic.
+// parsed as C. Only -emit-cir is supported from ClangIR input so far, so every
+// other CIR action reports a single diagnostic.
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm -x cir %s -o - 2>&1 \
// RUN: | FileCheck %s --implicit-check-not=error:
@@ -10,8 +10,6 @@
// RUN: | FileCheck %s --implicit-check-not=error:
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-llvm-bc -x cir %s -o /dev/null 2>&1 \
// RUN: | FileCheck %s --implicit-check-not=error:
-// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -fclangir -emit-cir -x cir %s -o - 2>&1 \
-// RUN: | FileCheck %s --implicit-check-not=error:
// ClangIR input implies -fclangir.
// RUN: not %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -x cir %s -o - 2>&1 \
@@ -24,7 +22,7 @@
// CHECK: error: support for ClangIR input is not yet implemented
!s32i = !cir.int<s, 32>
-module {
+module attributes {cir.triple = "x86_64-unknown-linux-gnu"} {
cir.func @f(%arg0: !s32i) -> !s32i {
cir.return %arg0 : !s32i
}
``````````
</details>
https://github.com/llvm/llvm-project/pull/228288
More information about the cfe-commits
mailing list