Skip to main content

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, Vendor};
15use wasmer_compiler::{
16    Compiler, CompilerConfig, Debugger, 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    /// Returns the debug directory where the debug files are written.
39    pub fn debug_dir(&self) -> &PathBuf {
40        &self.debug_dir
41    }
42
43    fn base_path(&self, module_hash: &Option<String>) -> PathBuf {
44        let mut path = self.debug_dir.clone();
45        if let Some(hash) = module_hash {
46            path.push(hash);
47        }
48        std::fs::create_dir_all(&path)
49            .unwrap_or_else(|_| panic!("cannot create debug directory: {}", path.display()));
50        path
51    }
52
53    /// Writes the pre-optimization intermediate representation to a debug file.
54    pub fn preopt_ir(&self, kind: &CompiledKind, module_hash: &Option<String>, mem_buffer: &[u8]) {
55        let mut path = self.base_path(module_hash);
56        path.push(function_kind_to_filename(kind, ".preopt.clif"));
57        let mut file =
58            File::create(path).expect("Error while creating debug file from Cranelift IR");
59        file.write_all(mem_buffer).unwrap();
60    }
61
62    /// Writes the object file memory buffer to a debug file.
63    pub fn obj_memory_buffer(
64        &self,
65        kind: &CompiledKind,
66        module_hash: &Option<String>,
67        mem_buffer: &[u8],
68    ) {
69        let mut path = self.base_path(module_hash);
70        path.push(function_kind_to_filename(kind, ".o"));
71        let mut file =
72            File::create(path).expect("Error while creating debug file from Cranelift object");
73        file.write_all(mem_buffer).unwrap();
74    }
75
76    /// Writes the assembly memory buffer to a debug file.
77    pub fn asm_memory_buffer(
78        &self,
79        kind: &CompiledKind,
80        module_hash: &Option<String>,
81        arch: Architecture,
82        mem_buffer: &[u8],
83    ) -> Result<(), wasmer_types::CompileError> {
84        let mut path = self.base_path(module_hash);
85        path.push(function_kind_to_filename(kind, ".s"));
86        save_assembly_to_file(arch, path, mem_buffer, HashMap::<usize, String>::new())
87    }
88}
89
90// Runtime Environment
91
92/// Possible optimization levels for the Cranelift codegen backend.
93#[non_exhaustive]
94#[derive(Clone, Debug)]
95pub enum CraneliftOptLevel {
96    /// No optimizations performed, minimizes compilation time by disabling most
97    /// optimizations.
98    None,
99    /// Generates the fastest possible code, but may take longer.
100    Speed,
101    /// Similar to `speed`, but also performs transformations aimed at reducing
102    /// code size.
103    SpeedAndSize,
104}
105
106/// Global configuration options used to create an
107/// `wasmer_engine::Engine` and customize its behavior.
108///
109/// This structure exposes a builder-like interface and is primarily
110/// consumed by `wasmer_engine::Engine::new`.
111#[derive(Debug, Clone)]
112pub struct Cranelift {
113    pub(crate) enable_nan_canonicalization: bool,
114    pub(crate) allow_experimental_unaligned_memory_accesses: bool,
115    enable_verifier: bool,
116    pub(crate) enable_perfmap: bool,
117    pub(crate) debugger: Option<Debugger>,
118    pub(crate) enable_pic: bool,
119    pub(crate) experimental_artifact: bool,
120    pub(crate) opt_level: CraneliftOptLevel,
121    /// The number of threads to use for compilation.
122    pub num_threads: NonZero<usize>,
123    /// The middleware chain.
124    pub(crate) middlewares: Vec<Arc<dyn ModuleMiddleware>>,
125    pub(crate) callbacks: Option<CraneliftCallbacks>,
126}
127
128impl Cranelift {
129    /// Creates a new configuration object with the default configuration
130    /// specified.
131    pub fn new() -> Self {
132        Self {
133            enable_nan_canonicalization: false,
134            allow_experimental_unaligned_memory_accesses: false,
135            enable_verifier: false,
136            opt_level: CraneliftOptLevel::Speed,
137            enable_pic: false,
138            experimental_artifact: false,
139            num_threads: std::thread::available_parallelism().unwrap_or(NonZero::new(1).unwrap()),
140            middlewares: vec![],
141            enable_perfmap: false,
142            debugger: None,
143            callbacks: None,
144        }
145    }
146
147    /// Enable the experimental artifact format.
148    pub fn experimental_artifact(&mut self, enable: bool) -> &mut Self {
149        self.experimental_artifact = enable;
150        self
151    }
152
153    /// Enable NaN canonicalization.
154    ///
155    /// NaN canonicalization is useful when trying to run WebAssembly
156    /// deterministically across different architectures.
157    pub fn canonicalize_nans(&mut self, enable: bool) -> &mut Self {
158        self.enable_nan_canonicalization = enable;
159        self
160    }
161
162    /// Enable run-time handling of potentially unaligned memory accesses.
163    /// Unaligned memory accesses occur when you try to read N bytes of data starting
164    /// from an address that is not evenly divisible by N.
165    ///
166    /// This feature is experimental and currently supports only scalar types.
167    pub fn allow_experimental_unaligned_memory_accesses(&mut self, enable: bool) -> &mut Self {
168        self.allow_experimental_unaligned_memory_accesses = enable;
169        self
170    }
171
172    /// Set the number of threads to use for compilation.
173    pub fn num_threads(&mut self, num_threads: NonZero<usize>) -> &mut Self {
174        self.num_threads = num_threads;
175        self
176    }
177
178    /// The optimization levels when optimizing the IR.
179    pub fn opt_level(&mut self, opt_level: CraneliftOptLevel) -> &mut Self {
180        self.opt_level = opt_level;
181        self
182    }
183
184    /// Generates the ISA for the provided target
185    pub fn isa(&self, target: &Target) -> CodegenResult<Arc<dyn TargetIsa>> {
186        let mut builder =
187            lookup(target.triple().clone()).expect("construct Cranelift ISA for triple");
188        // Cpu Features
189        let cpu_features = target.cpu_features();
190        if target.triple().architecture == Architecture::X86_64
191            && !cpu_features.contains(CpuFeature::SSE2)
192        {
193            panic!("x86 support requires SSE2");
194        }
195        if cpu_features.contains(CpuFeature::SSE3) {
196            builder.enable("has_sse3").expect("should be valid flag");
197        }
198        if cpu_features.contains(CpuFeature::SSSE3) {
199            builder.enable("has_ssse3").expect("should be valid flag");
200        }
201        if cpu_features.contains(CpuFeature::SSE41) {
202            builder.enable("has_sse41").expect("should be valid flag");
203        }
204        if cpu_features.contains(CpuFeature::SSE42) {
205            builder.enable("has_sse42").expect("should be valid flag");
206        }
207        if cpu_features.contains(CpuFeature::POPCNT) {
208            builder.enable("has_popcnt").expect("should be valid flag");
209        }
210        if cpu_features.contains(CpuFeature::AVX) {
211            builder.enable("has_avx").expect("should be valid flag");
212        }
213        if cpu_features.contains(CpuFeature::BMI1) {
214            builder.enable("has_bmi1").expect("should be valid flag");
215        }
216        if cpu_features.contains(CpuFeature::BMI2) {
217            builder.enable("has_bmi2").expect("should be valid flag");
218        }
219        if cpu_features.contains(CpuFeature::AVX2) {
220            builder.enable("has_avx2").expect("should be valid flag");
221        }
222        if cpu_features.contains(CpuFeature::AVX512DQ) {
223            builder
224                .enable("has_avx512dq")
225                .expect("should be valid flag");
226        }
227        if cpu_features.contains(CpuFeature::AVX512VL) {
228            builder
229                .enable("has_avx512vl")
230                .expect("should be valid flag");
231        }
232        if cpu_features.contains(CpuFeature::LZCNT) {
233            builder.enable("has_lzcnt").expect("should be valid flag");
234        }
235
236        builder.finish(self.flags(target))
237    }
238
239    /// Generates the flags for the compiler
240    pub fn flags(&self, target: &Target) -> settings::Flags {
241        let mut flags = settings::builder();
242
243        // Enable probestack
244        flags
245            .enable("enable_probestack")
246            .expect("should be valid flag");
247
248        // Always use inline stack probes (otherwise the call to Probestack needs to be relocated).
249        flags
250            .set("probestack_strategy", "inline")
251            .expect("should be valid flag");
252
253        if self.enable_pic {
254            flags.enable("is_pic").expect("should be a valid flag");
255        }
256
257        // These trampolines are always reachable through short jumps.
258        flags
259            .enable("use_colocated_libcalls")
260            .expect("should be a valid flag");
261
262        if matches!(target.triple().operating_system, OperatingSystem::Windows) {
263            // For macOS and Linux we rely on the precise `ReturnAbi` calling conventions.
264            flags
265                .enable("enable_multi_ret_implicit_sret")
266                .expect("should be a valid flag");
267        }
268
269        // Invert cranelift's default-on verification to instead default off.
270        flags
271            .set("enable_verifier", &self.enable_verifier.to_string())
272            .expect("should be valid flag");
273
274        flags
275            .set(
276                "opt_level",
277                match self.opt_level {
278                    CraneliftOptLevel::None => "none",
279                    CraneliftOptLevel::Speed => "speed",
280                    CraneliftOptLevel::SpeedAndSize => "speed_and_size",
281                },
282            )
283            .expect("should be valid flag");
284
285        flags
286            .set(
287                "enable_nan_canonicalization",
288                &self.enable_nan_canonicalization.to_string(),
289            )
290            .expect("should be valid flag");
291
292        if matches!(target.triple().vendor, Vendor::Apple) {
293            flags
294                .enable("enable_compact_unwind_abi")
295                .expect("should be valid flag");
296        }
297
298        settings::Flags::new(flags)
299    }
300
301    /// Callbacks that will triggered in the different compilation
302    /// phases in Cranelift.
303    pub fn callbacks(&mut self, callbacks: Option<CraneliftCallbacks>) -> &mut Self {
304        self.callbacks = callbacks;
305        self
306    }
307}
308
309impl CompilerConfig for Cranelift {
310    fn experimental_artifact(&mut self, enable: bool) {
311        self.experimental_artifact = enable;
312    }
313
314    fn enable_pic(&mut self) {
315        self.enable_pic = true;
316    }
317
318    fn enable_verifier(&mut self) {
319        self.enable_verifier = true;
320    }
321
322    fn enable_perfmap(&mut self) {
323        self.enable_perfmap = true;
324    }
325
326    fn enable_debugger(&mut self, debugger: Debugger) {
327        self.debugger = Some(debugger);
328    }
329
330    fn enable_experimental_unaligned_memory_accesses(&mut self) {
331        self.allow_experimental_unaligned_memory_accesses = true;
332    }
333
334    fn canonicalize_nans(&mut self, enable: bool) {
335        self.enable_nan_canonicalization = enable;
336    }
337
338    /// Transform it into the compiler
339    fn compiler(self: Box<Self>) -> Box<dyn Compiler> {
340        Box::new(CraneliftCompiler::new(*self))
341    }
342
343    /// Pushes a middleware onto the back of the middleware chain.
344    fn push_middleware(&mut self, middleware: Arc<dyn ModuleMiddleware>) {
345        self.middlewares.push(middleware);
346    }
347
348    fn supported_features_for_target(&self, target: &Target) -> wasmer_types::Features {
349        let mut feats = Features::default();
350
351        if matches!(
352            target.triple().operating_system,
353            OperatingSystem::Linux | OperatingSystem::Darwin(_)
354        ) {
355            feats.exceptions(true);
356        }
357        feats.exceptions(true);
358        feats.relaxed_simd(true);
359        feats.wide_arithmetic(true);
360        feats
361    }
362}
363
364impl Default for Cranelift {
365    fn default() -> Self {
366        Self::new()
367    }
368}
369
370impl From<Cranelift> for Engine {
371    fn from(config: Cranelift) -> Self {
372        EngineBuilder::new(config).engine()
373    }
374}