1 //! Generate Cranelift compiler settings.
2 
3 use arbitrary::{Arbitrary, Unstructured};
4 
5 /// Choose between matching the host architecture or a cross-compilation target.
6 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
7 pub enum CodegenSettings {
8     /// Use the host's feature set.
9     Native,
10     /// Generate a modified flag set for the current host.
11     Target {
12         /// The target triple of the host.
13         target: String,
14         /// A list of CPU features to enable, e.g., `("has_avx", "false")`.
15         flags: Vec<(String, String)>,
16     },
17 }
18 
19 impl CodegenSettings {
20     /// Configure Wasmtime with these codegen settings.
configure(&self, config: &mut wasmtime_cli_flags::CommonOptions)21     pub fn configure(&self, config: &mut wasmtime_cli_flags::CommonOptions) {
22         match self {
23             CodegenSettings::Native => {}
24             CodegenSettings::Target { target, flags } => {
25                 config.target = Some(target.to_string());
26                 for (key, value) in flags {
27                     config
28                         .codegen
29                         .cranelift
30                         .push((key.clone(), Some(value.clone())));
31                 }
32             }
33         }
34     }
35 
36     /// Returns the flags used for codegen.
flags(&self) -> &[(String, String)]37     pub(crate) fn flags(&self) -> &[(String, String)] {
38         if let Self::Target { flags, .. } = self {
39             flags
40         } else {
41             &[]
42         }
43     }
44 }
45 
46 impl<'a> Arbitrary<'a> for CodegenSettings {
47     #[expect(unused_variables, reason = "macro-generated code")]
arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self>48     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
49         // Helper macro to enable clif features based on what the native host
50         // supports. If the input says to enable a feature and the host doesn't
51         // support it then that test case is rejected with a warning.
52         //
53         // Note that this specifically consumes bytes from the fuzz input for
54         // features for all targets, discarding anything which isn't applicable
55         // to the current target. The theory behind this is that most fuzz bugs
56         // won't be related to this feature selection so by consistently
57         // consuming input irrespective of the current platform reproducing fuzz
58         // bugs should be easier between different architectures.
59         macro_rules! target_features {
60             (
61                 $(
62                     $arch:tt => {
63                         test:$test:ident,
64                         $(std: $std:tt => clif: $clif:tt $(ratio: $a:tt in $b:tt)?,)*
65                     },
66                 )*
67             ) => ({
68                 let mut flags = Vec::new();
69                 $( // for each `$arch`
70                     $( // for each `$std`/`$clif` pair
71                         // Use the input to generate whether `$clif` will be
72                         // enabled. By default this is a 1 in 2 chance but each
73                         // feature supports a custom ratio as well which shadows
74                         // the (low, hi)
75                         let (low, hi) = (1, 2);
76                         $(let (low, hi) = ($a, $b);)?
77                         let enable = u.ratio(low, hi)?;
78 
79                         // If we're actually on the relevant platform and the
80                         // feature is enabled be sure to check that this host
81                         // supports it. If the host doesn't support it then
82                         // print a warning and return an error because this fuzz
83                         // input must be discarded.
84                         #[cfg(target_arch = $arch)]
85                         if enable && !std::arch::$test!($std) {
86                             log::warn!("want to enable clif `{}` but host doesn't support it",
87                                 $clif);
88                             return Err(arbitrary::Error::EmptyChoose)
89                         }
90 
91                         // And finally actually push the feature into the set of
92                         // flags to enable, but only if we're on the right
93                         // architecture.
94                         if cfg!(target_arch = $arch) {
95                             flags.push((
96                                 $clif.to_string(),
97                                 enable.to_string(),
98                             ));
99                         }
100                     )*
101                 )*
102                 flags
103             })
104         }
105         if u.ratio(1, 10)? {
106             let flags = target_features! {
107                 "x86_64" => {
108                     test: is_x86_feature_detected,
109 
110                     std:"cmpxchg16b" => clif:"has_cmpxchg16b",
111                     std:"sse3" => clif:"has_sse3",
112                     std:"ssse3" => clif:"has_ssse3",
113                     std:"sse4.1" => clif:"has_sse41",
114                     std:"sse4.2" => clif:"has_sse42",
115                     std:"popcnt" => clif:"has_popcnt",
116                     std:"avx" => clif:"has_avx",
117                     std:"avx2" => clif:"has_avx2",
118                     std:"fma" => clif:"has_fma",
119                     std:"bmi1" => clif:"has_bmi1",
120                     std:"bmi2" => clif:"has_bmi2",
121                     std:"lzcnt" => clif:"has_lzcnt",
122 
123                     // not a lot of cpus support avx512 so these are weighted
124                     // to get enabled much less frequently.
125                     std:"avx512bitalg" => clif:"has_avx512bitalg" ratio:1 in 1000,
126                     std:"avx512dq" => clif:"has_avx512dq" ratio: 1 in 1000,
127                     std:"avx512f" => clif:"has_avx512f" ratio: 1 in 1000,
128                     std:"avx512vl" => clif:"has_avx512vl" ratio: 1 in 1000,
129                     std:"avx512vbmi" => clif:"has_avx512vbmi" ratio: 1 in 1000,
130                 },
131                 "aarch64" => {
132                     test: is_aarch64_feature_detected,
133 
134                     std: "bti" => clif: "use_bti",
135                     std: "lse" => clif: "has_lse",
136                     std: "fp16" => clif: "has_fp16",
137                     // even though the natural correspondence seems to be
138                     // between "paca" and "has_pauth", the latter has no effect
139                     // in isolation, so we actually use the setting that affects
140                     // code generation
141                     std: "paca" => clif: "sign_return_address",
142                     // "paca" and "pacg" check for the same underlying
143                     // architectural feature, so we use the latter to cover more
144                     // code generation settings, of which we have chosen the one
145                     // with the most significant effect
146                     std: "pacg" => clif: "sign_return_address_all" ratio: 1 in 2,
147                 },
148             };
149             return Ok(CodegenSettings::Target {
150                 target: target_lexicon::Triple::host().to_string(),
151                 flags,
152             });
153         }
154         Ok(CodegenSettings::Native)
155     }
156 }
157