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