1 //! Generate Cranelift compiler settings.
2 
3 use crate::generators::ModuleConfig;
4 use arbitrary::{Arbitrary, Unstructured};
5 use std::collections::HashMap;
6 
7 /// Choose between matching the host architecture or a cross-compilation target.
8 #[derive(Clone, Debug, Eq, Hash, PartialEq)]
9 pub enum CodegenSettings {
10     /// Use the host's feature set.
11     Native,
12     /// Generate a modified flag set for the current host.
13     #[allow(dead_code)]
14     Target {
15         /// The target triple of the host.
16         target: String,
17         /// A list of CPU features to enable, e.g., `("has_avx", "false")`.
18         flags: Vec<(String, String)>,
19     },
20 }
21 
22 impl CodegenSettings {
23     /// Configure Wasmtime with these codegen settings.
24     pub fn configure(&self, config: &mut wasmtime::Config) {
25         match self {
26             CodegenSettings::Native => {}
27             CodegenSettings::Target { target, flags } => {
28                 config.target(target).unwrap();
29                 for (key, value) in flags {
30                     unsafe {
31                         config.cranelift_flag_set(key, value);
32                     }
33                 }
34             }
35         }
36     }
37 
38     /// Features such as sse4.2 are unconditionally enabled on the x86_64 target
39     /// because they are hard required for SIMD, but when SIMD is disabled, for
40     /// example, we support disabling these features.
41     ///
42     /// This method will take the wasm feature selection chosen, through
43     /// `module_config`, and possibly try to disable some more features by
44     /// reading more of the input.
45     pub fn maybe_disable_more_features(
46         &mut self,
47         module_config: &ModuleConfig,
48         u: &mut Unstructured<'_>,
49     ) -> arbitrary::Result<()> {
50         let flags = match self {
51             CodegenSettings::Target { flags, .. } => flags,
52             _ => return Ok(()),
53         };
54 
55         if !module_config.config.simd_enabled {
56             // Note that regardless of architecture these booleans are generated
57             // to have test case failures unrelated to codegen setting input
58             // that fail on one architecture to fail on other architectures as
59             // well.
60             let new_flags = ["has_sse3", "has_ssse3", "has_sse41", "has_sse42"]
61                 .into_iter()
62                 .map(|name| Ok((name, u.arbitrary()?)))
63                 .collect::<arbitrary::Result<HashMap<_, bool>>>()?;
64 
65             for (name, val) in flags {
66                 if let Some(new_value) = new_flags.get(name.as_str()) {
67                     *val = new_value.to_string();
68                 }
69             }
70         }
71         Ok(())
72     }
73 }
74 
75 impl<'a> Arbitrary<'a> for CodegenSettings {
76     #[allow(unused_macros, unused_variables)]
77     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
78         // Helper macro to enable clif features based on what the native host
79         // supports. If the input says to enable a feature and the host doesn't
80         // support it then that test case is rejected with a warning.
81         //
82         // Note that this specifically consumes bytes from the fuzz input for
83         // features for all targets, discarding anything which isn't applicable
84         // to the current target. The theory behind this is that most fuzz bugs
85         // won't be related to this feature selection so by consistently
86         // consuming input irrespective of the current platform reproducing fuzz
87         // bugs should be easier between different architectures.
88         macro_rules! target_features {
89             (
90                 $(
91                     $arch:tt => {
92                         test:$test:ident,
93                         $(std: $std:tt => clif: $clif:tt $(ratio: $a:tt in $b:tt)?,)*
94                     },
95                 )*
96             ) => ({
97                 let mut flags = Vec::new();
98                 $( // for each `$arch`
99                     $( // for each `$std`/`$clif` pair
100                         // Use the input to generate whether `$clif` will be
101                         // enabled. By default this is a 1 in 2 chance but each
102                         // feature supports a custom ratio as well which shadows
103                         // the (low, hi)
104                         let (low, hi) = (1, 2);
105                         $(let (low, hi) = ($a, $b);)?
106                         let enable = u.ratio(low, hi)?;
107 
108                         // If we're actually on the relevant platform and the
109                         // feature is enabled be sure to check that this host
110                         // supports it. If the host doesn't support it then
111                         // print a warning and return an error because this fuzz
112                         // input must be discarded.
113                         #[cfg(target_arch = $arch)]
114                         if enable && !std::arch::$test!($std) {
115                             log::warn!("want to enable clif `{}` but host doesn't support it",
116                                 $clif);
117                             return Err(arbitrary::Error::EmptyChoose)
118                         }
119 
120                         // And finally actually push the feature into the set of
121                         // flags to enable, but only if we're on the right
122                         // architecture.
123                         if cfg!(target_arch = $arch) {
124                             flags.push((
125                                 $clif.to_string(),
126                                 enable.to_string(),
127                             ));
128                         }
129                     )*
130                 )*
131                 flags
132             })
133         }
134         if u.ratio(1, 10)? {
135             let flags = target_features! {
136                 "x86_64" => {
137                     test: is_x86_feature_detected,
138 
139                     // These features are considered to be baseline required by
140                     // Wasmtime. Currently some SIMD code generation will
141                     // fail if these features are disabled, so unconditionally
142                     // enable them as we're not interested in fuzzing without
143                     // them.
144                     //
145                     // Note that these may still be disabled above in
146                     // `maybe_disable_more_features`.
147                     std:"sse3" => clif:"has_sse3" ratio: 1 in 1,
148                     std:"ssse3" => clif:"has_ssse3" ratio: 1 in 1,
149                     std:"sse4.1" => clif:"has_sse41" ratio: 1 in 1,
150                     std:"sse4.2" => clif:"has_sse42" ratio: 1 in 1,
151 
152                     std:"popcnt" => clif:"has_popcnt",
153                     std:"avx" => clif:"has_avx",
154                     std:"avx2" => clif:"has_avx2",
155                     std:"fma" => clif:"has_fma",
156                     std:"bmi1" => clif:"has_bmi1",
157                     std:"bmi2" => clif:"has_bmi2",
158                     std:"lzcnt" => clif:"has_lzcnt",
159 
160                     // not a lot of of cpus support avx512 so these are weighted
161                     // to get enabled much less frequently.
162                     std:"avx512bitalg" => clif:"has_avx512bitalg" ratio:1 in 1000,
163                     std:"avx512dq" => clif:"has_avx512dq" ratio: 1 in 1000,
164                     std:"avx512f" => clif:"has_avx512f" ratio: 1 in 1000,
165                     std:"avx512vl" => clif:"has_avx512vl" ratio: 1 in 1000,
166                     std:"avx512vbmi" => clif:"has_avx512vbmi" ratio: 1 in 1000,
167                 },
168                 "aarch64" => {
169                     test: is_aarch64_feature_detected,
170 
171                     std: "bti" => clif: "use_bti",
172                     std: "lse" => clif: "has_lse",
173                     // even though the natural correspondence seems to be
174                     // between "paca" and "has_pauth", the latter has no effect
175                     // in isolation, so we actually use the setting that affects
176                     // code generation
177                     std: "paca" => clif: "sign_return_address",
178                     // "paca" and "pacg" check for the same underlying
179                     // architectural feature, so we use the latter to cover more
180                     // code generation settings, of which we have chosen the one
181                     // with the most significant effect
182                     std: "pacg" => clif: "sign_return_address_all" ratio: 1 in 2,
183                 },
184             };
185             return Ok(CodegenSettings::Target {
186                 target: target_lexicon::Triple::host().to_string(),
187                 flags,
188             });
189         }
190         Ok(CodegenSettings::Native)
191     }
192 }
193