CIR has more than one meaningful serialization boundary. In particular, there is a high-level/pre-target-lowering CIR form before target and ABI lowering (the ABI-free form), and a lowered/post-ABI CIR form closer to CIR-to-LLVM (the ABI-aware form).
Today, the driver exposes only one CIR output mode through -emit-cir, and that output is produced after the current CIR-to-CIR lowering sequence. This RFC argues that the pre-lowering CIR boundary should be made first-class and driver-visible because it is useful for testing, debugging, reduction, and CIR-level transformations.
The purpose of this RFC is to identify the missing boundary. The exact spelling can be discussed separately.
Background
CIR is Clang’s MLIR-based intermediate representation, intended to sit between the Clang AST and LLVM IR: lower than the AST, still higher-level and more source-adjacent than LLVM IR. The current pipeline is:
CIRGen(AST) → early CIR cleanup/canonicalization → target/ABI lowering → CIR-to-LLVM.
- CIRGen.
- pre-lowering CIR cleanup/canonicalization (target-independent where possible, language-aware)
- cir-canonicalize
- cir-simplify
- cir-idiom-recognizer
- target and ABI lowering (target specific, language aware)
- cir-target-lowering
- cir-cxxabi-lowering
- cir-callconv-lowering
- cir-lowering-prepare
Problem Statement
The CIR pipeline already has a meaningful conceptual boundary before target/ABI lowering: after CIRGen and early CIR cleanup/canonicalization, but before cir-target-lowering, cir-cxxabi-lowering, and future calling-convention lowering.
This boundary matters because pre-lowering and post-lowering CIR serve different purposes. Pre-lowering CIR is more source-adjacent: it preserves high-level function signatures, aggregate parameters, return types, and source/CIR dialect structure more directly. This makes it useful for CIRGen testing, high-level analyses, transformations, and multi-TU combine/link-style experiments or tools. In that sense, it maps more closely to ClangIR’s stated intent as “a high-level representation in Clang that reflects aspects of the C/C++ languages and their extensions.”
Post-lowering CIR is also valuable because target and ABI decisions have been materialized in the representation. The problem is therefore not that the current -emit-cir artifact is useless, but that the driver exposes only one CIR artifact boundary. Workflows that need the high-level boundary must currently rely on debug-style pass instrumentation (e.g. -mllvm -print-after=cir-canonicalize), which requires knowing internal pass names, breaks when the pipeline is reordered, and does not compose with normal driver output (-c -o file.cir). This is a debug/testing mechanism, not a first-class compiler output mode.
Relationship to -emit-llvm
The analogy with -emit-llvm is useful, but should not be understood as an argument about target independence. The LLVM IR produced by Clang is already tied to a target context: IRGen has recorded target facts such as the triple, datalayout, address spaces, calling conventions, and ABI-influenced layout decisions.
The important property of -emit-llvm is that it names a well-understood serialized IR boundary before the next major lowering/codegen stage, and that boundary is useful for testing, debugging, reduction, pass development, and tooling. The analogous question for CIR is therefore not “which CIR is target-independent” but “which CIR boundaries are worth naming as first-class driver outputs”.
Proposal
Expose the pre-lowering CIR boundary as a first-class driver artifact, in addition to the existing post-lowering -emit-cir output.
Emit points are defined by conditions, not pass positions. Each CIR emit point is specified by the conditions that hold at that point, rather than by the pass after which emission occurs. Anchoring to “after pass X” re-binds the artifact to a pass position, so restructuring the pipeline silently changes the guarantees at that spelling; anchoring to conditions keeps the contract stable as the pipeline evolves and makes explicit what is actually important about the point. The proposal names two emittable forms on this basis.
ABI-free form (pre-lowering). The IR state after CIRGen and target-agnostic CIR cleanup/canonicalization (cir-canonicalize, cir-simplify, cir-idiom-recognizer), and before any pass that materializes target or ABI decisions into the IR (cir-target-lowering, cir-cxxabi-lowering, cir-lowering-prepare, and cir-callconv-lowering). The defining condition is that no target or ABI decision has been materialized — function signatures, aggregate parameters and return types, and high-level dialect structure remain source-adjacent. Self-containment with respect to the AST (below) is also among the conditions for this point.
ABI-aware form (post-lowering). The IR state after target and ABI lowering have been materialized, before CIR-to-LLVM. This is the form the existing -emit-cir already produces; the proposal names it rather than changing it (see Relation to current -emit-cir). Its defining condition is the converse of the ABI-free form’s: target and ABI facts that were previously implicit are now explicit in the IR, which suits post-ABI analyses and tooling that reason about materialized layout and calling conventions.
Relation to current -emit-cir. Today -emit-cir emits CIR after the full CIR-to-CIR lowering sequence, including target/ABI lowering. That artifact remains useful and this proposal does not change it. The proposal adds a second, earlier artifact rather than redefining the existing one.
Stability. Neither boundary is proposed as a stable IR contract for out-of-tree consumers. Both are compiler outputs in the same sense -emit-llvm is: useful and well-defined at a given Clang version, but free to evolve.
Self-containment. For the ABI-free form to be useful as a first-class artifact and digestible by other tools, it must be self-contained with respect to the Clang frontend state needed by downstream CIR passes. Passes after the boundary should not require a live ASTContext; any AST-derived facts they need should be represented in the serialized CIR module. Where this is not yet true, exposing the boundary surfaces those dependencies as bugs to be fixed; the intended direction is that the artifact is independently consumable by CIR tools and by the downstream CIR pipeline.
Self-containment is a gate, not a form-distinguishing condition. The two forms above are distinguished by an axis (ABI-awareness) along which both states are independently useful artifacts. Self-containment with respect to the AST differs in kind: it is not an axis yielding a distinct useful artifact, because the AST-dependent state is never something one would emit — any external consumer of the serialized module has no live ASTContext. AST-freedom is therefore the gate an internal form must pass through to be emittable at all, applied equally to every emit point, rather than a property that tells one form from another. (Dumping AST-dependent CIR for debugging remains the province of -print-after-style instrumentation, which is not a driver artifact.)
Sequencing. The two forms reach self-containment on different timelines, and this proposal does not assume they arrive together. For the ABI-aware form, only CIR-to-LLVM must be AST-free (downstream of where AST-derived facts are consumed), so it may become round-trippable as cc1 input. For the ABI-free form, the entire target/ABI lowering sequence downstream of it must be AST-free, which requires more effort. Both forms can be named and exposed as outputs now; round-trip is expected to land for the ABI-aware form earlier than for the ABI-free form. The RFC names the destination for both and blocks neither on the other.
Spelling. The proposed surface is -emit-cir=, which extends naturally as further emit points are named. Form tokens name the condition that defines the point — the state the module is in — rather than its position in the pipeline; for example -emit-cir=abi-free and -emit-cir=abi-aware for the two forms above. These spellings are illustrative; exact tokens are deferred to discussion. The naming question is independent of whether the boundaries themselves are worth exposing, which is the subject of this RFC.
Many thanks to @davidriverg for doing the research on this.