Skip to main content

cranelift_codegen/
context.rs

1//! Cranelift compilation context and main entry point.
2//!
3//! When compiling many small functions, it is important to avoid repeatedly allocating and
4//! deallocating the data structures needed for compilation. The `Context` struct is used to hold
5//! on to memory allocations between function compilations.
6//!
7//! The context does not hold a `TargetIsa` instance which has to be provided as an argument
8//! instead. This is because an ISA instance is immutable and can be used by multiple compilation
9//! contexts concurrently. Typically, you would have one context per compilation thread and only a
10//! single ISA instance.
11
12use crate::alias_analysis::AliasAnalysis;
13use crate::dominator_tree::DominatorTree;
14use crate::egraph::EgraphPass;
15use crate::flowgraph::ControlFlowGraph;
16use crate::inline::{Inline, do_inlining};
17use crate::ir::Function;
18use crate::isa::TargetIsa;
19use crate::loop_analysis::LoopAnalysis;
20use crate::machinst::{CompiledCode, CompiledCodeStencil};
21use crate::nan_canonicalization::do_nan_canonicalization;
22use crate::remove_constant_phis::do_remove_constant_phis;
23use crate::result::{CodegenResult, CompileResult};
24use crate::settings::{FlagsOrIsa, OptLevel};
25use crate::trace;
26use crate::unreachable_code::eliminate_unreachable_code;
27use crate::verifier::{VerifierErrors, VerifierResult, verify_context};
28use crate::{CompileError, timing};
29#[cfg(feature = "souper-harvest")]
30use alloc::string::String;
31use alloc::vec::Vec;
32use cranelift_control::ControlPlane;
33use target_lexicon::Architecture;
34
35#[cfg(feature = "souper-harvest")]
36use crate::souper_harvest::do_souper_harvest;
37
38/// Persistent data structures and compilation pipeline.
39pub struct Context {
40    /// The function we're compiling.
41    pub func: Function,
42
43    /// The control flow graph of `func`.
44    pub cfg: ControlFlowGraph,
45
46    /// Dominator tree for `func`.
47    pub domtree: DominatorTree,
48
49    /// Loop analysis of `func`.
50    pub loop_analysis: LoopAnalysis,
51
52    /// Result of MachBackend compilation, if computed.
53    pub(crate) compiled_code: Option<CompiledCode>,
54
55    /// Flag: do we want a disassembly with the CompiledCode?
56    pub want_disasm: bool,
57}
58
59impl Context {
60    /// Allocate a new compilation context.
61    ///
62    /// The returned instance should be reused for compiling multiple functions in order to avoid
63    /// needless allocator thrashing.
64    pub fn new() -> Self {
65        Self::for_function(Function::new())
66    }
67
68    /// Allocate a new compilation context with an existing Function.
69    ///
70    /// The returned instance should be reused for compiling multiple functions in order to avoid
71    /// needless allocator thrashing.
72    pub fn for_function(func: Function) -> Self {
73        Self {
74            func,
75            cfg: ControlFlowGraph::new(),
76            domtree: DominatorTree::new(),
77            loop_analysis: LoopAnalysis::new(),
78            compiled_code: None,
79            want_disasm: false,
80        }
81    }
82
83    /// Clear all data structures in this context.
84    pub fn clear(&mut self) {
85        self.func.clear();
86        self.cfg.clear();
87        self.domtree.clear();
88        self.loop_analysis.clear();
89        self.compiled_code = None;
90        self.want_disasm = false;
91    }
92
93    /// Returns the compilation result for this function, available after any `compile` function
94    /// has been called.
95    pub fn compiled_code(&self) -> Option<&CompiledCode> {
96        self.compiled_code.as_ref()
97    }
98
99    /// Returns the compilation result for this function, available after any `compile` function
100    /// has been called.
101    pub fn take_compiled_code(&mut self) -> Option<CompiledCode> {
102        self.compiled_code.take()
103    }
104
105    /// Set the flag to request a disassembly when compiling with a
106    /// `MachBackend` backend.
107    pub fn set_disasm(&mut self, val: bool) {
108        self.want_disasm = val;
109    }
110
111    /// Compile the function, and emit machine code into a `Vec<u8>`.
112    #[deprecated = "use Context::compile"]
113    pub fn compile_and_emit(
114        &mut self,
115        isa: &dyn TargetIsa,
116        mem: &mut Vec<u8>,
117        ctrl_plane: &mut ControlPlane,
118    ) -> CompileResult<'_, &CompiledCode> {
119        let compiled_code = self.compile(isa, ctrl_plane)?;
120        mem.extend_from_slice(compiled_code.code_buffer());
121        Ok(compiled_code)
122    }
123
124    /// Internally compiles the function into a stencil.
125    ///
126    /// Public only for testing and fuzzing purposes.
127    pub fn compile_stencil(
128        &mut self,
129        isa: &dyn TargetIsa,
130        ctrl_plane: &mut ControlPlane,
131    ) -> CodegenResult<CompiledCodeStencil> {
132        let result;
133        trace!("****** START compiling {}", self.func.display_spec());
134        {
135            let _tt = timing::compile();
136
137            self.verify_if(isa)?;
138            self.optimize(isa, ctrl_plane)?;
139            result = isa.compile_function(&self.func, &self.domtree, self.want_disasm, ctrl_plane);
140        }
141        trace!("****** DONE compiling {}\n", self.func.display_spec());
142        result
143    }
144
145    /// Optimize the function, performing all compilation steps up to
146    /// but not including machine-code lowering and register
147    /// allocation.
148    ///
149    /// Public only for testing purposes.
150    pub fn optimize(
151        &mut self,
152        isa: &dyn TargetIsa,
153        ctrl_plane: &mut ControlPlane,
154    ) -> CodegenResult<()> {
155        log::debug!(
156            "Number of CLIF instructions to optimize: {}",
157            self.func.dfg.num_insts()
158        );
159        log::debug!(
160            "Number of CLIF blocks to optimize: {}",
161            self.func.dfg.num_blocks()
162        );
163
164        let opt_level = isa.flags().opt_level();
165        crate::trace!(
166            "Optimizing (opt level {:?}):\n{}",
167            opt_level,
168            self.func.display()
169        );
170
171        if isa.flags().enable_nan_canonicalization() {
172            self.canonicalize_nans(isa)?;
173        }
174
175        self.verify_if(isa)?;
176
177        self.compute_cfg();
178        self.compute_domtree();
179        self.eliminate_unreachable_code(isa)?;
180        self.remove_constant_phis(isa)?;
181
182        self.func.dfg.resolve_all_aliases();
183
184        if opt_level != OptLevel::None {
185            self.egraph_pass(isa, ctrl_plane)?;
186        }
187
188        Ok(())
189    }
190
191    /// Perform function call inlining.
192    ///
193    /// Returns `true` if any function call was inlined, `false` otherwise.
194    pub fn inline(&mut self, inliner: impl Inline) -> CodegenResult<bool> {
195        do_inlining(&mut self.func, inliner)
196    }
197
198    /// Compile the function,
199    ///
200    /// Run the function through all the passes necessary to generate
201    /// code for the target ISA represented by `isa`. The generated
202    /// machine code is not relocated. Instead, any relocations can be
203    /// obtained from `compiled_code.buffer.relocs()`.
204    ///
205    /// Performs any optimizations that are enabled, unless
206    /// `optimize()` was already invoked.
207    ///
208    /// Returns the generated machine code as well as information about
209    /// the function's code and read-only data.
210    pub fn compile(
211        &mut self,
212        isa: &dyn TargetIsa,
213        ctrl_plane: &mut ControlPlane,
214    ) -> CompileResult<'_, &CompiledCode> {
215        let stencil = self
216            .compile_stencil(isa, ctrl_plane)
217            .map_err(|error| CompileError {
218                inner: error,
219                func: &self.func,
220            })?;
221        Ok(self
222            .compiled_code
223            .insert(stencil.apply_params(&self.func.params)))
224    }
225
226    /// If available, return information about the code layout in the
227    /// final machine code: the offsets (in bytes) of each basic-block
228    /// start, and all basic-block edges.
229    #[deprecated = "use CompiledCode::get_code_bb_layout"]
230    pub fn get_code_bb_layout(&self) -> Option<(Vec<usize>, Vec<(usize, usize)>)> {
231        self.compiled_code().map(CompiledCode::get_code_bb_layout)
232    }
233
234    /// Creates unwind information for the function.
235    ///
236    /// Returns `None` if the function has no unwind information.
237    #[cfg(feature = "unwind")]
238    #[deprecated = "use CompiledCode::create_unwind_info"]
239    pub fn create_unwind_info(
240        &self,
241        isa: &dyn TargetIsa,
242    ) -> CodegenResult<Option<crate::isa::unwind::UnwindInfo>> {
243        self.compiled_code().unwrap().create_unwind_info(isa)
244    }
245
246    /// Run the verifier on the function.
247    ///
248    /// Also check that the dominator tree and control flow graph are consistent with the function.
249    ///
250    /// TODO: rename to "CLIF validate" or similar.
251    pub fn verify<'a, FOI: Into<FlagsOrIsa<'a>>>(&self, fisa: FOI) -> VerifierResult<()> {
252        let mut errors = VerifierErrors::default();
253        let _ = verify_context(&self.func, &self.cfg, &self.domtree, fisa, &mut errors);
254
255        if errors.is_empty() {
256            Ok(())
257        } else {
258            Err(errors)
259        }
260    }
261
262    /// Run the verifier only if the `enable_verifier` setting is true.
263    pub fn verify_if<'a, FOI: Into<FlagsOrIsa<'a>>>(&self, fisa: FOI) -> CodegenResult<()> {
264        let fisa = fisa.into();
265        if fisa.flags.enable_verifier() {
266            self.verify(fisa)?;
267        }
268        Ok(())
269    }
270
271    /// Perform constant-phi removal on the function.
272    pub fn remove_constant_phis<'a, FOI: Into<FlagsOrIsa<'a>>>(
273        &mut self,
274        fisa: FOI,
275    ) -> CodegenResult<()> {
276        do_remove_constant_phis(&mut self.func, &mut self.domtree);
277        self.verify_if(fisa)?;
278        Ok(())
279    }
280
281    /// Perform NaN canonicalizing rewrites on the function.
282    pub fn canonicalize_nans(&mut self, isa: &dyn TargetIsa) -> CodegenResult<()> {
283        // Currently only RiscV64 is the only arch that may not have vector support.
284        let has_vector_support = match isa.triple().architecture {
285            Architecture::Riscv64(_) => match isa.isa_flags().iter().find(|f| f.name == "has_v") {
286                Some(value) => value.as_bool().unwrap_or(false),
287                None => false,
288            },
289            _ => true,
290        };
291        do_nan_canonicalization(&mut self.func, has_vector_support);
292        self.verify_if(isa)
293    }
294
295    /// Compute the control flow graph.
296    pub fn compute_cfg(&mut self) {
297        self.cfg.compute(&self.func)
298    }
299
300    /// Compute dominator tree.
301    pub fn compute_domtree(&mut self) {
302        self.domtree.compute(&self.func, &self.cfg);
303    }
304
305    /// Compute the loop analysis.
306    pub fn compute_loop_analysis(&mut self) {
307        self.loop_analysis
308            .compute(&self.func, &self.cfg, &self.domtree)
309    }
310
311    /// Compute the control flow graph and dominator tree.
312    pub fn flowgraph(&mut self) {
313        self.compute_cfg();
314        self.compute_domtree()
315    }
316
317    /// Perform unreachable code elimination.
318    pub fn eliminate_unreachable_code<'a, FOI>(&mut self, fisa: FOI) -> CodegenResult<()>
319    where
320        FOI: Into<FlagsOrIsa<'a>>,
321    {
322        let domtree = &self.domtree;
323        eliminate_unreachable_code(&mut self.func, &mut self.cfg, |block| {
324            domtree.is_reachable(block)
325        });
326        self.verify_if(fisa)
327    }
328
329    /// Replace all redundant loads with the known values in
330    /// memory. These are loads whose values were already loaded by
331    /// other loads earlier, as well as loads whose values were stored
332    /// by a store instruction to the same instruction (so-called
333    /// "store-to-load forwarding").
334    pub fn replace_redundant_loads(&mut self) -> CodegenResult<()> {
335        let mut analysis = AliasAnalysis::new(&self.func, &self.domtree);
336        analysis.compute_and_update_aliases(&mut self.func);
337        Ok(())
338    }
339
340    /// Harvest candidate left-hand sides for superoptimization with Souper.
341    #[cfg(feature = "souper-harvest")]
342    pub fn souper_harvest(
343        &mut self,
344        out: &mut std::sync::mpsc::Sender<String>,
345    ) -> CodegenResult<()> {
346        do_souper_harvest(&self.func, out);
347        Ok(())
348    }
349
350    /// Run optimizations via the egraph infrastructure.
351    pub fn egraph_pass<'a, FOI>(
352        &mut self,
353        fisa: FOI,
354        ctrl_plane: &mut ControlPlane,
355    ) -> CodegenResult<()>
356    where
357        FOI: Into<FlagsOrIsa<'a>>,
358    {
359        let _tt = timing::egraph();
360
361        trace!(
362            "About to optimize with egraph phase:\n{}",
363            self.func.display()
364        );
365        let fisa = fisa.into();
366        self.compute_loop_analysis();
367        let mut alias_analysis = AliasAnalysis::new(&self.func, &self.domtree);
368        let mut pass = EgraphPass::new(
369            &mut self.func,
370            &self.domtree,
371            &self.loop_analysis,
372            &mut alias_analysis,
373            ctrl_plane,
374            &mut self.cfg,
375        );
376        pass.run();
377        log::debug!("egraph stats: {:?}", pass.stats);
378        trace!("After egraph optimization:\n{}", self.func.display());
379
380        // Branch optimizations can invalidate these; recompute them.
381        self.compute_cfg();
382        self.compute_domtree();
383
384        self.verify_if(fisa)?;
385
386        Ok(())
387    }
388}