[clang] [CIR] Make the post-CIRGen emission path independent of ASTContext (PR #229441)
Konstantinos Parasyris via cfe-commits
cfe-commits at lists.llvm.org
Tue Oct 6 07:53:13 PDT 2026
https://github.com/koparasy created https://github.com/llvm/llvm-project/pull/229441
This is mostly an NFC.
No change to generated LLVM IR or object code.
`#cir.lowering_lang_options` gains openmp_has_targets and
sycl_is_host, so `-emit-cir` output changes.
This is preliminary work towards reusing code when
reading `cir` and emitting its output (#227917)
>From 9d2a24558cbe6ea3466f9c0845eca0ace64452b8 Mon Sep 17 00:00:00 2001
From: Konstantinos Parasyris <konstantinos.parasyris at intel.com>
Date: Tue, 6 Oct 2026 07:42:16 -0700
Subject: [PATCH] [CIR] Make the post-CIRGen emission path independent of
ASTContext
No change to generated LLVM IR or object code. #cir.lowering_lang_options
gains openmp_has_targets and sycl_is_host, so -emit-cir output changes.
This is preliminary work towards reusing code when reading `cir` and
emitting its output.
---
.../include/clang/CIR/Dialect/IR/CIRAttrs.td | 13 +-
clang/lib/CIR/CodeGen/CIRGenModule.cpp | 2 +
clang/lib/CIR/FrontendAction/CIRGenAction.cpp | 311 ++++++++++--------
.../CIR/CodeGen/lowering-lang-options.cpp | 10 +
clang/test/CIR/IR/lowering-lang-options.cir | 6 +-
5 files changed, 199 insertions(+), 143 deletions(-)
diff --git a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
index f6aaac5ab4cab..01574522f128a 100644
--- a/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
+++ b/clang/include/clang/CIR/Dialect/IR/CIRAttrs.td
@@ -2416,8 +2416,10 @@ def CIR_LoweringLangOptionsAttr
Each bool field mirrors the like-named `LangOptions` member: `exceptions`,
`threadsafe_statics`, `cuda`, `cuda_is_device`, `hip`, `gpu_rdc`,
- `openmp` (`LangOpts.OpenMP != 0`; only its on/off state matters), and
- `openmp_is_target_device`. `clang_abi_compat` is an `i32` mirroring
+ `openmp` (`LangOpts.OpenMP != 0`; only its on/off state matters),
+ `openmp_is_target_device`, and `sycl_is_host`. `openmp_has_targets` is
+ `!LangOpts.OMPTargetTriples.empty()`; only whether OpenMP offload targets
+ were requested matters, not which ones. `clang_abi_compat` is an `i32` mirroring
`LangOptions::ClangABI`; it is an `i32` rather than a CIR enum because
`ClangABI` gains an enumerator every Clang release (see
`clang/Basic/ABIVersions.def`), and the ordinal round-trips fine within a
@@ -2428,7 +2430,8 @@ def CIR_LoweringLangOptionsAttr
module attributes {cir.lowering_lang_options = #cir.lowering_lang_options<
exceptions = false, threadsafe_statics = true, cuda = false,
cuda_is_device = false, hip = false, gpu_rdc = false, openmp = false,
- openmp_is_target_device = false, clang_abi_compat = 17>} { }
+ openmp_is_target_device = false, openmp_has_targets = false,
+ sycl_is_host = false, clang_abi_compat = 17>} { }
```
}];
@@ -2441,13 +2444,15 @@ def CIR_LoweringLangOptionsAttr
"bool":$gpu_rdc,
"bool":$openmp,
"bool":$openmp_is_target_device,
+ "bool":$openmp_has_targets,
+ "bool":$sycl_is_host,
"int32_t":$clang_abi_compat
);
let assemblyFormat = [{
`<` struct($exceptions, $threadsafe_statics, $cuda, $cuda_is_device, $hip,
$gpu_rdc, $openmp, $openmp_is_target_device,
- $clang_abi_compat) `>`
+ $openmp_has_targets, $sycl_is_host, $clang_abi_compat) `>`
}];
let canHaveIllegalCXXABIType = 0;
diff --git a/clang/lib/CIR/CodeGen/CIRGenModule.cpp b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
index 5063b6a4cc779..b087184bf9cf7 100644
--- a/clang/lib/CIR/CodeGen/CIRGenModule.cpp
+++ b/clang/lib/CIR/CodeGen/CIRGenModule.cpp
@@ -185,6 +185,8 @@ CIRGenModule::CIRGenModule(mlir::MLIRContext &mlirContext,
/*gpu_rdc=*/langOpts.GPURelocatableDeviceCode,
/*openmp=*/langOpts.OpenMP != 0,
/*openmp_is_target_device=*/langOpts.OpenMPIsTargetDevice,
+ /*openmp_has_targets=*/!langOpts.OMPTargetTriples.empty(),
+ /*sycl_is_host=*/langOpts.SYCLIsHost,
/*clang_abi_compat=*/
static_cast<int32_t>(langOpts.getClangABICompat())));
diff --git a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
index 627e659f62ba8..91cd47887f528 100644
--- a/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
+++ b/clang/lib/CIR/FrontendAction/CIRGenAction.cpp
@@ -24,6 +24,7 @@
#include "clang/CodeGen/ModuleLinker.h"
#include "clang/CodeGenUtils/BackendDiagnosticHandler.h"
#include "clang/Frontend/CompilerInstance.h"
+#include "llvm/ADT/STLFunctionalExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringSet.h"
@@ -82,6 +83,171 @@ static void printCIRModule(mlir::ModuleOp MLIRModule, raw_ostream &OS) {
MLIRModule->print(OS, Flags);
}
+// TODO: share with BackendConsumer::LinkInModules once the rest of the
+// linking logic (not just diagnostics) is unified.
+static bool linkInModules(CompilerInstance &CI, llvm::Module &M,
+ SmallVectorImpl<::clang::LinkModule> &LinkModules,
+ BackendDiagnosticConsumer &DiagConsumer) {
+ for (auto &LM : LinkModules) {
+ assert(LM.Module && "LinkModule does not actually have a module");
+
+ if (LM.PropagateAttrs)
+ for (llvm::Function &F : *LM.Module) {
+ if (F.isIntrinsic())
+ continue;
+ clang::CodeGen::mergeDefaultFunctionDefinitionAttributes(
+ F, CI.getCodeGenOpts(), CI.getLangOpts(), CI.getTargetOpts(),
+ LM.Internalize);
+ }
+
+ DiagConsumer.setCurLinkModule(LM.Module.get());
+ bool Err;
+ if (LM.Internalize) {
+ Err = llvm::Linker::linkModules(
+ M, std::move(LM.Module), LM.LinkFlags,
+ [](llvm::Module &M, const llvm::StringSet<> &GVS) {
+ llvm::internalizeModule(M, [&GVS](const llvm::GlobalValue &GV) {
+ return !GV.hasName() || (GVS.count(GV.getName()) == 0);
+ });
+ });
+ } else {
+ Err = llvm::Linker::linkModules(M, std::move(LM.Module), LM.LinkFlags);
+ }
+
+ if (Err)
+ return true;
+ }
+
+ LinkModules.clear();
+ return false;
+}
+
+// Reads the device binary named by -foffload-include-binary and embeds it
+// into the host module. wrapSYCLBinaries also appends the registration ctor
+// at priority 101 when no registration-function out-param is supplied.
+static void embedSYCLDeviceBinary(CompilerInstance &CI, llvm::Module &M) {
+ StringRef fileName = CI.getCodeGenOpts().OffloadBinaryToEmbedFile;
+ auto bufferOrErr = CI.getVirtualFileSystem().getBufferForFile(fileName);
+ if (std::error_code ec = bufferOrErr.getError()) {
+ CI.getDiagnostics().Report(diag::err_cannot_open_file)
+ << fileName << ec.message();
+ return;
+ }
+ std::unique_ptr<llvm::MemoryBuffer> buffer = std::move(bufferOrErr.get());
+ if (llvm::Error err = llvm::offloading::wrapSYCLBinaries(
+ M, ArrayRef<char>(buffer->getBufferStart(), buffer->getBufferSize()),
+ llvm::offloading::SYCLJITOptions(), /*IsFinalizedImage=*/true)) {
+ CI.getDiagnostics().Report(diag::err_fe_error_backend)
+ << llvm::toString(std::move(err));
+ return;
+ }
+}
+
+// Emits the output requested by \p Action for \p MlirModule, a CIR module that
+// has been through the CIR-to-CIR pipeline. Language facts are read from the
+// module's #cir.lowering_lang_options and target facts from \p CI, so this
+// does not need an ASTContext and can also run on CIR loaded from a file.
+// \p AddFunctionSourceLocations, if set, runs on the lowered LLVM module
+// before linking.
+static void emitCIRModuleOutput(
+ CIRGenAction::OutputType Action, CompilerInstance &CI,
+ mlir::ModuleOp MlirModule, llvm::LLVMContext &LLVMCtx,
+ SmallVectorImpl<::clang::LinkModule> &LinkModules,
+ BackendDiagnosticConsumer &DiagConsumer,
+ std::unique_ptr<raw_pwrite_stream> OutputStream,
+ llvm::function_ref<void(llvm::Module &)> AddFunctionSourceLocations = {}) {
+ CodeGenOptions &CGO = CI.getCodeGenOpts();
+ switch (Action) {
+ case CIRGenAction::OutputType::EmitCIR:
+ if (OutputStream && MlirModule)
+ printCIRModule(MlirModule, *OutputStream);
+ break;
+ case CIRGenAction::OutputType::EmitLLVM:
+ case CIRGenAction::OutputType::EmitBC:
+ case CIRGenAction::OutputType::EmitObj:
+ case CIRGenAction::OutputType::EmitAssembly: {
+ StringRef saveTempsPrefix = CGO.SaveTempsFilePrefix;
+ std::string cirSaveTempsOutFile, mlirSaveTempsOutFile;
+ if (!saveTempsPrefix.empty()) {
+ SmallString<128> stem(saveTempsPrefix);
+ llvm::sys::path::replace_extension(stem, "cir");
+ cirSaveTempsOutFile = std::string(stem);
+ llvm::sys::path::replace_extension(stem, "mlir");
+ mlirSaveTempsOutFile = std::string(stem);
+ }
+
+ if (!cirSaveTempsOutFile.empty()) {
+ std::error_code ec;
+ llvm::raw_fd_ostream out(cirSaveTempsOutFile, ec);
+ if (!ec)
+ MlirModule->print(out);
+ }
+
+ // If errors occurred during codegen, stop before running the backend.
+ if (CI.getDiagnostics().hasErrorOccurred())
+ return;
+
+ // Read the language facts before lowering rewrites the module. Without the
+ // attribute (e.g. hand-written CIR) every fact defaults to off.
+ auto LangOptsAttr = MlirModule->getAttrOfType<cir::LoweringLangOptionsAttr>(
+ cir::CIRDialect::getLoweringLangOptionsAttrName());
+ const bool EnableOpenMP = LangOptsAttr && LangOptsAttr.getOpenmp();
+ const bool SYCLIsHost = LangOptsAttr && LangOptsAttr.getSyclIsHost();
+ // LangOptions::CUDA is also set for HIP.
+ const bool IsCUDAOrOpenMPOffload =
+ LangOptsAttr &&
+ (LangOptsAttr.getCuda() || LangOptsAttr.getOpenmpHasTargets());
+
+ // Route LLVM backend diagnostics (optimization remarks, unsupported
+ // features, inline-asm errors, etc.) through clang diagnostics for
+ // the remainder of the LLVM-emitting pipeline.
+ std::unique_ptr<llvm::DiagnosticHandler> OldDiagnosticHandler =
+ LLVMCtx.getDiagnosticHandler();
+ llvm::scope_exit RestoreDiagnosticHandler([&]() {
+ LLVMCtx.setDiagnosticHandler(std::move(OldDiagnosticHandler));
+ });
+ LLVMCtx.setDiagnosticHandler(DiagConsumer.createDiagnosticHandler());
+
+ std::unique_ptr<llvm::Module> LLVMModule =
+ lowerFromCIRToLLVMIR(MlirModule, LLVMCtx, EnableOpenMP,
+ mlirSaveTempsOutFile, &CI.getVirtualFileSystem());
+
+ LLVMModule->setDataLayout(CI.getTarget().getDataLayoutString());
+
+ if (AddFunctionSourceLocations)
+ AddFunctionSourceLocations(*LLVMModule);
+
+ if (linkInModules(CI, *LLVMModule, LinkModules, DiagConsumer))
+ return;
+
+ // Embed the offloaded SYCL device binary into the host module.
+ if (SYCLIsHost && !CGO.OffloadBinaryToEmbedFile.empty())
+ embedSYCLDeviceBinary(CI, *LLVMModule);
+
+ // CUDA, HIP and OpenMP offloading rely on host-side offload entries that
+ // are not emitted on the ClangIR path yet, so embedding their device
+ // objects would produce a host object that cannot be registered.
+ if (!CGO.OffloadObjects.empty() && IsCUDAOrOpenMPOffload) {
+ DiagnosticsEngine &Diags = CI.getDiagnostics();
+ Diags.Report(Diags.getCustomDiagID(
+ DiagnosticsEngine::Error,
+ "ClangIR code gen Not Yet Implemented: embedding offload objects "
+ "for CUDA, HIP or OpenMP offloading"));
+ return;
+ }
+
+ // If there is device offloading code embed it in the host now.
+ EmbedObject(LLVMModule.get(), CGO, CI.getVirtualFileSystem(),
+ CI.getDiagnostics());
+
+ BackendAction BEAction = getBackendActionFromOutputType(Action);
+ emitBackendOutput(CI, CGO, LLVMModule.get(), BEAction,
+ CI.getVirtualFileSystemPtr(), std::move(OutputStream));
+ break;
+ }
+ }
+}
+
class CIRGenConsumer : public clang::ASTConsumer {
virtual void anchor();
@@ -189,146 +355,17 @@ class CIRGenConsumer : public clang::ASTConsumer {
}
}
- switch (Action) {
- case CIRGenAction::OutputType::EmitCIR:
- if (OutputStream && MlirModule)
- printCIRModule(MlirModule, *OutputStream);
- break;
- case CIRGenAction::OutputType::EmitLLVM:
- case CIRGenAction::OutputType::EmitBC:
- case CIRGenAction::OutputType::EmitObj:
- case CIRGenAction::OutputType::EmitAssembly: {
- StringRef saveTempsPrefix = CGO.SaveTempsFilePrefix;
- std::string cirSaveTempsOutFile, mlirSaveTempsOutFile;
- if (!saveTempsPrefix.empty()) {
- SmallString<128> stem(saveTempsPrefix);
- llvm::sys::path::replace_extension(stem, "cir");
- cirSaveTempsOutFile = std::string(stem);
- llvm::sys::path::replace_extension(stem, "mlir");
- mlirSaveTempsOutFile = std::string(stem);
- }
-
- if (!cirSaveTempsOutFile.empty()) {
- std::error_code ec;
- llvm::raw_fd_ostream out(cirSaveTempsOutFile, ec);
- if (!ec)
- MlirModule->print(out);
- }
-
- // If errors occurred during codegen, stop before running the backend.
- if (CI.getDiagnostics().hasErrorOccurred())
- return;
-
- // Route LLVM backend diagnostics (optimization remarks, unsupported
- // features, inline-asm errors, etc.) through clang diagnostics for
- // the remainder of the LLVM-emitting pipeline.
- std::unique_ptr<llvm::DiagnosticHandler> OldDiagnosticHandler =
- LLVMCtx.getDiagnosticHandler();
- llvm::scope_exit RestoreDiagnosticHandler([&]() {
- LLVMCtx.setDiagnosticHandler(std::move(OldDiagnosticHandler));
- });
- LLVMCtx.setDiagnosticHandler(DiagConsumer.createDiagnosticHandler());
-
- std::unique_ptr<llvm::Module> LLVMModule = lowerFromCIRToLLVMIR(
- MlirModule, LLVMCtx, C.getLangOpts().OpenMP, mlirSaveTempsOutFile,
- &CI.getVirtualFileSystem());
-
- LLVMModule->setDataLayout(C.getTargetInfo().getDataLayoutString());
-
- for (llvm::Function &F : LLVMModule->functions())
+ // CIRGen-only step: map the emitted LLVM functions back to their source
+ // declarations so backend diagnostics point at the original source.
+ auto AddFunctionSourceLocations = [&](llvm::Module &LLVMModule) {
+ for (llvm::Function &F : LLVMModule.functions())
if (const Decl *FD = Gen->getDeclForMangledName(F.getName()))
DiagConsumer.addFunctionSourceLocation(
F.getName(), FD->getASTContext().getFullLoc(FD->getLocation()));
-
- if (linkInModules(*LLVMModule))
- return;
-
- // Embed the offloaded SYCL device binary into the host module.
- if (C.getLangOpts().SYCLIsHost && !CGO.OffloadBinaryToEmbedFile.empty())
- embedSYCLDeviceBinary(*LLVMModule);
-
- // CUDA, HIP and OpenMP offloading rely on host-side offload entries that
- // are not emitted on the ClangIR path yet, so embedding their device
- // objects would produce a host object that cannot be registered.
- const LangOptions &LangOpts = C.getLangOpts();
- if (!CGO.OffloadObjects.empty() &&
- (LangOpts.CUDA || !LangOpts.OMPTargetTriples.empty())) {
- DiagnosticsEngine &Diags = CI.getDiagnostics();
- Diags.Report(Diags.getCustomDiagID(
- DiagnosticsEngine::Error,
- "ClangIR code gen Not Yet Implemented: embedding offload objects "
- "for CUDA, HIP or OpenMP offloading"));
- return;
- }
-
- // If there is device offloading code embed it in the host now.
- EmbedObject(LLVMModule.get(), CGO, CI.getVirtualFileSystem(),
- CI.getDiagnostics());
-
- BackendAction BEAction = getBackendActionFromOutputType(Action);
- emitBackendOutput(CI, CI.getCodeGenOpts(), LLVMModule.get(), BEAction, FS,
- std::move(OutputStream));
- break;
- }
- }
- }
-
- // TODO: share with BackendConsumer::LinkInModules once the rest of the
- // linking logic (not just diagnostics) is unified.
- bool linkInModules(llvm::Module &M) {
- for (auto &LM : LinkModules) {
- assert(LM.Module && "LinkModule does not actually have a module");
-
- if (LM.PropagateAttrs)
- for (llvm::Function &F : *LM.Module) {
- if (F.isIntrinsic())
- continue;
- clang::CodeGen::mergeDefaultFunctionDefinitionAttributes(
- F, CGO, CI.getLangOpts(), CI.getTargetOpts(), LM.Internalize);
- }
-
- DiagConsumer.setCurLinkModule(LM.Module.get());
- bool Err;
- if (LM.Internalize) {
- Err = llvm::Linker::linkModules(
- M, std::move(LM.Module), LM.LinkFlags,
- [](llvm::Module &M, const llvm::StringSet<> &GVS) {
- llvm::internalizeModule(M, [&GVS](const llvm::GlobalValue &GV) {
- return !GV.hasName() || (GVS.count(GV.getName()) == 0);
- });
- });
- } else {
- Err = llvm::Linker::linkModules(M, std::move(LM.Module), LM.LinkFlags);
- }
-
- if (Err)
- return true;
- }
-
- LinkModules.clear();
- return false;
- }
-
- // Reads the device binary named by -foffload-include-binary and embeds it
- // into the host module. wrapSYCLBinaries also appends the registration ctor
- // at priority 101 when no registration-function out-param is supplied.
- void embedSYCLDeviceBinary(llvm::Module &M) {
- StringRef fileName = CGO.OffloadBinaryToEmbedFile;
- auto bufferOrErr = CI.getVirtualFileSystem().getBufferForFile(fileName);
- if (std::error_code ec = bufferOrErr.getError()) {
- CI.getDiagnostics().Report(diag::err_cannot_open_file)
- << fileName << ec.message();
- return;
- }
- std::unique_ptr<llvm::MemoryBuffer> buffer = std::move(bufferOrErr.get());
- if (llvm::Error err = llvm::offloading::wrapSYCLBinaries(
- M,
- ArrayRef<char>(buffer->getBufferStart(), buffer->getBufferSize()),
- llvm::offloading::SYCLJITOptions(), /*IsFinalizedImage=*/true)) {
- CI.getDiagnostics().Report(diag::err_fe_error_backend)
- << llvm::toString(std::move(err));
- return;
- }
+ };
+ emitCIRModuleOutput(Action, CI, MlirModule, LLVMCtx, LinkModules,
+ DiagConsumer, std::move(OutputStream),
+ AddFunctionSourceLocations);
}
void HandleTagDeclDefinition(TagDecl *D) override {
diff --git a/clang/test/CIR/CodeGen/lowering-lang-options.cpp b/clang/test/CIR/CodeGen/lowering-lang-options.cpp
index 196577428208a..f1c3683068b87 100644
--- a/clang/test/CIR/CodeGen/lowering-lang-options.cpp
+++ b/clang/test/CIR/CodeGen/lowering-lang-options.cpp
@@ -3,6 +3,8 @@
// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir \
// RUN: -fexceptions -fcxx-exceptions -emit-cir %s -o - \
// RUN: | FileCheck %s --check-prefix=EXC
+// RUN: %clang_cc1 -std=c++17 -triple x86_64-unknown-linux-gnu -fclangir \
+// RUN: -fsycl-is-host -emit-cir %s -o - | FileCheck %s --check-prefix=SYCL
// CIRGen serializes the LangOptions facts that post-CIRGen lowering consumes
// onto the ModuleOp as a #cir.lowering_lang_options attribute, unconditionally, so
@@ -23,6 +25,8 @@ int x;
// PLAIN-SAME: gpu_rdc = false
// PLAIN-SAME: openmp = false
// PLAIN-SAME: openmp_is_target_device = false
+// PLAIN-SAME: openmp_has_targets = false
+// PLAIN-SAME: sycl_is_host = false
// PLAIN-SAME: clang_abi_compat = {{[0-9]+}}>
// -fexceptions -fcxx-exceptions flips exceptions. (OpenMP is exercised in the
@@ -34,3 +38,9 @@ int x;
// EXC-SAME: openmp = false
// EXC-SAME: openmp_is_target_device = false
// EXC-SAME: clang_abi_compat = {{[0-9]+}}>
+
+// -fsycl-is-host flips sycl_is_host, which gates embedding the SYCL device
+// binary into the host object.
+// SYCL: cir.lowering_lang_options = #cir.lowering_lang_options<
+// SYCL-SAME: openmp_has_targets = false
+// SYCL-SAME: sycl_is_host = true
diff --git a/clang/test/CIR/IR/lowering-lang-options.cir b/clang/test/CIR/IR/lowering-lang-options.cir
index 2ef90bd94ed4e..608aad4ab0097 100644
--- a/clang/test/CIR/IR/lowering-lang-options.cir
+++ b/clang/test/CIR/IR/lowering-lang-options.cir
@@ -7,10 +7,12 @@
module attributes {cir.lowering_lang_options = #cir.lowering_lang_options<
exceptions = true, threadsafe_statics = false, cuda = true,
cuda_is_device = true, hip = true, gpu_rdc = true, openmp = true,
- openmp_is_target_device = true, clang_abi_compat = 9>} {
+ openmp_is_target_device = true, openmp_has_targets = true,
+ sycl_is_host = true, clang_abi_compat = 9>} {
}
// CHECK: cir.lowering_lang_options = #cir.lowering_lang_options<
// CHECK-SAME: exceptions = true, threadsafe_statics = false, cuda = true,
// CHECK-SAME: cuda_is_device = true, hip = true, gpu_rdc = true, openmp = true,
-// CHECK-SAME: openmp_is_target_device = true, clang_abi_compat = 9>
+// CHECK-SAME: openmp_is_target_device = true, openmp_has_targets = true,
+// CHECK-SAME: sycl_is_host = true, clang_abi_compat = 9>
More information about the cfe-commits
mailing list