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