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