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     #[allow(dead_code)]
12     Target {
13         /// The target triple of the host.
14         target: String,
15         /// A list of CPU features to enable, e.g., `("has_avx", "false")`.
16         flags: Vec<(String, String)>,
17     },
18 }
19 
20 impl CodegenSettings {
21     /// Configure Wasmtime with these codegen settings.
22     pub fn configure(&self, config: &mut wasmtime::Config) {
23         match self {
24             CodegenSettings::Native => {}
25             CodegenSettings::Target { target, flags } => {
26                 config.target(target).unwrap();
27                 for (key, value) in flags {
28                     unsafe {
29                         config.cranelift_flag_set(key, value);
30                     }
31                 }
32             }
33         }
34     }
35 }
36 
37 impl<'a> Arbitrary<'a> for CodegenSettings {
38     #[allow(unused_macros, unused_variables)]
39     fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
40         // Helper macro to enable clif features based on what the native host
41         // supports. If the input says to enable a feature and the host doesn't
42         // support it then that test case is rejected with a warning.
43         //
44         // Note that this specifically consumes bytes from the fuzz input for
45         // features for all targets, discarding anything which isn't applicable
46         // to the current target. The theory behind this is that most fuzz bugs
47         // won't be related to this feature selection so by consistently
48         // consuming input irrespective of the current platform reproducing fuzz
49         // bugs should be easier between different architectures.
50         macro_rules! target_features {
51             (
52                 $(
53                     $arch:tt => {
54                         test:$test:ident,
55                         $(std: $std:tt => clif: $clif:tt $(ratio: $a:tt in $b:tt)?,)*
56                     },
57                 )*
58             ) => ({
59                 let mut flags = Vec::new();
60                 $( // for each `$arch`
61                     $( // for each `$std`/`$clif` pair
62                         // Use the input to generate whether `$clif` will be
63                         // enabled. By default this is a 1 in 2 chance but each
64                         // feature supports a custom ratio as well which shadows
65                         // the (low, hi)
66                         let (low, hi) = (1, 2);
67                         $(let (low, hi) = ($a, $b);)?
68                         let enable = u.ratio(low, hi)?;
69 
70                         // If we're actually on the relevant platform and the
71                         // feature is enabled be sure to check that this host
72                         // supports it. If the host doesn't support it then
73                         // print a warning and return an error because this fuzz
74                         // input must be discarded.
75                         #[cfg(target_arch = $arch)]
76                         if enable && !std::arch::$test!($std) {
77                             log::warn!("want to enable clif `{}` but host doesn't support it",
78                                 $clif);
79                             return Err(arbitrary::Error::EmptyChoose)
80                         }
81 
82                         // And finally actually push the feature into the set of
83                         // flags to enable, but only if we're on the right
84                         // architecture.
85                         if cfg!(target_arch = $arch) {
86                             flags.push((
87                                 $clif.to_string(),
88                                 enable.to_string(),
89                             ));
90                         }
91                     )*
92                 )*
93                 flags
94             })
95         }
96         if u.ratio(1, 10)? {
97             let flags = target_features! {
98                 "x86_64" => {
99                     test: is_x86_feature_detected,
100 
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