//! Generate Cranelift compiler settings. use crate::generators::ModuleConfig; use arbitrary::{Arbitrary, Unstructured}; use std::collections::HashMap; /// Choose between matching the host architecture or a cross-compilation target. #[derive(Clone, Debug, Eq, Hash, PartialEq)] pub enum CodegenSettings { /// Use the host's feature set. Native, /// Generate a modified flag set for the current host. #[allow(dead_code)] Target { /// The target triple of the host. target: String, /// A list of CPU features to enable, e.g., `("has_avx", "false")`. flags: Vec<(String, String)>, }, } impl CodegenSettings { /// Configure Wasmtime with these codegen settings. pub fn configure(&self, config: &mut wasmtime::Config) { match self { CodegenSettings::Native => {} CodegenSettings::Target { target, flags } => { config.target(target).unwrap(); for (key, value) in flags { unsafe { config.cranelift_flag_set(key, value); } } } } } /// Features such as sse4.2 are unconditionally enabled on the x86_64 target /// because they are hard required for SIMD, but when SIMD is disabled, for /// example, we support disabling these features. /// /// This method will take the wasm feature selection chosen, through /// `module_config`, and possibly try to disable some more features by /// reading more of the input. pub fn maybe_disable_more_features( &mut self, module_config: &ModuleConfig, u: &mut Unstructured<'_>, ) -> arbitrary::Result<()> { let flags = match self { CodegenSettings::Target { flags, .. } => flags, _ => return Ok(()), }; if !module_config.config.simd_enabled { // Note that regardless of architecture these booleans are generated // to have test case failures unrelated to codegen setting input // that fail on one architecture to fail on other architectures as // well. let new_flags = ["has_sse3", "has_ssse3", "has_sse41", "has_sse42"] .into_iter() .map(|name| Ok((name, u.arbitrary()?))) .collect::>>()?; for (name, val) in flags { if let Some(new_value) = new_flags.get(name.as_str()) { *val = new_value.to_string(); } } } Ok(()) } } impl<'a> Arbitrary<'a> for CodegenSettings { #[allow(unused_macros, unused_variables)] fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { // Helper macro to enable clif features based on what the native host // supports. If the input says to enable a feature and the host doesn't // support it then that test case is rejected with a warning. // // Note that this specifically consumes bytes from the fuzz input for // features for all targets, discarding anything which isn't applicable // to the current target. The theory behind this is that most fuzz bugs // won't be related to this feature selection so by consistently // consuming input irrespective of the current platform reproducing fuzz // bugs should be easier between different architectures. macro_rules! target_features { ( $( $arch:tt => { test:$test:ident, $(std: $std:tt => clif: $clif:tt $(ratio: $a:tt in $b:tt)?,)* }, )* ) => ({ let mut flags = Vec::new(); $( // for each `$arch` $( // for each `$std`/`$clif` pair // Use the input to generate whether `$clif` will be // enabled. By default this is a 1 in 2 chance but each // feature supports a custom ratio as well which shadows // the (low, hi) let (low, hi) = (1, 2); $(let (low, hi) = ($a, $b);)? let enable = u.ratio(low, hi)?; // If we're actually on the relevant platform and the // feature is enabled be sure to check that this host // supports it. If the host doesn't support it then // print a warning and return an error because this fuzz // input must be discarded. #[cfg(target_arch = $arch)] if enable && !std::arch::$test!($std) { log::warn!("want to enable clif `{}` but host doesn't support it", $clif); return Err(arbitrary::Error::EmptyChoose) } // And finally actually push the feature into the set of // flags to enable, but only if we're on the right // architecture. if cfg!(target_arch = $arch) { flags.push(( $clif.to_string(), enable.to_string(), )); } )* )* flags }) } if u.ratio(1, 10)? { let flags = target_features! { "x86_64" => { test: is_x86_feature_detected, // These features are considered to be baseline required by // Wasmtime. Currently some SIMD code generation will // fail if these features are disabled, so unconditionally // enable them as we're not interested in fuzzing without // them. // // Note that these may still be disabled above in // `maybe_disable_more_features`. std:"sse3" => clif:"has_sse3" ratio: 1 in 1, std:"ssse3" => clif:"has_ssse3" ratio: 1 in 1, std:"sse4.1" => clif:"has_sse41" ratio: 1 in 1, std:"sse4.2" => clif:"has_sse42" ratio: 1 in 1, std:"popcnt" => clif:"has_popcnt", std:"avx" => clif:"has_avx", std:"avx2" => clif:"has_avx2", std:"fma" => clif:"has_fma", std:"bmi1" => clif:"has_bmi1", std:"bmi2" => clif:"has_bmi2", std:"lzcnt" => clif:"has_lzcnt", // not a lot of of cpus support avx512 so these are weighted // to get enabled much less frequently. std:"avx512bitalg" => clif:"has_avx512bitalg" ratio:1 in 1000, std:"avx512dq" => clif:"has_avx512dq" ratio: 1 in 1000, std:"avx512f" => clif:"has_avx512f" ratio: 1 in 1000, std:"avx512vl" => clif:"has_avx512vl" ratio: 1 in 1000, std:"avx512vbmi" => clif:"has_avx512vbmi" ratio: 1 in 1000, }, "aarch64" => { test: is_aarch64_feature_detected, std: "bti" => clif: "use_bti", std: "lse" => clif: "has_lse", // even though the natural correspondence seems to be // between "paca" and "has_pauth", the latter has no effect // in isolation, so we actually use the setting that affects // code generation std: "paca" => clif: "sign_return_address", // "paca" and "pacg" check for the same underlying // architectural feature, so we use the latter to cover more // code generation settings, of which we have chosen the one // with the most significant effect std: "pacg" => clif: "sign_return_address_all" ratio: 1 in 2, }, }; return Ok(CodegenSettings::Target { target: target_lexicon::Triple::host().to_string(), flags, }); } Ok(CodegenSettings::Native) } }