wasmer_cli/
backend.rs

1//! Common module with common used structures across different
2//! commands.
3
4// NOTE: A lot of this code depends on feature flags.
5// To not go crazy with annotations, some lints are disabled for the whole
6// module.
7#![allow(dead_code, unused_imports, unused_variables)]
8
9use std::num::NonZero;
10use std::string::ToString;
11use std::sync::Arc;
12use std::{path::PathBuf, str::FromStr};
13
14use anyhow::{Context, Result, bail};
15#[cfg(feature = "sys")]
16use wasmer::sys::*;
17use wasmer::*;
18use wasmer_types::{Features, target::Target};
19
20#[cfg(feature = "compiler")]
21use wasmer_compiler::CompilerConfig;
22
23use wasmer::Engine;
24
25#[derive(Debug, clap::Parser, Clone, Default)]
26/// The WebAssembly features that can be passed through the
27/// Command Line args.
28pub struct WasmFeatures {
29    /// Enable support for the SIMD proposal.
30    #[clap(long = "enable-simd")]
31    pub simd: bool,
32
33    /// Disable support for the threads proposal.
34    #[clap(long = "disable-threads")]
35    pub disable_threads: bool,
36
37    /// Deprecated, threads are enabled by default.
38    #[clap(long = "enable-threads")]
39    pub _threads: bool,
40
41    /// Enable support for the reference types proposal.
42    #[clap(long = "enable-reference-types")]
43    pub reference_types: bool,
44
45    /// Enable support for the multi value proposal.
46    #[clap(long = "enable-multi-value")]
47    pub multi_value: bool,
48
49    /// Enable support for the bulk memory proposal.
50    #[clap(long = "enable-bulk-memory")]
51    pub bulk_memory: bool,
52
53    /// Enable support for the tail call proposal.
54    #[clap(long = "enable-tail-call")]
55    pub tail_call: bool,
56
57    /// Enable support for the module linking proposal.
58    #[clap(long = "enable-module-linking")]
59    pub module_linking: bool,
60
61    /// Enable support for the multi memory proposal.
62    #[clap(long = "enable-multi-memory")]
63    pub multi_memory: bool,
64
65    /// Enable support for the memory64 proposal.
66    #[clap(long = "enable-memory64")]
67    pub memory64: bool,
68
69    /// Enable support for the exceptions proposal.
70    #[clap(long = "enable-exceptions")]
71    pub exceptions: bool,
72
73    /// Enable support for the relaxed SIMD proposal.
74    #[clap(long = "enable-relaxed-simd")]
75    pub relaxed_simd: bool,
76
77    /// Enable support for the extended constant expressions proposal.
78    #[clap(long = "enable-extended-const")]
79    pub extended_const: bool,
80
81    /// Enable support for all pre-standard proposals.
82    #[clap(long = "enable-all")]
83    pub all: bool,
84}
85
86#[derive(Debug, Clone, clap::Parser, Default)]
87/// The compiler options
88pub struct RuntimeOptions {
89    /// Use Singlepass compiler.
90    #[cfg(feature = "singlepass")]
91    #[clap(short, long, conflicts_with_all = &Vec::<&str>::from_iter([
92        #[cfg(feature = "llvm")]
93        "llvm", 
94        #[cfg(feature = "v8")]
95        "v8", 
96        #[cfg(feature = "cranelift")]
97        "cranelift", 
98        #[cfg(feature = "wamr")]
99        "wamr", 
100        #[cfg(feature = "wasmi")]
101        "wasmi"
102    ]))]
103    singlepass: bool,
104
105    /// Use Cranelift compiler.
106    #[cfg(feature = "cranelift")]
107    #[clap(short, long, conflicts_with_all = &Vec::<&str>::from_iter([
108        #[cfg(feature = "llvm")]
109        "llvm", 
110        #[cfg(feature = "v8")]
111        "v8", 
112        #[cfg(feature = "singlepass")]
113        "singlepass", 
114        #[cfg(feature = "wamr")]
115        "wamr", 
116        #[cfg(feature = "wasmi")]
117        "wasmi"
118    ]))]
119    cranelift: bool,
120
121    /// Use LLVM compiler.
122    #[cfg(feature = "llvm")]
123    #[clap(short, long, conflicts_with_all = &Vec::<&str>::from_iter([
124        #[cfg(feature = "cranelift")]
125        "cranelift", 
126        #[cfg(feature = "v8")]
127        "v8", 
128        #[cfg(feature = "singlepass")]
129        "singlepass", 
130        #[cfg(feature = "wamr")]
131        "wamr", 
132        #[cfg(feature = "wasmi")]
133        "wasmi"
134    ]))]
135    llvm: bool,
136
137    /// Use the V8 runtime.
138    #[cfg(feature = "v8")]
139    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
140        #[cfg(feature = "cranelift")]
141        "cranelift", 
142        #[cfg(feature = "llvm")]
143        "llvm", 
144        #[cfg(feature = "singlepass")]
145        "singlepass", 
146        #[cfg(feature = "wamr")]
147        "wamr", 
148        #[cfg(feature = "wasmi")]
149        "wasmi"
150    ]))]
151    v8: bool,
152
153    /// Use WAMR.
154    #[cfg(feature = "wamr")]
155    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
156        #[cfg(feature = "cranelift")]
157        "cranelift", 
158        #[cfg(feature = "llvm")]
159        "llvm", 
160        #[cfg(feature = "singlepass")]
161        "singlepass", 
162        #[cfg(feature = "v8")]
163        "v8", 
164        #[cfg(feature = "wasmi")]
165        "wasmi"
166    ]))]
167    wamr: bool,
168
169    /// Use the wasmi runtime.
170    #[cfg(feature = "wasmi")]
171    #[clap(long, conflicts_with_all = &Vec::<&str>::from_iter([
172        #[cfg(feature = "cranelift")]
173        "cranelift", 
174        #[cfg(feature = "llvm")]
175        "llvm", 
176        #[cfg(feature = "singlepass")]
177        "singlepass", 
178        #[cfg(feature = "v8")]
179        "v8", 
180        #[cfg(feature = "wamr")]
181        "wamr"
182    ]))]
183    wasmi: bool,
184
185    /// Enable compiler internal verification.
186    ///
187    /// Available for cranelift, LLVM and singlepass.
188    #[clap(long)]
189    enable_verifier: bool,
190
191    /// Debug directory, where IR and object files will be written to.
192    ///
193    /// Available for cranelift, LLVM and singlepass.
194    #[clap(long, alias = "llvm-debug-dir")]
195    compiler_debug_dir: Option<PathBuf>,
196
197    /// Enable a profiler.
198    ///
199    /// Available for cranelift, LLVM and singlepass.
200    #[clap(long, value_enum)]
201    profiler: Option<Profiler>,
202
203    /// Only available for the LLVM compiler. Enable the "pass-params" optimization, where the first (#0)
204    /// global and the first (#0) memory passed between guest functions as explicit parameters.
205    #[cfg(feature = "llvm")]
206    #[clap(long)]
207    enable_pass_params_opt: bool,
208
209    /// Only available for the LLVM compiler. Sets the number of threads used to compile the
210    /// input module(s).
211    #[cfg(feature = "llvm")]
212    #[clap(long)]
213    llvm_num_threads: Option<NonZero<usize>>,
214
215    #[clap(flatten)]
216    features: WasmFeatures,
217}
218
219#[derive(Clone, Debug)]
220pub enum Profiler {
221    /// Perfmap-based profilers.
222    Perfmap,
223}
224
225impl FromStr for Profiler {
226    type Err = anyhow::Error;
227
228    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
229        match s.to_lowercase().as_str() {
230            "perfmap" => Ok(Self::Perfmap),
231            _ => Err(anyhow::anyhow!("Unrecognized profiler: {s}")),
232        }
233    }
234}
235
236impl RuntimeOptions {
237    pub fn get_available_backends(&self) -> Result<Vec<BackendType>> {
238        // If a specific backend is explicitly requested, use it
239        #[cfg(feature = "cranelift")]
240        {
241            if self.cranelift {
242                return Ok(vec![BackendType::Cranelift]);
243            }
244        }
245
246        #[cfg(feature = "llvm")]
247        {
248            if self.llvm {
249                return Ok(vec![BackendType::LLVM]);
250            }
251        }
252
253        #[cfg(feature = "singlepass")]
254        {
255            if self.singlepass {
256                return Ok(vec![BackendType::Singlepass]);
257            }
258        }
259
260        #[cfg(feature = "wamr")]
261        {
262            if self.wamr {
263                return Ok(vec![BackendType::Wamr]);
264            }
265        }
266
267        #[cfg(feature = "v8")]
268        {
269            if self.v8 {
270                return Ok(vec![BackendType::V8]);
271            }
272        }
273
274        #[cfg(feature = "wasmi")]
275        {
276            if self.wasmi {
277                return Ok(vec![BackendType::Wasmi]);
278            }
279        }
280
281        Ok(BackendType::enabled())
282    }
283
284    /// Filter enabled backends based on required WebAssembly features
285    pub fn filter_backends_by_features(
286        backends: Vec<BackendType>,
287        required_features: &Features,
288        target: &Target,
289    ) -> Vec<BackendType> {
290        backends
291            .into_iter()
292            .filter(|backend| backend.supports_features(required_features, target))
293            .collect()
294    }
295
296    pub fn get_store(&self) -> Result<Store> {
297        let engine = self.get_engine(&Target::default())?;
298        Ok(Store::new(engine))
299    }
300
301    pub fn get_engine(&self, target: &Target) -> Result<Engine> {
302        let backends = self.get_available_backends()?;
303        let backend = backends.first().context("no compiler backend enabled")?;
304        let backend_kind = wasmer::BackendKind::from(backend);
305        let required_features = wasmer::Engine::default_features_for_backend(&backend_kind, target);
306        backend.get_engine(target, &required_features, self)
307    }
308
309    pub fn get_engine_for_module(&self, module_contents: &[u8], target: &Target) -> Result<Engine> {
310        let required_features = self
311            .detect_features_from_wasm(module_contents)
312            .unwrap_or_default();
313
314        self.get_engine_for_features(&required_features, target)
315    }
316
317    pub fn get_engine_for_features(
318        &self,
319        required_features: &Features,
320        target: &Target,
321    ) -> Result<Engine> {
322        let backends = self.get_available_backends()?;
323        let filtered_backends =
324            Self::filter_backends_by_features(backends.clone(), required_features, target);
325
326        if filtered_backends.is_empty() {
327            let enabled_backends = BackendType::enabled();
328            if backends.len() == 1 && enabled_backends.len() > 1 {
329                // If the user has chosen an specific backend, we can suggest to use another one
330                let filtered_backends =
331                    Self::filter_backends_by_features(enabled_backends, required_features, target);
332                let extra_text: String = if !filtered_backends.is_empty() {
333                    format!(". You can use --{} instead", filtered_backends[0])
334                } else {
335                    "".to_string()
336                };
337                bail!(
338                    "The {} backend does not support the required features for the Wasm module{}",
339                    backends[0],
340                    extra_text
341                );
342            } else {
343                bail!(
344                    "No backends support the required features for the Wasm module. Feel free to open an issue at https://github.com/wasmerio/wasmer/issues"
345                );
346            }
347        }
348        filtered_backends
349            .first()
350            .unwrap()
351            .get_engine(target, required_features, self)
352    }
353
354    #[cfg(feature = "compiler")]
355    /// Get the enaled Wasm features.
356    pub fn get_features(&self, features: &Features) -> Result<Features> {
357        let mut result = features.clone();
358        if !self.features.disable_threads || self.features.all {
359            result.threads(true);
360        }
361        if self.features.disable_threads && !self.features.all {
362            result.threads(false);
363        }
364        if self.features.multi_value || self.features.all {
365            result.multi_value(true);
366        }
367        if self.features.simd || self.features.all {
368            result.simd(true);
369        }
370        if self.features.bulk_memory || self.features.all {
371            result.bulk_memory(true);
372        }
373        if self.features.reference_types || self.features.all {
374            result.reference_types(true);
375        }
376        Ok(result)
377    }
378
379    #[cfg(feature = "compiler")]
380    /// Get a copy of the default features with user-configured options
381    pub fn get_configured_features(&self) -> Result<Features> {
382        let features = Features::default();
383        self.get_features(&features)
384    }
385
386    /// Detect features from a WebAssembly module binary.
387    pub fn detect_features_from_wasm(
388        &self,
389        wasm_bytes: &[u8],
390    ) -> Result<Features, wasmparser::BinaryReaderError> {
391        let mut features = Features::detect_from_wasm(wasm_bytes)?;
392
393        // Merge with user-configured features
394        if !self.features.disable_threads || self.features.all {
395            features.threads(true);
396        }
397        if self.features.reference_types || self.features.all {
398            features.reference_types(true);
399        }
400        if self.features.simd || self.features.all {
401            features.simd(true);
402        }
403        if self.features.bulk_memory || self.features.all {
404            features.bulk_memory(true);
405        }
406        if self.features.multi_value || self.features.all {
407            features.multi_value(true);
408        }
409        if self.features.tail_call || self.features.all {
410            features.tail_call(true);
411        }
412        if self.features.module_linking || self.features.all {
413            features.module_linking(true);
414        }
415        if self.features.multi_memory || self.features.all {
416            features.multi_memory(true);
417        }
418        if self.features.memory64 || self.features.all {
419            features.memory64(true);
420        }
421        if self.features.exceptions || self.features.all {
422            features.exceptions(true);
423        }
424
425        Ok(features)
426    }
427
428    #[cfg(feature = "compiler")]
429    pub fn get_sys_compiler_engine_for_target(
430        &self,
431        target: Target,
432    ) -> std::result::Result<Engine, anyhow::Error> {
433        let backends = self.get_available_backends()?;
434        let compiler_config = self.get_sys_compiler_config(backends.first().unwrap())?;
435        let default_features = compiler_config.default_features_for_target(&target);
436        let features = self.get_features(&default_features)?;
437        Ok(wasmer_compiler::EngineBuilder::new(compiler_config)
438            .set_features(Some(features))
439            .set_target(Some(target))
440            .engine()
441            .into())
442    }
443
444    #[allow(unused_variables)]
445    #[cfg(feature = "compiler")]
446    pub(crate) fn get_sys_compiler_config(
447        &self,
448        rt: &BackendType,
449    ) -> Result<Box<dyn CompilerConfig>> {
450        let compiler_config: Box<dyn CompilerConfig> = match rt {
451            BackendType::Headless => bail!("The headless engine can't be chosen"),
452            #[cfg(feature = "singlepass")]
453            BackendType::Singlepass => {
454                let mut config = wasmer_compiler_singlepass::Singlepass::new();
455                if self.enable_verifier {
456                    config.enable_verifier();
457                }
458                if let Some(p) = &self.profiler {
459                    match p {
460                        Profiler::Perfmap => config.enable_perfmap(),
461                    }
462                }
463                if let Some(mut debug_dir) = self.compiler_debug_dir.clone() {
464                    use wasmer_compiler_singlepass::SinglepassCallbacks;
465
466                    debug_dir.push("singlepass");
467                    config.callbacks(Some(SinglepassCallbacks::new(debug_dir)?));
468                }
469
470                Box::new(config)
471            }
472            #[cfg(feature = "cranelift")]
473            BackendType::Cranelift => {
474                let mut config = wasmer_compiler_cranelift::Cranelift::new();
475                if self.enable_verifier {
476                    config.enable_verifier();
477                }
478                if let Some(p) = &self.profiler {
479                    match p {
480                        Profiler::Perfmap => config.enable_perfmap(),
481                    }
482                }
483                if let Some(mut debug_dir) = self.compiler_debug_dir.clone() {
484                    use wasmer_compiler_cranelift::CraneliftCallbacks;
485
486                    debug_dir.push("cranelift");
487                    config.callbacks(Some(CraneliftCallbacks::new(debug_dir)?));
488                }
489                Box::new(config)
490            }
491            #[cfg(feature = "llvm")]
492            BackendType::LLVM => {
493                use wasmer_compiler_llvm::LLVMCallbacks;
494                use wasmer_types::entity::EntityRef;
495                let mut config = LLVM::new();
496
497                if self.enable_pass_params_opt {
498                    config.enable_pass_params_opt();
499                }
500
501                if let Some(num_threads) = self.llvm_num_threads {
502                    config.num_threads(num_threads);
503                }
504
505                if let Some(mut debug_dir) = self.compiler_debug_dir.clone() {
506                    debug_dir.push("llvm");
507                    config.callbacks(Some(LLVMCallbacks::new(debug_dir)?));
508                }
509                if self.enable_verifier {
510                    config.enable_verifier();
511                }
512                if let Some(p) = &self.profiler {
513                    match p {
514                        Profiler::Perfmap => config.enable_perfmap(),
515                    }
516                }
517
518                Box::new(config)
519            }
520            BackendType::V8 | BackendType::Wamr | BackendType::Wasmi => unreachable!(),
521            #[cfg(not(all(feature = "singlepass", feature = "cranelift", feature = "llvm")))]
522            compiler => {
523                bail!("The `{compiler}` compiler is not included in this binary.")
524            }
525        };
526
527        #[allow(unreachable_code)]
528        Ok(compiler_config)
529    }
530}
531
532/// The compiler used for the store
533#[derive(Debug, PartialEq, Eq, Clone, Copy)]
534#[allow(clippy::upper_case_acronyms, dead_code)]
535pub enum BackendType {
536    /// Singlepass compiler
537    Singlepass,
538
539    /// Cranelift compiler
540    Cranelift,
541
542    /// LLVM compiler
543    LLVM,
544
545    /// V8 runtime
546    V8,
547
548    /// Wamr runtime
549    Wamr,
550
551    /// Wasmi runtime
552    Wasmi,
553
554    /// Headless compiler
555    #[allow(dead_code)]
556    Headless,
557}
558
559impl BackendType {
560    /// Return all enabled compilers
561    pub fn enabled() -> Vec<Self> {
562        vec![
563            #[cfg(feature = "cranelift")]
564            Self::Cranelift,
565            #[cfg(feature = "llvm")]
566            Self::LLVM,
567            #[cfg(feature = "singlepass")]
568            Self::Singlepass,
569            #[cfg(feature = "v8")]
570            Self::V8,
571            #[cfg(feature = "wamr")]
572            Self::Wamr,
573            #[cfg(feature = "wasmi")]
574            Self::Wasmi,
575        ]
576    }
577
578    /// Get an engine for this backend type
579    pub fn get_engine(
580        &self,
581        target: &Target,
582        features: &Features,
583        runtime_opts: &RuntimeOptions,
584    ) -> Result<Engine> {
585        match self {
586            #[cfg(feature = "singlepass")]
587            Self::Singlepass => {
588                let mut config = wasmer_compiler_singlepass::Singlepass::new();
589                if runtime_opts.enable_verifier {
590                    config.enable_verifier();
591                }
592                if let Some(p) = &runtime_opts.profiler {
593                    match p {
594                        Profiler::Perfmap => config.enable_perfmap(),
595                    }
596                }
597                if let Some(mut debug_dir) = runtime_opts.compiler_debug_dir.clone() {
598                    use wasmer_compiler_singlepass::SinglepassCallbacks;
599
600                    debug_dir.push("singlepass");
601                    config.callbacks(Some(SinglepassCallbacks::new(debug_dir)?));
602                }
603                let engine = wasmer_compiler::EngineBuilder::new(config)
604                    .set_features(Some(features.clone()))
605                    .set_target(Some(target.clone()))
606                    .engine()
607                    .into();
608                Ok(engine)
609            }
610            #[cfg(feature = "cranelift")]
611            Self::Cranelift => {
612                let mut config = wasmer_compiler_cranelift::Cranelift::new();
613                if runtime_opts.enable_verifier {
614                    config.enable_verifier();
615                }
616                if let Some(p) = &runtime_opts.profiler {
617                    match p {
618                        Profiler::Perfmap => config.enable_perfmap(),
619                    }
620                }
621                if let Some(mut debug_dir) = runtime_opts.compiler_debug_dir.clone() {
622                    use wasmer_compiler_cranelift::CraneliftCallbacks;
623
624                    debug_dir.push("cranelift");
625                    config.callbacks(Some(CraneliftCallbacks::new(debug_dir)?));
626                }
627                let engine = wasmer_compiler::EngineBuilder::new(config)
628                    .set_features(Some(features.clone()))
629                    .set_target(Some(target.clone()))
630                    .engine()
631                    .into();
632                Ok(engine)
633            }
634            #[cfg(feature = "llvm")]
635            Self::LLVM => {
636                use wasmer_compiler_llvm::LLVMCallbacks;
637                use wasmer_types::entity::EntityRef;
638
639                let mut config = wasmer_compiler_llvm::LLVM::new();
640
641                if let Some(mut debug_dir) = runtime_opts.compiler_debug_dir.clone() {
642                    debug_dir.push("llvm");
643                    config.callbacks(Some(LLVMCallbacks::new(debug_dir)?));
644                }
645                if runtime_opts.enable_verifier {
646                    config.enable_verifier();
647                }
648
649                if runtime_opts.enable_pass_params_opt {
650                    config.enable_pass_params_opt();
651                }
652
653                if let Some(num_threads) = runtime_opts.llvm_num_threads {
654                    config.num_threads(num_threads);
655                }
656
657                if let Some(p) = &runtime_opts.profiler {
658                    match p {
659                        Profiler::Perfmap => config.enable_perfmap(),
660                    }
661                }
662
663                let engine = wasmer_compiler::EngineBuilder::new(config)
664                    .set_features(Some(features.clone()))
665                    .set_target(Some(target.clone()))
666                    .engine()
667                    .into();
668                Ok(engine)
669            }
670            #[cfg(feature = "v8")]
671            Self::V8 => Ok(wasmer::v8::V8::new().into()),
672            #[cfg(feature = "wamr")]
673            Self::Wamr => Ok(wasmer::wamr::Wamr::new().into()),
674            #[cfg(feature = "wasmi")]
675            Self::Wasmi => Ok(wasmer::wasmi::Wasmi::new().into()),
676            Self::Headless => bail!("Headless is not a valid runtime to instantiate directly"),
677            #[allow(unreachable_patterns)]
678            _ => bail!("Unsupported backend type"),
679        }
680    }
681
682    /// Check if this backend supports all the required WebAssembly features
683    #[allow(unreachable_code)]
684    pub fn supports_features(&self, required_features: &Features, target: &Target) -> bool {
685        // Map BackendType to the corresponding wasmer::BackendKind
686        let backend_kind = match self {
687            #[cfg(feature = "singlepass")]
688            Self::Singlepass => wasmer::BackendKind::Singlepass,
689            #[cfg(feature = "cranelift")]
690            Self::Cranelift => wasmer::BackendKind::Cranelift,
691            #[cfg(feature = "llvm")]
692            Self::LLVM => wasmer::BackendKind::LLVM,
693            #[cfg(feature = "v8")]
694            Self::V8 => wasmer::BackendKind::V8,
695            #[cfg(feature = "wamr")]
696            Self::Wamr => wasmer::BackendKind::Wamr,
697            #[cfg(feature = "wasmi")]
698            Self::Wasmi => wasmer::BackendKind::Wasmi,
699            Self::Headless => return false, // Headless can't compile
700            #[allow(unreachable_patterns)]
701            _ => return false,
702        };
703
704        // Get the supported features from the backend
705        let supported = wasmer::Engine::supported_features_for_backend(&backend_kind, target);
706
707        // Check if the backend supports all required features
708        if !supported.contains_features(required_features) {
709            return false;
710        }
711
712        true
713    }
714}
715
716impl From<&BackendType> for wasmer::BackendKind {
717    fn from(backend_type: &BackendType) -> Self {
718        match backend_type {
719            #[cfg(feature = "singlepass")]
720            BackendType::Singlepass => wasmer::BackendKind::Singlepass,
721            #[cfg(feature = "cranelift")]
722            BackendType::Cranelift => wasmer::BackendKind::Cranelift,
723            #[cfg(feature = "llvm")]
724            BackendType::LLVM => wasmer::BackendKind::LLVM,
725            #[cfg(feature = "v8")]
726            BackendType::V8 => wasmer::BackendKind::V8,
727            #[cfg(feature = "wamr")]
728            BackendType::Wamr => wasmer::BackendKind::Wamr,
729            #[cfg(feature = "wasmi")]
730            BackendType::Wasmi => wasmer::BackendKind::Wasmi,
731            _ => {
732                #[cfg(feature = "sys")]
733                {
734                    wasmer::BackendKind::Headless
735                }
736                #[cfg(not(feature = "sys"))]
737                {
738                    unreachable!("No backend enabled!")
739                }
740            }
741        }
742    }
743}
744
745impl std::fmt::Display for BackendType {
746    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
747        write!(
748            f,
749            "{}",
750            match self {
751                Self::Singlepass => "singlepass",
752                Self::Cranelift => "cranelift",
753                Self::LLVM => "llvm",
754                Self::V8 => "v8",
755                Self::Wamr => "wamr",
756                Self::Wasmi => "wasmi",
757                Self::Headless => "headless",
758            }
759        )
760    }
761}