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 sse3 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"] 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 150 std:"sse4.1" => clif:"has_sse41", 151 std:"sse4.2" => clif:"has_sse42", 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