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