| #![allow(unused, non_camel_case_types)] |
| |
| use std::collections::{BTreeMap, HashMap, HashSet}; |
| use std::fs::File; |
| use std::io; |
| use std::io::{BufWriter, Write}; |
| |
| use serde::Deserialize; |
| |
| const PRINT_INSTRUCTION_VIOLATIONS: bool = false; |
| const GENERATE_MISSING_X86_MD: bool = false; |
| const SS: u8 = (8 * size_of::<usize>()) as u8; |
| |
| struct Function { |
| name: &'static str, |
| arguments: &'static [&'static Type], |
| ret: Option<&'static Type>, |
| target_feature: Option<&'static str>, |
| instrs: &'static [&'static str], |
| file: &'static str, |
| required_const: &'static [usize], |
| has_test: bool, |
| doc: &'static str, |
| } |
| |
| static BF16: Type = Type::BFloat16; |
| static F16: Type = Type::PrimFloat(16); |
| static F32: Type = Type::PrimFloat(32); |
| static F64: Type = Type::PrimFloat(64); |
| static I8: Type = Type::PrimSigned(8); |
| static I16: Type = Type::PrimSigned(16); |
| static I32: Type = Type::PrimSigned(32); |
| static I64: Type = Type::PrimSigned(64); |
| static U8: Type = Type::PrimUnsigned(8); |
| static U16: Type = Type::PrimUnsigned(16); |
| static U32: Type = Type::PrimUnsigned(32); |
| static U64: Type = Type::PrimUnsigned(64); |
| static U128: Type = Type::PrimUnsigned(128); |
| static USIZE: Type = Type::PrimUnsigned(SS); |
| static ORDERING: Type = Type::Ordering; |
| |
| static M128: Type = Type::M128; |
| static M128BH: Type = Type::M128BH; |
| static M128I: Type = Type::M128I; |
| static M128D: Type = Type::M128D; |
| static M128H: Type = Type::M128H; |
| static M256: Type = Type::M256; |
| static M256BH: Type = Type::M256BH; |
| static M256I: Type = Type::M256I; |
| static M256D: Type = Type::M256D; |
| static M256H: Type = Type::M256H; |
| static M512: Type = Type::M512; |
| static M512BH: Type = Type::M512BH; |
| static M512I: Type = Type::M512I; |
| static M512D: Type = Type::M512D; |
| static M512H: Type = Type::M512H; |
| static MMASK8: Type = Type::MMASK8; |
| static MMASK16: Type = Type::MMASK16; |
| static MMASK32: Type = Type::MMASK32; |
| static MMASK64: Type = Type::MMASK64; |
| static MM_CMPINT_ENUM: Type = Type::MM_CMPINT_ENUM; |
| static MM_MANTISSA_NORM_ENUM: Type = Type::MM_MANTISSA_NORM_ENUM; |
| static MM_MANTISSA_SIGN_ENUM: Type = Type::MM_MANTISSA_SIGN_ENUM; |
| static MM_PERM_ENUM: Type = Type::MM_PERM_ENUM; |
| |
| static TUPLE: Type = Type::Tuple; |
| static CPUID: Type = Type::CpuidResult; |
| static NEVER: Type = Type::Never; |
| |
| #[derive(Debug, PartialEq, Copy, Clone)] |
| enum Type { |
| PrimFloat(u8), |
| PrimSigned(u8), |
| PrimUnsigned(u8), |
| BFloat16, |
| MutPtr(&'static Type), |
| ConstPtr(&'static Type), |
| M128, |
| M128BH, |
| M128D, |
| M128H, |
| M128I, |
| M256, |
| M256BH, |
| M256D, |
| M256H, |
| M256I, |
| M512, |
| M512BH, |
| M512D, |
| M512H, |
| M512I, |
| MMASK8, |
| MMASK16, |
| MMASK32, |
| MMASK64, |
| MM_CMPINT_ENUM, |
| MM_MANTISSA_NORM_ENUM, |
| MM_MANTISSA_SIGN_ENUM, |
| MM_PERM_ENUM, |
| Tuple, |
| CpuidResult, |
| Never, |
| Ordering, |
| } |
| |
| stdarch_verify::x86_functions!(static FUNCTIONS); |
| |
| #[derive(Deserialize)] |
| struct Data { |
| #[serde(rename = "intrinsic", default)] |
| intrinsics: Vec<Intrinsic>, |
| } |
| |
| #[derive(Deserialize)] |
| struct Intrinsic { |
| #[serde(rename = "return")] |
| return_: Return, |
| #[serde(rename = "@name")] |
| name: String, |
| #[serde(rename = "@tech")] |
| tech: String, |
| #[serde(rename = "CPUID", default)] |
| cpuid: Vec<String>, |
| #[serde(rename = "parameter", default)] |
| parameters: Vec<Parameter>, |
| #[serde(rename = "@sequence", default)] |
| generates_sequence: bool, |
| #[serde(default)] |
| instruction: Vec<Instruction>, |
| } |
| |
| #[derive(Deserialize)] |
| struct Parameter { |
| #[serde(rename = "@type")] |
| type_: String, |
| #[serde(rename = "@etype", default)] |
| etype: String, |
| } |
| |
| #[derive(Deserialize)] |
| struct Return { |
| #[serde(rename = "@type", default)] |
| type_: String, |
| } |
| |
| #[derive(Deserialize, Debug)] |
| struct Instruction { |
| #[serde(rename = "@name")] |
| name: String, |
| } |
| |
| macro_rules! bail { |
| ($($t:tt)*) => { return Err(format!($($t)*)) } |
| } |
| |
| #[test] |
| fn verify_all_signatures() { |
| // This XML document was downloaded from Intel's site. To update this you |
| // can visit intel's intrinsics guide online documentation: |
| // |
| // https://software.intel.com/sites/landingpage/IntrinsicsGuide/# |
| // |
| // Open up the network console and you'll see an xml file was downloaded |
| // (currently called data-3.6.9.xml). That's the file we downloaded |
| // here. |
| let xml = include_bytes!("../x86-intel.xml"); |
| |
| let xml = &xml[..]; |
| let data: Data = quick_xml::de::from_reader(xml).expect("failed to deserialize xml"); |
| let mut map = HashMap::new(); |
| for intrinsic in &data.intrinsics { |
| map.entry(&intrinsic.name[..]) |
| .or_insert_with(Vec::new) |
| .push(intrinsic); |
| } |
| |
| let mut all_valid = true; |
| 'outer: for rust in FUNCTIONS { |
| if !rust.has_test { |
| // FIXME: this list should be almost empty |
| let skip = [ |
| // MXCSR - deprecated, immediate UB |
| "_mm_getcsr", |
| "_mm_setcsr", |
| "_MM_GET_EXCEPTION_MASK", |
| "_MM_GET_EXCEPTION_STATE", |
| "_MM_GET_FLUSH_ZERO_MODE", |
| "_MM_GET_ROUNDING_MODE", |
| "_MM_SET_EXCEPTION_MASK", |
| "_MM_SET_EXCEPTION_STATE", |
| "_MM_SET_FLUSH_ZERO_MODE", |
| "_MM_SET_ROUNDING_MODE", |
| // CPUID |
| "__cpuid_count", |
| "__cpuid", |
| "__get_cpuid_max", |
| // Privileged, see https://github.com/rust-lang/stdarch/issues/209 |
| "_xsetbv", |
| "_xsaves", |
| "_xrstors", |
| "_xsaves64", |
| "_xrstors64", |
| "_mm_loadiwkey", |
| // RDRAND |
| "_rdrand16_step", |
| "_rdrand32_step", |
| "_rdrand64_step", |
| "_rdseed16_step", |
| "_rdseed32_step", |
| "_rdseed64_step", |
| // Prefetch |
| "_mm_prefetch", |
| // CMPXCHG |
| "cmpxchg16b", |
| // Undefined |
| "_mm_undefined_ps", |
| "_mm_undefined_pd", |
| "_mm_undefined_si128", |
| "_mm_undefined_ph", |
| "_mm256_undefined_ps", |
| "_mm256_undefined_pd", |
| "_mm256_undefined_si256", |
| "_mm256_undefined_ph", |
| "_mm512_undefined_ps", |
| "_mm512_undefined_pd", |
| "_mm512_undefined_epi32", |
| "_mm512_undefined", |
| "_mm512_undefined_ph", |
| // Has doc-tests instead |
| "_mm256_shuffle_epi32", |
| "_mm256_unpackhi_epi8", |
| "_mm256_unpacklo_epi8", |
| "_mm256_unpackhi_epi16", |
| "_mm256_unpacklo_epi16", |
| "_mm256_unpackhi_epi32", |
| "_mm256_unpacklo_epi32", |
| "_mm256_unpackhi_epi64", |
| "_mm256_unpacklo_epi64", |
| // Has tests with some other intrinsic |
| "__writeeflags", |
| "_xrstor", |
| "_xrstor64", |
| "_fxrstor", |
| "_fxrstor64", |
| "_xend", |
| "_xabort_code", |
| // Aliases |
| "_mm_comige_ss", |
| "_mm_cvt_ss2si", |
| "_mm_cvtt_ss2si", |
| "_mm_cvt_si2ss", |
| "_mm_set_ps1", |
| "_mm_load_ps1", |
| "_mm_store_ps1", |
| "_mm_bslli_si128", |
| "_mm_bsrli_si128", |
| "_bextr2_u32", |
| "_mm_tzcnt_32", |
| "_mm256_bslli_epi128", |
| "_mm256_bsrli_epi128", |
| "_mm_cvtsi64x_si128", |
| "_mm_cvtsi128_si64x", |
| "_mm_cvtsi64x_sd", |
| "_bextr2_u64", |
| "_mm_tzcnt_64", |
| ]; |
| if !skip.contains(&rust.name) { |
| println!( |
| "missing run-time test named `test_{}` for `{}`", |
| { |
| let mut id = rust.name; |
| while id.starts_with('_') { |
| id = &id[1..]; |
| } |
| id |
| }, |
| rust.name |
| ); |
| all_valid = false; |
| } |
| } |
| |
| match rust.name { |
| // These aren't defined by Intel but they're defined by what appears |
| // to be all other compilers. For more information see |
| // rust-lang/stdarch#307, and otherwise these signatures |
| // have all been manually verified. |
| "__readeflags" | |
| "__writeeflags" | |
| "__cpuid_count" | |
| "__cpuid" | |
| "__get_cpuid_max" | |
| "_MM_SHUFFLE" | |
| "_xabort_code" | |
| // Not listed with intel, but manually verified |
| "cmpxchg16b" |
| => continue, |
| _ => {} |
| } |
| |
| // these are all AMD-specific intrinsics |
| if let Some(feature) = rust.target_feature { |
| if feature.contains("sse4a") || feature.contains("tbm") { |
| continue; |
| } |
| } |
| |
| let intel = match map.remove(rust.name) { |
| Some(i) => i, |
| None => panic!("missing intel definition for {}", rust.name), |
| }; |
| |
| let mut errors = Vec::new(); |
| for intel in intel { |
| match matches(rust, intel) { |
| Ok(()) => continue 'outer, |
| Err(e) => errors.push(e), |
| } |
| } |
| println!("failed to verify `{}`", rust.name); |
| for error in errors { |
| println!(" * {error}"); |
| } |
| all_valid = false; |
| } |
| assert!(all_valid); |
| |
| if GENERATE_MISSING_X86_MD { |
| print_missing( |
| &map, |
| BufWriter::new(File::create("../core_arch/missing-x86.md").unwrap()), |
| ) |
| .unwrap(); |
| } |
| } |
| |
| fn print_missing(map: &HashMap<&str, Vec<&Intrinsic>>, mut f: impl Write) -> io::Result<()> { |
| let mut missing = BTreeMap::new(); // BTreeMap to keep the cpuids ordered |
| |
| // we cannot use SVML and MMX, and MPX is not in LLVM, and intrinsics without any cpuid requirement |
| // are accessible from safe rust |
| for intrinsic in map.values().flatten().filter(|intrinsic| { |
| intrinsic.tech != "SVML" |
| && intrinsic.tech != "MMX" |
| && !intrinsic.cpuid.is_empty() |
| && !intrinsic.cpuid.contains(&"MPX".to_string()) |
| && intrinsic.return_.type_ != "__m64" |
| && !intrinsic |
| .parameters |
| .iter() |
| .any(|param| param.type_.contains("__m64")) |
| }) { |
| missing |
| .entry(&intrinsic.cpuid) |
| .or_insert_with(Vec::new) |
| .push(intrinsic); |
| } |
| |
| for (k, v) in &mut missing { |
| v.sort_by_key(|intrinsic| &intrinsic.name); // sort to make the order of everything same |
| writeln!(f, "\n<details><summary>{k:?}</summary><p>\n")?; |
| for intel in v { |
| let url = format!( |
| "https://software.intel.com/sites/landingpage\ |
| /IntrinsicsGuide/#text={}", |
| intel.name |
| ); |
| writeln!(f, " * [ ] [`{}`]({url})", intel.name)?; |
| } |
| writeln!(f, "</p></details>\n")?; |
| } |
| |
| f.flush() |
| } |
| |
| fn check_target_features(rust: &Function, intel: &Intrinsic) -> Result<(), String> { |
| // Verify that all `#[target_feature]` annotations are correct, |
| // ensuring that we've actually enabled the right instruction |
| // set for this intrinsic. |
| match rust.name { |
| "_bswap" | "_bswap64" => {} |
| |
| // These don't actually have a target feature unlike their brethren with |
| // the `x` inside the name which requires adx |
| "_addcarry_u32" | "_addcarry_u64" | "_subborrow_u32" | "_subborrow_u64" => {} |
| |
| "_bittest" |
| | "_bittestandset" |
| | "_bittestandreset" |
| | "_bittestandcomplement" |
| | "_bittest64" |
| | "_bittestandset64" |
| | "_bittestandreset64" |
| | "_bittestandcomplement64" => {} |
| |
| _ => { |
| if intel.cpuid.is_empty() { |
| bail!("missing cpuid for {}", rust.name); |
| } |
| } |
| } |
| |
| let rust_features = match rust.target_feature { |
| Some(features) => features |
| .split(',') |
| .map(|feature| feature.to_string()) |
| .collect(), |
| None => HashSet::new(), |
| }; |
| |
| let mut intel_cpuids = HashSet::new(); |
| |
| for cpuid in &intel.cpuid { |
| // The pause intrinsic is in the SSE2 module, but it is backwards |
| // compatible with CPUs without SSE2, and it therefore does not need the |
| // target-feature attribute. |
| if rust.name == "_mm_pause" { |
| continue; |
| } |
| |
| // these flags on the rdtsc/rtdscp intrinsics we don't test for right |
| // now, but we may wish to add these one day! |
| // |
| // For more info see #308 |
| if *cpuid == "TSC" || *cpuid == "RDTSCP" { |
| continue; |
| } |
| |
| // Some CPUs support VAES/GFNI/VPCLMULQDQ without AVX512, even though |
| // the Intel documentation states that those instructions require |
| // AVX512VL. |
| if *cpuid == "AVX512VL" |
| && intel |
| .cpuid |
| .iter() |
| .any(|x| matches!(&**x, "VAES" | "GFNI" | "VPCLMULQDQ")) |
| { |
| continue; |
| } |
| |
| let cpuid = cpuid.to_lowercase().replace('_', ""); |
| |
| // Fix mismatching feature names: |
| let fixed_cpuid = match cpuid.as_ref() { |
| // The XML file names IFMA as "avx512ifma52", while Rust calls |
| // it "avx512ifma". |
| "avx512ifma52" => String::from("avx512ifma"), |
| "xss" => String::from("xsaves"), |
| "keylocker" => String::from("kl"), |
| "keylockerwide" => String::from("widekl"), |
| _ => cpuid, |
| }; |
| |
| intel_cpuids.insert(fixed_cpuid); |
| } |
| |
| if intel_cpuids.contains("gfni") { |
| if rust.name.contains("mask") { |
| // LLVM requires avx512bw for all masked GFNI intrinsics, and also avx512vl for the 128- and 256-bit versions |
| if !rust.name.starts_with("_mm512") { |
| intel_cpuids.insert(String::from("avx512vl")); |
| } |
| intel_cpuids.insert(String::from("avx512bw")); |
| } else if rust.name.starts_with("_mm256") { |
| // LLVM requires AVX for all non-masked 256-bit GFNI intrinsics |
| intel_cpuids.insert(String::from("avx")); |
| } |
| } |
| |
| // Also, 512-bit vpclmulqdq intrisic requires avx512f |
| if &rust.name == &"_mm512_clmulepi64_epi128" { |
| intel_cpuids.insert(String::from("avx512f")); |
| } |
| |
| if rust_features != intel_cpuids { |
| bail!( |
| "Intel cpuids `{:?}` doesn't match Rust `{:?}` for {}", |
| intel_cpuids, |
| rust_features, |
| rust.name |
| ); |
| } |
| |
| Ok(()) |
| } |
| |
| fn matches(rust: &Function, intel: &Intrinsic) -> Result<(), String> { |
| check_target_features(rust, intel)?; |
| |
| if PRINT_INSTRUCTION_VIOLATIONS { |
| if rust.instrs.is_empty() { |
| if !intel.instruction.is_empty() && !intel.generates_sequence { |
| println!( |
| "instruction not listed for `{}`, but intel lists {:?}", |
| rust.name, intel.instruction |
| ); |
| } |
| |
| // If intel doesn't list any instructions and we do then don't |
| // bother trying to look for instructions in intel, we've just got |
| // some extra assertions on our end. |
| } else if !intel.instruction.is_empty() { |
| for instr in rust.instrs { |
| let asserting = intel |
| .instruction |
| .iter() |
| .any(|a| a.name.to_lowercase().starts_with(instr)); |
| if !asserting { |
| println!( |
| "intel failed to list `{}` as an instruction for `{}`", |
| instr, rust.name |
| ); |
| } |
| } |
| } |
| } |
| |
| // Make sure we've got the right return type. |
| if let Some(t) = rust.ret { |
| equate(t, &intel.return_.type_, "", intel, false)?; |
| } else if !intel.return_.type_.is_empty() && intel.return_.type_ != "void" { |
| bail!( |
| "{} returns `{}` with intel, void in rust", |
| rust.name, |
| intel.return_.type_ |
| ); |
| } |
| |
| // If there's no arguments on Rust's side intel may list one "void" |
| // argument, so handle that here. |
| if rust.arguments.is_empty() && intel.parameters.len() == 1 { |
| if intel.parameters[0].type_ != "void" { |
| bail!("rust has 0 arguments, intel has one for") |
| } |
| } else { |
| // Otherwise we want all parameters to be exactly the same |
| if rust.arguments.len() != intel.parameters.len() { |
| bail!("wrong number of arguments on {}", rust.name); |
| } |
| for (i, (a, b)) in intel.parameters.iter().zip(rust.arguments).enumerate() { |
| let is_const = rust.required_const.contains(&i); |
| equate(b, &a.type_, &a.etype, &intel, is_const)?; |
| } |
| } |
| |
| let any_i64 = rust |
| .arguments |
| .iter() |
| .cloned() |
| .chain(rust.ret) |
| .any(|arg| matches!(*arg, Type::PrimSigned(64) | Type::PrimUnsigned(64))); |
| let any_i64_exempt = match rust.name { |
| // These intrinsics have all been manually verified against Clang's |
| // headers to be available on x86, and the u64 arguments seem |
| // spurious I guess? |
| "_xsave" | "_xrstor" | "_xsetbv" | "_xgetbv" | "_xsaveopt" | "_xsavec" | "_xsaves" |
| | "_xrstors" => true, |
| |
| // Apparently all of clang/msvc/gcc accept these intrinsics on |
| // 32-bit, so let's do the same |
| "_mm_set_epi64x" |
| | "_mm_set1_epi64x" |
| | "_mm256_set_epi64x" |
| | "_mm256_setr_epi64x" |
| | "_mm256_set1_epi64x" |
| | "_mm512_set1_epi64" |
| | "_mm256_mask_set1_epi64" |
| | "_mm256_maskz_set1_epi64" |
| | "_mm_mask_set1_epi64" |
| | "_mm_maskz_set1_epi64" |
| | "_mm512_set4_epi64" |
| | "_mm512_setr4_epi64" |
| | "_mm512_set_epi64" |
| | "_mm512_setr_epi64" |
| | "_mm512_reduce_add_epi64" |
| | "_mm512_mask_reduce_add_epi64" |
| | "_mm512_reduce_mul_epi64" |
| | "_mm512_mask_reduce_mul_epi64" |
| | "_mm512_reduce_max_epi64" |
| | "_mm512_mask_reduce_max_epi64" |
| | "_mm512_reduce_max_epu64" |
| | "_mm512_mask_reduce_max_epu64" |
| | "_mm512_reduce_min_epi64" |
| | "_mm512_mask_reduce_min_epi64" |
| | "_mm512_reduce_min_epu64" |
| | "_mm512_mask_reduce_min_epu64" |
| | "_mm512_reduce_and_epi64" |
| | "_mm512_mask_reduce_and_epi64" |
| | "_mm512_reduce_or_epi64" |
| | "_mm512_mask_reduce_or_epi64" |
| | "_mm512_mask_set1_epi64" |
| | "_mm512_maskz_set1_epi64" |
| | "_mm_cvt_roundss_si64" |
| | "_mm_cvt_roundss_i64" |
| | "_mm_cvt_roundss_u64" |
| | "_mm_cvtss_i64" |
| | "_mm_cvtss_u64" |
| | "_mm_cvt_roundsd_si64" |
| | "_mm_cvt_roundsd_i64" |
| | "_mm_cvt_roundsd_u64" |
| | "_mm_cvtsd_i64" |
| | "_mm_cvtsd_u64" |
| | "_mm_cvt_roundi64_ss" |
| | "_mm_cvt_roundi64_sd" |
| | "_mm_cvt_roundsi64_ss" |
| | "_mm_cvt_roundsi64_sd" |
| | "_mm_cvt_roundu64_ss" |
| | "_mm_cvt_roundu64_sd" |
| | "_mm_cvti64_ss" |
| | "_mm_cvti64_sd" |
| | "_mm_cvtt_roundss_si64" |
| | "_mm_cvtt_roundss_i64" |
| | "_mm_cvtt_roundss_u64" |
| | "_mm_cvttss_i64" |
| | "_mm_cvttss_u64" |
| | "_mm_cvtt_roundsd_si64" |
| | "_mm_cvtt_roundsd_i64" |
| | "_mm_cvtt_roundsd_u64" |
| | "_mm_cvttsd_i64" |
| | "_mm_cvttsd_u64" |
| | "_mm_cvtu64_ss" |
| | "_mm_cvtu64_sd" => true, |
| |
| // These return a 64-bit argument but they're assembled from other |
| // 32-bit registers, so these work on 32-bit just fine. See #308 for |
| // more info. |
| "_rdtsc" | "__rdtscp" => true, |
| |
| _ => false, |
| }; |
| if any_i64 && !any_i64_exempt && !rust.file.contains("x86_64") { |
| bail!( |
| "intrinsic `{}` uses a 64-bit bare type but may be \ |
| available on 32-bit platforms", |
| rust.name |
| ); |
| } |
| if !rust.doc.contains("Intel") { |
| bail!("No link to Intel"); |
| } |
| let recognized_links = [ |
| "https://www.intel.com/content/www/us/en/docs/intrinsics-guide/index.html", |
| "https://software.intel.com/sites/landingpage/IntrinsicsGuide/", |
| ]; |
| if !recognized_links.iter().any(|link| rust.doc.contains(link)) { |
| bail!("Unrecognized Intel Link"); |
| } |
| if !rust.doc.contains(&rust.name[1..]) { |
| // We can leave the leading underscore |
| bail!("Bad link to Intel"); |
| } |
| Ok(()) |
| } |
| |
| fn pointed_type(intrinsic: &Intrinsic) -> Result<Type, String> { |
| Ok( |
| if intrinsic.tech == "AMX" |
| || intrinsic |
| .cpuid |
| .iter() |
| .any(|cpuid| matches!(&**cpuid, "KEYLOCKER" | "KEYLOCKER_WIDE" | "XSAVE" | "FXSR")) |
| { |
| // AMX, KEYLOCKER and XSAVE intrinsics should take `*u8` |
| U8 |
| } else if intrinsic.name == "_mm_clflush" { |
| // Just a false match in the following logic |
| U8 |
| } else if ["_mm_storeu_si", "_mm_loadu_si"] |
| .iter() |
| .any(|x| intrinsic.name.starts_with(x)) |
| { |
| // These have already been stabilized, so cannot be changed anymore |
| U8 |
| } else if intrinsic.name.ends_with("i8") { |
| I8 |
| } else if intrinsic.name.ends_with("i16") { |
| I16 |
| } else if intrinsic.name.ends_with("i32") { |
| I32 |
| } else if intrinsic.name.ends_with("i64") { |
| I64 |
| } else if intrinsic.name.ends_with("i128") { |
| M128I |
| } else if intrinsic.name.ends_with("i256") { |
| M256I |
| } else if intrinsic.name.ends_with("i512") { |
| M512I |
| } else if intrinsic.name.ends_with("h") { |
| F16 |
| } else if intrinsic.name.ends_with("s") { |
| F32 |
| } else if intrinsic.name.ends_with("d") { |
| F64 |
| } else { |
| bail!( |
| "Don't know what type of *void to use for {}", |
| intrinsic.name |
| ); |
| }, |
| ) |
| } |
| |
| fn equate( |
| t: &Type, |
| intel: &str, |
| etype: &str, |
| intrinsic: &Intrinsic, |
| is_const: bool, |
| ) -> Result<(), String> { |
| // Make pointer adjacent to the type: float * foo => float* foo |
| let mut intel = intel.replace(" *", "*"); |
| // Make mutability modifier adjacent to the pointer: |
| // float const * foo => float const* foo |
| intel = intel.replace("const *", "const*"); |
| // Normalize mutability modifier to after the type: |
| // const float* foo => float const* |
| if intel.starts_with("const") && intel.ends_with('*') { |
| intel = intel.replace("const ", ""); |
| intel = intel.replace('*', " const*"); |
| } |
| if etype == "IMM" || intel == "constexpr int" { |
| // The _bittest intrinsics claim to only accept immediates but actually |
| // accept run-time values as well. |
| if !is_const && !intrinsic.name.starts_with("_bittest") { |
| bail!("argument required to be const but isn't"); |
| } |
| } else { |
| // const int must be an IMM |
| assert_ne!(intel, "const int"); |
| if is_const { |
| bail!("argument is const but shouldn't be"); |
| } |
| } |
| match (t, &intel[..]) { |
| (&Type::PrimFloat(16), "_Float16") => {} |
| (&Type::PrimFloat(32), "float") => {} |
| (&Type::PrimFloat(64), "double") => {} |
| (&Type::PrimSigned(8), "__int8" | "char") => {} |
| (&Type::PrimSigned(16), "__int16" | "short") => {} |
| (&Type::PrimSigned(32), "__int32" | "constexpr int" | "const int" | "int") => {} |
| (&Type::PrimSigned(64), "__int64" | "long long") => {} |
| (&Type::PrimUnsigned(8), "unsigned char") => {} |
| (&Type::PrimUnsigned(16), "unsigned short") => {} |
| (&Type::BFloat16, "__bfloat16") => {} |
| ( |
| &Type::PrimUnsigned(32), |
| "unsigned __int32" | "unsigned int" | "unsigned long" | "const unsigned int", |
| ) => {} |
| (&Type::PrimUnsigned(64), "unsigned __int64") => {} |
| (&Type::PrimUnsigned(SS), "size_t") => {} |
| |
| (&Type::M128, "__m128") => {} |
| (&Type::M128BH, "__m128bh") => {} |
| (&Type::M128I, "__m128i") => {} |
| (&Type::M128D, "__m128d") => {} |
| (&Type::M128H, "__m128h") => {} |
| (&Type::M256, "__m256") => {} |
| (&Type::M256BH, "__m256bh") => {} |
| (&Type::M256I, "__m256i") => {} |
| (&Type::M256D, "__m256d") => {} |
| (&Type::M256H, "__m256h") => {} |
| (&Type::M512, "__m512") => {} |
| (&Type::M512BH, "__m512bh") => {} |
| (&Type::M512I, "__m512i") => {} |
| (&Type::M512D, "__m512d") => {} |
| (&Type::M512H, "__m512h") => {} |
| (&Type::MMASK64, "__mmask64") => {} |
| (&Type::MMASK32, "__mmask32") => {} |
| (&Type::MMASK16, "__mmask16") => {} |
| (&Type::MMASK8, "__mmask8") => {} |
| |
| (&Type::MutPtr(_type), "void*") | (&Type::ConstPtr(_type), "void const*") => { |
| let pointed_type = pointed_type(intrinsic)?; |
| if _type != &pointed_type { |
| bail!( |
| "incorrect void pointer type {_type:?} in {}, should be pointer to {pointed_type:?}", |
| intrinsic.name, |
| ); |
| } |
| } |
| |
| (&Type::MutPtr(&Type::PrimFloat(32)), "float*") => {} |
| (&Type::MutPtr(&Type::PrimFloat(64)), "double*") => {} |
| (&Type::MutPtr(&Type::PrimSigned(8)), "char*") => {} |
| (&Type::MutPtr(&Type::PrimSigned(32)), "__int32*" | "int*") => {} |
| (&Type::MutPtr(&Type::PrimSigned(64)), "__int64*") => {} |
| (&Type::MutPtr(&Type::PrimUnsigned(8)), "unsigned char*") => {} |
| (&Type::MutPtr(&Type::PrimUnsigned(16)), "unsigned short*") => {} |
| (&Type::MutPtr(&Type::PrimUnsigned(32)), "unsigned int*" | "unsigned __int32*") => {} |
| (&Type::MutPtr(&Type::PrimUnsigned(64)), "unsigned __int64*") => {} |
| |
| (&Type::MutPtr(&Type::MMASK8), "__mmask8*") => {} |
| (&Type::MutPtr(&Type::MMASK32), "__mmask32*") => {} |
| (&Type::MutPtr(&Type::MMASK64), "__mmask64*") => {} |
| (&Type::MutPtr(&Type::MMASK16), "__mmask16*") => {} |
| |
| (&Type::MutPtr(&Type::M128), "__m128*") => {} |
| (&Type::MutPtr(&Type::M128BH), "__m128bh*") => {} |
| (&Type::MutPtr(&Type::M128I), "__m128i*") => {} |
| (&Type::MutPtr(&Type::M128D), "__m128d*") => {} |
| (&Type::MutPtr(&Type::M256), "__m256*") => {} |
| (&Type::MutPtr(&Type::M256BH), "__m256bh*") => {} |
| (&Type::MutPtr(&Type::M256I), "__m256i*") => {} |
| (&Type::MutPtr(&Type::M256D), "__m256d*") => {} |
| (&Type::MutPtr(&Type::M512), "__m512*") => {} |
| (&Type::MutPtr(&Type::M512BH), "__m512bh*") => {} |
| (&Type::MutPtr(&Type::M512I), "__m512i*") => {} |
| (&Type::MutPtr(&Type::M512D), "__m512d*") => {} |
| |
| (&Type::ConstPtr(&Type::PrimFloat(16)), "_Float16 const*") => {} |
| (&Type::ConstPtr(&Type::PrimFloat(32)), "float const*") => {} |
| (&Type::ConstPtr(&Type::PrimFloat(64)), "double const*") => {} |
| (&Type::ConstPtr(&Type::PrimSigned(8)), "char const*") => {} |
| (&Type::ConstPtr(&Type::PrimSigned(32)), "__int32 const*" | "int const*") => {} |
| (&Type::ConstPtr(&Type::PrimSigned(64)), "__int64 const*") => {} |
| (&Type::ConstPtr(&Type::PrimUnsigned(16)), "unsigned short const*") => {} |
| (&Type::ConstPtr(&Type::PrimUnsigned(32)), "unsigned int const*") => {} |
| (&Type::ConstPtr(&Type::PrimUnsigned(64)), "unsigned __int64 const*") => {} |
| (&Type::ConstPtr(&Type::BFloat16), "__bf16 const*") => {} |
| |
| (&Type::ConstPtr(&Type::M128), "__m128 const*") => {} |
| (&Type::ConstPtr(&Type::M128BH), "__m128bh const*") => {} |
| (&Type::ConstPtr(&Type::M128I), "__m128i const*") => {} |
| (&Type::ConstPtr(&Type::M128D), "__m128d const*") => {} |
| (&Type::ConstPtr(&Type::M128H), "__m128h const*") => {} |
| (&Type::ConstPtr(&Type::M256), "__m256 const*") => {} |
| (&Type::ConstPtr(&Type::M256BH), "__m256bh const*") => {} |
| (&Type::ConstPtr(&Type::M256I), "__m256i const*") => {} |
| (&Type::ConstPtr(&Type::M256D), "__m256d const*") => {} |
| (&Type::ConstPtr(&Type::M256H), "__m256h const*") => {} |
| (&Type::ConstPtr(&Type::M512), "__m512 const*") => {} |
| (&Type::ConstPtr(&Type::M512BH), "__m512bh const*") => {} |
| (&Type::ConstPtr(&Type::M512I), "__m512i const*") => {} |
| (&Type::ConstPtr(&Type::M512D), "__m512d const*") => {} |
| |
| (&Type::ConstPtr(&Type::MMASK8), "__mmask8*") => {} |
| (&Type::ConstPtr(&Type::MMASK16), "__mmask16*") => {} |
| (&Type::ConstPtr(&Type::MMASK32), "__mmask32*") => {} |
| (&Type::ConstPtr(&Type::MMASK64), "__mmask64*") => {} |
| |
| (&Type::MM_CMPINT_ENUM, "_MM_CMPINT_ENUM") => {} |
| (&Type::MM_MANTISSA_NORM_ENUM, "_MM_MANTISSA_NORM_ENUM") => {} |
| (&Type::MM_MANTISSA_SIGN_ENUM, "_MM_MANTISSA_SIGN_ENUM") => {} |
| (&Type::MM_PERM_ENUM, "_MM_PERM_ENUM") => {} |
| |
| // This is a macro (?) in C which seems to mutate its arguments, but |
| // that means that we're taking pointers to arguments in rust |
| // as we're not exposing it as a macro. |
| (&Type::MutPtr(&Type::M128), "__m128") if intrinsic.name == "_MM_TRANSPOSE4_PS" => {} |
| |
| // The _rdtsc intrinsic uses a __int64 return type, but this is a bug in |
| // the intrinsics guide: https://github.com/rust-lang/stdarch/issues/559 |
| // We have manually fixed the bug by changing the return type to `u64`. |
| (&Type::PrimUnsigned(64), "__int64") if intrinsic.name == "_rdtsc" => {} |
| |
| // The _bittest and _bittest64 intrinsics takes a mutable pointer in the |
| // intrinsics guide even though it never writes through the pointer: |
| (&Type::ConstPtr(&Type::PrimSigned(32)), "__int32*") if intrinsic.name == "_bittest" => {} |
| (&Type::ConstPtr(&Type::PrimSigned(64)), "__int64*") if intrinsic.name == "_bittest64" => {} |
| // The _xrstor, _fxrstor, _xrstor64, _fxrstor64 intrinsics take a |
| // mutable pointer in the intrinsics guide even though they never write |
| // through the pointer: |
| (&Type::ConstPtr(&Type::PrimUnsigned(8)), "void*") |
| if matches!( |
| &*intrinsic.name, |
| "_xrstor" | "_xrstor64" | "_fxrstor" | "_fxrstor64" |
| ) => {} |
| // The _mm_stream_load_si128 intrinsic take a mutable pointer in the intrinsics |
| // guide even though they never write through the pointer |
| (&Type::ConstPtr(&Type::M128I), "void*") if intrinsic.name == "_mm_stream_load_si128" => {} |
| /// Intel requires the mask argument for _mm_shuffle_ps to be an |
| // unsigned integer, but all other _mm_shuffle_.. intrinsics |
| // take a signed-integer. This breaks `_MM_SHUFFLE` for |
| // `_mm_shuffle_ps` |
| (&Type::PrimSigned(32), "unsigned int") if intrinsic.name == "_mm_shuffle_ps" => {} |
| |
| _ => bail!( |
| "failed to equate: `{intel}` and {t:?} for {}", |
| intrinsic.name |
| ), |
| } |
| Ok(()) |
| } |