wasmer_compiler_cranelift/
config.rs

1use crate::compiler::CraneliftCompiler;
2use cranelift_codegen::{
3    CodegenResult,
4    isa::{TargetIsa, lookup},
5    settings::{self, Configurable},
6};
7use std::{
8    collections::HashMap,
9    fs::File,
10    io::{self, Write},
11    sync::Arc,
12};
13use std::{num::NonZero, path::PathBuf};
14use target_lexicon::OperatingSystem;
15use wasmer_compiler::{
16    Compiler, CompilerConfig, Engine, EngineBuilder, ModuleMiddleware,
17    misc::{CompiledKind, function_kind_to_filename, save_assembly_to_file},
18};
19use wasmer_types::{
20    Features,
21    target::{Architecture, CpuFeature, Target},
22};
23
24/// Callbacks to the different Cranelift compilation phases.
25#[derive(Debug, Clone)]
26pub struct CraneliftCallbacks {
27    debug_dir: PathBuf,
28}
29
30impl CraneliftCallbacks {
31    /// Creates a new instance of `CraneliftCallbacks` with the specified debug directory.
32    pub fn new(debug_dir: PathBuf) -> Result<Self, io::Error> {
33        // Create the debug dir in case it doesn't exist
34        std::fs::create_dir_all(&debug_dir)?;
35        Ok(Self { debug_dir })
36    }
37
38    /// Writes the pre-optimization intermediate representation to a debug file.
39    pub fn preopt_ir(&self, kind: &CompiledKind, mem_buffer: &[u8]) {
40        let mut path = self.debug_dir.clone();
41        path.push(function_kind_to_filename(kind, ".preopt.clif"));
42        let mut file =
43            File::create(path).expect("Error while creating debug file from Cranelift IR");
44        file.write_all(mem_buffer).unwrap();
45    }
46
47    /// Writes the object file memory buffer to a debug file.
48    pub fn obj_memory_buffer(&self, kind: &CompiledKind, mem_buffer: &[u8]) {
49        let mut path = self.debug_dir.clone();
50        path.push(function_kind_to_filename(kind, ".o"));
51        let mut file =
52            File::create(path).expect("Error while creating debug file from Cranelift object");
53        file.write_all(mem_buffer).unwrap();
54    }
55
56    /// Writes the assembly memory buffer to a debug file.
57    pub fn asm_memory_buffer(
58        &self,
59        kind: &CompiledKind,
60        arch: Architecture,
61        mem_buffer: &[u8],
62    ) -> Result<(), wasmer_types::CompileError> {
63        let mut path = self.debug_dir.clone();
64        path.push(function_kind_to_filename(kind, ".s"));
65        save_assembly_to_file(arch, path, mem_buffer, HashMap::<usize, String>::new())
66    }
67}
68
69// Runtime Environment
70
71/// Possible optimization levels for the Cranelift codegen backend.
72#[non_exhaustive]
73#[derive(Clone, Debug)]
74pub enum CraneliftOptLevel {
75    /// No optimizations performed, minimizes compilation time by disabling most
76    /// optimizations.
77    None,
78    /// Generates the fastest possible code, but may take longer.
79    Speed,
80    /// Similar to `speed`, but also performs transformations aimed at reducing
81    /// code size.
82    SpeedAndSize,
83}
84
85/// Global configuration options used to create an
86/// `wasmer_engine::Engine` and customize its behavior.
87///
88/// This structure exposes a builder-like interface and is primarily
89/// consumed by `wasmer_engine::Engine::new`.
90#[derive(Debug, Clone)]
91pub struct Cranelift {
92    enable_nan_canonicalization: bool,
93    enable_verifier: bool,
94    pub(crate) enable_perfmap: bool,
95    enable_pic: bool,
96    opt_level: CraneliftOptLevel,
97    /// The number of threads to use for compilation.
98    pub num_threads: NonZero<usize>,
99    /// The middleware chain.
100    pub(crate) middlewares: Vec<Arc<dyn ModuleMiddleware>>,
101    pub(crate) callbacks: Option<CraneliftCallbacks>,
102}
103
104impl Cranelift {
105    /// Creates a new configuration object with the default configuration
106    /// specified.
107    pub fn new() -> Self {
108        Self {
109            enable_nan_canonicalization: false,
110            enable_verifier: false,
111            opt_level: CraneliftOptLevel::Speed,
112            enable_pic: false,
113            num_threads: std::thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap()),
114            middlewares: vec![],
115            enable_perfmap: false,
116            callbacks: None,
117        }
118    }
119
120    /// Enable NaN canonicalization.
121    ///
122    /// NaN canonicalization is useful when trying to run WebAssembly
123    /// deterministically across different architectures.
124    pub fn canonicalize_nans(&mut self, enable: bool) -> &mut Self {
125        self.enable_nan_canonicalization = enable;
126        self
127    }
128
129    /// Set the number of threads to use for compilation.
130    pub fn num_threads(&mut self, num_threads: NonZero<usize>) -> &mut Self {
131        self.num_threads = num_threads;
132        self
133    }
134
135    /// The optimization levels when optimizing the IR.
136    pub fn opt_level(&mut self, opt_level: CraneliftOptLevel) -> &mut Self {
137        self.opt_level = opt_level;
138        self
139    }
140
141    /// Generates the ISA for the provided target
142    pub fn isa(&self, target: &Target) -> CodegenResult<Arc<dyn TargetIsa>> {
143        let mut builder =
144            lookup(target.triple().clone()).expect("construct Cranelift ISA for triple");
145        // Cpu Features
146        let cpu_features = target.cpu_features();
147        if target.triple().architecture == Architecture::X86_64
148            && !cpu_features.contains(CpuFeature::SSE2)
149        {
150            panic!("x86 support requires SSE2");
151        }
152        if cpu_features.contains(CpuFeature::SSE3) {
153            builder.enable("has_sse3").expect("should be valid flag");
154        }
155        if cpu_features.contains(CpuFeature::SSSE3) {
156            builder.enable("has_ssse3").expect("should be valid flag");
157        }
158        if cpu_features.contains(CpuFeature::SSE41) {
159            builder.enable("has_sse41").expect("should be valid flag");
160        }
161        if cpu_features.contains(CpuFeature::SSE42) {
162            builder.enable("has_sse42").expect("should be valid flag");
163        }
164        if cpu_features.contains(CpuFeature::POPCNT) {
165            builder.enable("has_popcnt").expect("should be valid flag");
166        }
167        if cpu_features.contains(CpuFeature::AVX) {
168            builder.enable("has_avx").expect("should be valid flag");
169        }
170        if cpu_features.contains(CpuFeature::BMI1) {
171            builder.enable("has_bmi1").expect("should be valid flag");
172        }
173        if cpu_features.contains(CpuFeature::BMI2) {
174            builder.enable("has_bmi2").expect("should be valid flag");
175        }
176        if cpu_features.contains(CpuFeature::AVX2) {
177            builder.enable("has_avx2").expect("should be valid flag");
178        }
179        if cpu_features.contains(CpuFeature::AVX512DQ) {
180            builder
181                .enable("has_avx512dq")
182                .expect("should be valid flag");
183        }
184        if cpu_features.contains(CpuFeature::AVX512VL) {
185            builder
186                .enable("has_avx512vl")
187                .expect("should be valid flag");
188        }
189        if cpu_features.contains(CpuFeature::LZCNT) {
190            builder.enable("has_lzcnt").expect("should be valid flag");
191        }
192
193        builder.finish(self.flags())
194    }
195
196    /// Generates the flags for the compiler
197    pub fn flags(&self) -> settings::Flags {
198        let mut flags = settings::builder();
199
200        // Enable probestack
201        flags
202            .enable("enable_probestack")
203            .expect("should be valid flag");
204
205        // Always use inline stack probes (otherwise the call to Probestack needs to be relocated).
206        flags
207            .set("probestack_strategy", "inline")
208            .expect("should be valid flag");
209
210        if self.enable_pic {
211            flags.enable("is_pic").expect("should be a valid flag");
212        }
213
214        // We set up libcall trampolines in engine-universal.
215        // These trampolines are always reachable through short jumps.
216        flags
217            .enable("use_colocated_libcalls")
218            .expect("should be a valid flag");
219
220        // Allow Cranelift to implicitly spill multi-value returns via a hidden
221        // StructReturn argument when register results are exhausted.
222        flags
223            .enable("enable_multi_ret_implicit_sret")
224            .expect("should be a valid flag");
225
226        // Invert cranelift's default-on verification to instead default off.
227        let enable_verifier = if self.enable_verifier {
228            "true"
229        } else {
230            "false"
231        };
232        flags
233            .set("enable_verifier", enable_verifier)
234            .expect("should be valid flag");
235
236        flags
237            .set(
238                "opt_level",
239                match self.opt_level {
240                    CraneliftOptLevel::None => "none",
241                    CraneliftOptLevel::Speed => "speed",
242                    CraneliftOptLevel::SpeedAndSize => "speed_and_size",
243                },
244            )
245            .expect("should be valid flag");
246
247        let enable_nan_canonicalization = if self.enable_nan_canonicalization {
248            "true"
249        } else {
250            "false"
251        };
252        flags
253            .set("enable_nan_canonicalization", enable_nan_canonicalization)
254            .expect("should be valid flag");
255
256        settings::Flags::new(flags)
257    }
258
259    /// Callbacks that will triggered in the different compilation
260    /// phases in Cranelift.
261    pub fn callbacks(&mut self, callbacks: Option<CraneliftCallbacks>) -> &mut Self {
262        self.callbacks = callbacks;
263        self
264    }
265}
266
267impl CompilerConfig for Cranelift {
268    fn enable_pic(&mut self) {
269        self.enable_pic = true;
270    }
271
272    fn enable_verifier(&mut self) {
273        self.enable_verifier = true;
274    }
275
276    fn enable_perfmap(&mut self) {
277        self.enable_perfmap = true;
278    }
279
280    fn canonicalize_nans(&mut self, enable: bool) {
281        self.enable_nan_canonicalization = enable;
282    }
283
284    /// Transform it into the compiler
285    fn compiler(self: Box<Self>) -> Box<dyn Compiler> {
286        Box::new(CraneliftCompiler::new(*self))
287    }
288
289    /// Pushes a middleware onto the back of the middleware chain.
290    fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>) {
291        self.middlewares.push(middleware);
292    }
293
294    fn supported_features_for_target(&self, target: &Target) -> wasmer_types::Features {
295        let mut feats = Features::default();
296        if target.triple().operating_system == OperatingSystem::Linux {
297            feats.exceptions(true);
298        }
299        feats
300    }
301}
302
303impl Default for Cranelift {
304    fn default() -> Self {
305        Self::new()
306    }
307}
308
309impl From<Cranelift> for Engine {
310    fn from(config: Cranelift) -> Self {
311        EngineBuilder::new(config).engine()
312    }
313}