| //! Hexagon HVX Code Generator |
| //! |
| //! This generator creates v64.rs and v128.rs from scratch using the LLVM HVX |
| //! header file as the sole source of truth. It parses the C intrinsic prototypes |
| //! and generates Rust wrapper functions with appropriate attributes. |
| //! |
| //! The two generated files provide: |
| //! - v64.rs: 64-byte vector mode intrinsics (512-bit vectors) |
| //! - v128.rs: 128-byte vector mode intrinsics (1024-bit vectors) |
| //! |
| //! Both modules are available unconditionally, but require the appropriate |
| //! target features to actually use the intrinsics. |
| //! |
| //! Usage: |
| //! cd crates/stdarch-gen-hexagon |
| //! cargo run |
| //! # Output is written to ../core_arch/src/hexagon/v64.rs and v128.rs |
| |
| use regex::Regex; |
| use std::collections::{HashMap, HashSet}; |
| use std::fs::File; |
| use std::io::Write; |
| use std::path::Path; |
| |
| /// Mappings from HVX intrinsics to architecture-independent SIMD intrinsics. |
| /// These intrinsics have equivalent semantics and can be lowered to the generic form. |
| fn get_simd_intrinsic_mappings() -> HashMap<&'static str, &'static str> { |
| let mut map = HashMap::new(); |
| // Bitwise operations (element-size independent) |
| map.insert("vxor", "simd_xor"); |
| map.insert("vand", "simd_and"); |
| map.insert("vor", "simd_or"); |
| // Word (32-bit) arithmetic operations |
| map.insert("vaddw", "simd_add"); |
| map.insert("vsubw", "simd_sub"); |
| map |
| } |
| |
| /// The tracking issue number for the stdarch_hexagon feature |
| const TRACKING_ISSUE: &str = "151523"; |
| |
| /// HVX vector length mode |
| #[derive(Debug, Clone, Copy, PartialEq)] |
| enum VectorMode { |
| /// 64-byte vectors (512 bits) |
| V64, |
| /// 128-byte vectors (1024 bits) |
| V128, |
| } |
| |
| impl VectorMode { |
| fn bytes(&self) -> u32 { |
| match self { |
| VectorMode::V64 => 64, |
| VectorMode::V128 => 128, |
| } |
| } |
| |
| fn bits(&self) -> u32 { |
| self.bytes() * 8 |
| } |
| |
| fn lanes(&self) -> u32 { |
| self.bytes() / 4 // 32-bit lanes |
| } |
| |
| fn target_feature(&self) -> &'static str { |
| match self { |
| VectorMode::V64 => "hvx-length64b", |
| VectorMode::V128 => "hvx-length128b", |
| } |
| } |
| } |
| |
| /// LLVM version the header file is from (for reference) |
| /// Source: https://github.com/llvm/llvm-project/blob/llvmorg-22.1.0-rc1/clang/lib/Headers/hvx_hexagon_protos.h |
| const LLVM_VERSION: &str = "22.1.0-rc1"; |
| |
| /// Maximum HVX architecture version supported by rustc |
| /// Check with: rustc --target=hexagon-unknown-linux-musl --print target-features |
| const MAX_SUPPORTED_ARCH: u32 = 79; |
| |
| /// Local header file path (checked into the repository) |
| const HEADER_FILE: &str = "hvx_hexagon_protos.h"; |
| |
| /// Intrinsic information parsed from the LLVM header |
| #[derive(Debug, Clone)] |
| struct IntrinsicInfo { |
| /// The Q6_* intrinsic name (e.g., "Q6_V_vadd_VV") |
| q6_name: String, |
| /// The LLVM builtin name without prefix (e.g., "V6_vaddb") |
| builtin_name: String, |
| /// The short instruction name for assert_instr (e.g., "vaddb") |
| instr_name: String, |
| /// The assembly syntax from the comment |
| asm_syntax: String, |
| /// Instruction type |
| instr_type: String, |
| /// Execution slots |
| exec_slots: String, |
| /// Minimum HVX architecture version required |
| min_arch: u32, |
| /// Return type |
| return_type: RustType, |
| /// Parameters (name, type) |
| params: Vec<(String, RustType)>, |
| /// Whether this is a compound intrinsic (multiple builtins) |
| is_compound: bool, |
| /// For compound intrinsics: the parsed expression tree |
| compound_expr: Option<CompoundExpr>, |
| } |
| |
| /// Expression tree for compound intrinsics |
| #[derive(Debug, Clone)] |
| enum CompoundExpr { |
| /// A call to a builtin: (builtin_name without V6_ prefix, arguments) |
| BuiltinCall(String, Vec<CompoundExpr>), |
| /// A parameter reference by name |
| Param(String), |
| /// An integer literal (like -1) |
| IntLiteral(i32), |
| } |
| |
| /// Rust type mappings |
| #[derive(Debug, Clone, PartialEq)] |
| enum RustType { |
| HvxVector, |
| HvxVectorPair, |
| HvxVectorPred, |
| I32, |
| MutPtrHvxVector, |
| Unit, |
| } |
| |
| impl RustType { |
| fn from_c_type(c_type: &str) -> Option<Self> { |
| match c_type.trim() { |
| "HVX_Vector" => Some(RustType::HvxVector), |
| "HVX_VectorPair" => Some(RustType::HvxVectorPair), |
| "HVX_VectorPred" => Some(RustType::HvxVectorPred), |
| "Word32" => Some(RustType::I32), |
| "HVX_Vector*" => Some(RustType::MutPtrHvxVector), |
| "void" => Some(RustType::Unit), |
| _ => None, |
| } |
| } |
| |
| fn to_rust_str(&self) -> &'static str { |
| match self { |
| RustType::HvxVector => "HvxVector", |
| RustType::HvxVectorPair => "HvxVectorPair", |
| RustType::HvxVectorPred => "HvxVectorPred", |
| RustType::I32 => "i32", |
| RustType::MutPtrHvxVector => "*mut HvxVector", |
| RustType::Unit => "()", |
| } |
| } |
| |
| fn to_extern_str(&self) -> &'static str { |
| match self { |
| RustType::HvxVector => "HvxVector", |
| RustType::HvxVectorPair => "HvxVectorPair", |
| RustType::HvxVectorPred => "HvxVectorPred", |
| RustType::I32 => "i32", |
| RustType::MutPtrHvxVector => "*mut HvxVector", |
| RustType::Unit => "()", |
| } |
| } |
| } |
| |
| /// Parse a compound macro expression into an expression tree |
| fn parse_compound_expr(expr: &str) -> Option<CompoundExpr> { |
| let expr = expr.trim(); |
| |
| // Try to match an integer literal (like -1) |
| if let Ok(n) = expr.parse::<i32>() { |
| return Some(CompoundExpr::IntLiteral(n)); |
| } |
| |
| // Try to match a simple parameter name (Vu, Vv, Rt, Qs, Qt, Qx, Vx, etc.) |
| // These are typically short identifiers in the macro |
| if expr.len() <= 3 |
| && expr.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') |
| && !expr.contains("__") |
| { |
| return Some(CompoundExpr::Param(expr.to_lowercase())); |
| } |
| |
| // Check if it's wrapped in extra parens first |
| if expr.starts_with('(') && expr.ends_with(')') { |
| // Check if these parens wrap the entire expression |
| let inner = &expr[1..expr.len() - 1]; |
| // Count depth: if after removing outer parens the expression is balanced, |
| // the outer parens were enclosing everything |
| if is_balanced_parens(inner) { |
| // But we also need to verify these aren't part of a function call |
| // If the inner expression is balanced and the whole thing starts with ( |
| // and ends with ), it's a paren wrapper |
| let result = parse_compound_expr(inner); |
| if result.is_some() { |
| return result; |
| } |
| } |
| } |
| |
| // Try to match __BUILTIN_VECTOR_WRAP(__builtin_HEXAGON_V6_xxx)(args) |
| // The args portion may contain nested calls, so we need to find the matching paren |
| if expr.starts_with("__BUILTIN_VECTOR_WRAP(__builtin_HEXAGON_V6_") { |
| // Find the end of the builtin name (after V6_) |
| let prefix = "__BUILTIN_VECTOR_WRAP(__builtin_HEXAGON_V6_"; |
| let after_prefix = &expr[prefix.len()..]; |
| if let Some(paren_pos) = after_prefix.find(')') { |
| let builtin_name = &after_prefix[..paren_pos]; |
| let rest = &after_prefix[paren_pos + 1..]; // Skip the closing ) of the WRAP |
| // rest should now be "(args)" |
| if rest.starts_with('(') && rest.ends_with(')') { |
| let args_str = &rest[1..rest.len() - 1]; |
| let args = parse_compound_args(args_str)?; |
| return Some(CompoundExpr::BuiltinCall(builtin_name.to_string(), args)); |
| } |
| } |
| } |
| |
| // Try to match __builtin_HEXAGON_V6_xxx(args) without wrap |
| if expr.starts_with("__builtin_HEXAGON_V6_") { |
| let prefix = "__builtin_HEXAGON_V6_"; |
| let after_prefix = &expr[prefix.len()..]; |
| if let Some(paren_pos) = after_prefix.find('(') { |
| let builtin_name = &after_prefix[..paren_pos]; |
| let rest = &after_prefix[paren_pos..]; |
| if rest.starts_with('(') && rest.ends_with(')') { |
| let args_str = &rest[1..rest.len() - 1]; |
| let args = parse_compound_args(args_str)?; |
| return Some(CompoundExpr::BuiltinCall(builtin_name.to_string(), args)); |
| } |
| } |
| } |
| |
| None |
| } |
| |
| /// Check if parentheses are balanced in a string |
| fn is_balanced_parens(s: &str) -> bool { |
| let mut depth = 0; |
| for c in s.chars() { |
| match c { |
| '(' => depth += 1, |
| ')' => { |
| depth -= 1; |
| if depth < 0 { |
| return false; |
| } |
| } |
| _ => {} |
| } |
| } |
| depth == 0 |
| } |
| |
| /// Parse comma-separated arguments, respecting nested parentheses |
| fn parse_compound_args(args_str: &str) -> Option<Vec<CompoundExpr>> { |
| let mut args = Vec::new(); |
| let mut current = String::new(); |
| let mut depth = 0; |
| |
| for c in args_str.chars() { |
| match c { |
| '(' => { |
| depth += 1; |
| current.push(c); |
| } |
| ')' => { |
| depth -= 1; |
| current.push(c); |
| } |
| ',' if depth == 0 => { |
| let arg = current.trim().to_string(); |
| if !arg.is_empty() { |
| args.push(parse_compound_expr(&arg)?); |
| } |
| current.clear(); |
| } |
| _ => current.push(c), |
| } |
| } |
| |
| // Don't forget the last argument |
| let arg = current.trim().to_string(); |
| if !arg.is_empty() { |
| args.push(parse_compound_expr(&arg)?); |
| } |
| |
| Some(args) |
| } |
| |
| /// Extract all builtin names used in a compound expression |
| fn collect_builtins_from_expr(expr: &CompoundExpr, builtins: &mut HashSet<String>) { |
| match expr { |
| CompoundExpr::BuiltinCall(name, args) => { |
| builtins.insert(name.clone()); |
| for arg in args { |
| collect_builtins_from_expr(arg, builtins); |
| } |
| } |
| CompoundExpr::Param(_) | CompoundExpr::IntLiteral(_) => {} |
| } |
| } |
| |
| /// Read the local HVX header file |
| fn read_header(crate_dir: &Path) -> Result<String, String> { |
| let header_path = crate_dir.join(HEADER_FILE); |
| println!("Reading HVX header from: {}", header_path.display()); |
| println!(" (LLVM version: {})", LLVM_VERSION); |
| |
| std::fs::read_to_string(&header_path).map_err(|e| { |
| format!( |
| "Failed to read header file {}: {}", |
| header_path.display(), |
| e |
| ) |
| }) |
| } |
| |
| /// Parse a C function prototype to extract return type and parameters |
| fn parse_prototype(prototype: &str) -> Option<(RustType, Vec<(String, RustType)>)> { |
| // Pattern: ReturnType FunctionName(ParamType1 Param1, ParamType2 Param2, ...) |
| let proto_re = Regex::new(r"(\w+(?:\*)?)\s+Q6_\w+\(([^)]*)\)").unwrap(); |
| |
| if let Some(caps) = proto_re.captures(prototype) { |
| let return_type_str = caps[1].trim(); |
| let params_str = &caps[2]; |
| |
| let return_type = RustType::from_c_type(return_type_str)?; |
| |
| let mut params = Vec::new(); |
| if !params_str.trim().is_empty() { |
| // Pattern: Type Name or Type* Name |
| let param_re = Regex::new(r"(\w+\*?)\s+(\w+)").unwrap(); |
| for param in params_str.split(',') { |
| let param = param.trim(); |
| if let Some(pcaps) = param_re.captures(param) { |
| let ptype_str = pcaps[1].trim(); |
| let pname = pcaps[2].to_lowercase(); |
| if let Some(ptype) = RustType::from_c_type(ptype_str) { |
| params.push((pname, ptype)); |
| } else { |
| return None; // Unknown type |
| } |
| } |
| } |
| } |
| |
| Some((return_type, params)) |
| } else { |
| None |
| } |
| } |
| |
| /// Parse the LLVM header file to extract intrinsic information |
| fn parse_header(content: &str) -> Vec<IntrinsicInfo> { |
| let mut intrinsics = Vec::new(); |
| |
| let arch_re = Regex::new(r"#if __HVX_ARCH__ >= (\d+)").unwrap(); |
| |
| // Regex to extract the simple builtin name from a macro body |
| // Match: __BUILTIN_VECTOR_WRAP(__builtin_HEXAGON_V6_xxx)(args) |
| let simple_builtin_re = |
| Regex::new(r"__BUILTIN_VECTOR_WRAP\(__builtin_HEXAGON_(\w+)\)\([^)]*\)\s*$").unwrap(); |
| |
| // Also handle builtins without VECTOR_WRAP |
| let simple_builtin_re2 = Regex::new(r"__builtin_HEXAGON_(\w+)\([^)]*\)\s*$").unwrap(); |
| |
| // Regex to extract Q6 name from #define |
| let q6_name_re = Regex::new(r"#define\s+(Q6_\w+)").unwrap(); |
| |
| // Regex to extract macro expression body |
| let macro_expr_re = Regex::new(r"#define\s+Q6_\w+\([^)]*\)\s+(.+)").unwrap(); |
| |
| let lines: Vec<&str> = content.lines().collect(); |
| let mut current_arch: u32 = 60; |
| let mut i = 0; |
| |
| while i < lines.len() { |
| // Track architecture version |
| if let Some(caps) = arch_re.captures(lines[i]) { |
| if let Ok(arch) = caps[1].parse() { |
| current_arch = arch; |
| } |
| } |
| |
| // Look for Assembly Syntax comment block |
| if lines[i].contains("Assembly Syntax:") { |
| let mut asm_syntax = String::new(); |
| let mut prototype = String::new(); |
| let mut instr_type = String::new(); |
| let mut exec_slots = String::new(); |
| |
| // Parse the comment block |
| let mut j = i; |
| while j < lines.len() && !lines[j].starts_with("#define") { |
| let line = lines[j]; |
| if line.contains("Assembly Syntax:") { |
| if let Some(pos) = line.find("Assembly Syntax:") { |
| asm_syntax = line[pos + 16..].trim().to_string(); |
| } |
| } else if line.contains("C Intrinsic Prototype:") { |
| if let Some(pos) = line.find("C Intrinsic Prototype:") { |
| prototype = line[pos + 22..].trim().to_string(); |
| } |
| } else if line.contains("Instruction Type:") { |
| if let Some(pos) = line.find("Instruction Type:") { |
| instr_type = line[pos + 17..].trim().to_string(); |
| } |
| } else if line.contains("Execution Slots:") { |
| if let Some(pos) = line.find("Execution Slots:") { |
| exec_slots = line[pos + 16..].trim().to_string(); |
| } |
| } |
| j += 1; |
| } |
| |
| // Now find the #define line |
| while j < lines.len() && !lines[j].starts_with("#define") { |
| j += 1; |
| } |
| |
| if j < lines.len() { |
| let define_line = lines[j]; |
| |
| // Extract Q6 name and check if it's simple or compound |
| if let Some(caps) = q6_name_re.captures(define_line) { |
| let q6_name = caps[1].to_string(); |
| |
| // Get the full macro body (handle line continuations) |
| let mut macro_body = define_line.to_string(); |
| let mut k = j; |
| while macro_body.trim_end().ends_with('\\') && k + 1 < lines.len() { |
| k += 1; |
| macro_body.push_str(lines[k]); |
| } |
| |
| // Try to extract simple builtin name |
| let builtin_name = simple_builtin_re |
| .captures(¯o_body) |
| .or_else(|| simple_builtin_re2.captures(¯o_body)) |
| .map(|bcaps| bcaps[1].to_string()); |
| |
| // Check if it's a compound intrinsic (multiple __builtin calls) |
| let builtin_count = macro_body.matches("__builtin_HEXAGON_").count(); |
| let is_compound = builtin_count > 1; |
| |
| // Parse prototype |
| if let Some((return_type, params)) = parse_prototype(&prototype) { |
| if is_compound { |
| // For compound intrinsics, parse the expression |
| // Extract the macro body after the parameter list |
| if let Some(expr_caps) = macro_expr_re.captures(¯o_body) { |
| let expr_str = expr_caps[1].trim().replace(['\n', '\\'], " "); |
| let expr_str = expr_str.trim(); |
| |
| if let Some(compound_expr) = parse_compound_expr(expr_str) { |
| // For compound intrinsics, we use the outermost builtin |
| // as the "primary" for the instruction name |
| let (primary_builtin, instr_name) = match &compound_expr { |
| CompoundExpr::BuiltinCall(name, _) => { |
| (name.clone(), name.clone()) |
| } |
| _ => continue, |
| }; |
| |
| intrinsics.push(IntrinsicInfo { |
| q6_name, |
| builtin_name: format!("V6_{}", primary_builtin), |
| instr_name, |
| asm_syntax, |
| instr_type, |
| exec_slots, |
| min_arch: current_arch, |
| return_type, |
| params, |
| is_compound: true, |
| compound_expr: Some(compound_expr), |
| }); |
| } |
| } |
| } else if let Some(builtin) = builtin_name { |
| // Extract short instruction name |
| let instr_name = builtin |
| .strip_prefix("V6_") |
| .map(|s| s.to_string()) |
| .unwrap_or_else(|| builtin.clone()); |
| |
| intrinsics.push(IntrinsicInfo { |
| q6_name, |
| builtin_name: builtin, |
| instr_name, |
| asm_syntax, |
| instr_type, |
| exec_slots, |
| min_arch: current_arch, |
| return_type, |
| params, |
| is_compound: false, |
| compound_expr: None, |
| }); |
| } |
| } |
| } |
| } |
| i = j; |
| } |
| i += 1; |
| } |
| |
| intrinsics |
| } |
| |
| /// Generate the module documentation |
| fn generate_module_doc(mode: VectorMode) -> String { |
| format!( |
| r#"//! Hexagon HVX {bytes}-byte vector mode intrinsics |
| //! |
| //! This module provides intrinsics for the Hexagon Vector Extensions (HVX) |
| //! in {bytes}-byte vector mode ({bits}-bit vectors). |
| //! |
| //! HVX is a wide vector extension designed for high-performance signal processing. |
| //! [Hexagon HVX Programmer's Reference Manual](https://docs.qualcomm.com/doc/80-N2040-61) |
| //! |
| //! ## Vector Types |
| //! |
| //! In {bytes}-byte mode: |
| //! - `HvxVector` is {bits} bits ({bytes} bytes) containing {lanes} x 32-bit values |
| //! - `HvxVectorPair` is {pair_bits} bits ({pair_bytes} bytes) |
| //! - `HvxVectorPred` is {bits} bits ({bytes} bytes) for predicate operations |
| //! |
| //! To use this module, compile with `-C target-feature=+{target_feature}`. |
| //! |
| //! ## Naming Convention |
| //! |
| //! Function names preserve the original Q6 naming case because the convention |
| //! uses case to distinguish register types: |
| //! - `W` (uppercase) = vector pair (`HvxVectorPair`) |
| //! - `V` (uppercase) = vector (`HvxVector`) |
| //! - `Q` (uppercase) = predicate (`HvxVectorPred`) |
| //! - `R` = scalar register (`i32`) |
| //! |
| //! For example, `Q6_W_vcombine_VV` operates on a vector pair while |
| //! `Q6_V_hi_W` extracts a vector from a pair. |
| //! |
| //! ## Architecture Versions |
| //! |
| //! Different intrinsics require different HVX architecture versions. Use the |
| //! appropriate target feature to enable the required version: |
| //! - HVX v60: `-C target-feature=+hvxv60` (basic HVX operations) |
| //! - HVX v62: `-C target-feature=+hvxv62` |
| //! - HVX v65: `-C target-feature=+hvxv65` (includes floating-point support) |
| //! - HVX v66: `-C target-feature=+hvxv66` |
| //! - HVX v68: `-C target-feature=+hvxv68` |
| //! - HVX v69: `-C target-feature=+hvxv69` |
| //! - HVX v73: `-C target-feature=+hvxv73` |
| //! - HVX v79: `-C target-feature=+hvxv79` |
| //! |
| //! Each version includes all features from previous versions. |
| "#, |
| bytes = mode.bytes(), |
| bits = mode.bits(), |
| lanes = mode.lanes(), |
| pair_bytes = mode.bytes() * 2, |
| pair_bits = mode.bits() * 2, |
| target_feature = mode.target_feature(), |
| ) |
| } |
| |
| /// Generate the type definitions for a specific vector mode |
| fn generate_types(mode: VectorMode) -> String { |
| let lanes = mode.lanes(); |
| let pair_lanes = lanes * 2; |
| let bits = mode.bits(); |
| let bytes = mode.bytes(); |
| let pair_bits = bits * 2; |
| let pair_bytes = bytes * 2; |
| |
| format!( |
| r#" |
| #![allow(non_camel_case_types)] |
| #![allow(non_snake_case)] |
| |
| #[cfg(test)] |
| use stdarch_test::assert_instr; |
| |
| use crate::intrinsics::simd::{{simd_add, simd_and, simd_or, simd_sub, simd_xor}}; |
| |
| // HVX type definitions for {bytes}-byte vector mode |
| types! {{ |
| #![unstable(feature = "stdarch_hexagon", issue = "{TRACKING_ISSUE}")] |
| |
| /// HVX vector type ({bits} bits / {bytes} bytes) |
| /// |
| /// This type represents a single HVX vector register containing {lanes} x 32-bit values. |
| pub struct HvxVector({lanes} x i32); |
| |
| /// HVX vector pair type ({pair_bits} bits / {pair_bytes} bytes) |
| /// |
| /// This type represents a pair of HVX vector registers, often used for |
| /// operations that produce double-width results. |
| pub struct HvxVectorPair({pair_lanes} x i32); |
| |
| /// HVX vector predicate type ({bits} bits / {bytes} bytes) |
| /// |
| /// This type represents a predicate vector used for conditional operations. |
| /// Each bit corresponds to a lane in the vector. |
| pub struct HvxVectorPred({lanes} x i32); |
| }} |
| "#, |
| bytes = bytes, |
| bits = bits, |
| lanes = lanes, |
| pair_bits = pair_bits, |
| pair_bytes = pair_bytes, |
| pair_lanes = pair_lanes, |
| TRACKING_ISSUE = TRACKING_ISSUE, |
| ) |
| } |
| |
| /// Builtin signature information for extern declarations |
| struct BuiltinSignature { |
| /// The V6_ prefixed name |
| full_name: String, |
| /// The short name (without V6_) |
| short_name: String, |
| /// Return type |
| return_type: RustType, |
| /// Parameter types |
| param_types: Vec<RustType>, |
| } |
| |
| /// Get known signatures for builtins used in compound operations |
| /// These are the helper builtins that don't have their own Q6_ wrapper |
| fn get_compound_helper_signatures() -> HashMap<String, BuiltinSignature> { |
| let mut map = HashMap::new(); |
| |
| // vandvrt: HVX_Vector -> i32 -> HVX_Vector |
| // Converts predicate to vector representation. LLVM uses HVX_Vector for both. |
| map.insert( |
| "vandvrt".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vandvrt".to_string(), |
| short_name: "vandvrt".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::I32], |
| }, |
| ); |
| |
| // vandqrt: HVX_Vector -> i32 -> HVX_Vector |
| // Converts vector representation back to predicate. LLVM uses HVX_Vector for both. |
| map.insert( |
| "vandqrt".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vandqrt".to_string(), |
| short_name: "vandqrt".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::I32], |
| }, |
| ); |
| |
| // vandvrt_acc: HVX_Vector -> HVX_Vector -> i32 -> HVX_Vector |
| map.insert( |
| "vandvrt_acc".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vandvrt_acc".to_string(), |
| short_name: "vandvrt_acc".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector, RustType::I32], |
| }, |
| ); |
| |
| // vandqrt_acc: HVX_Vector -> HVX_Vector -> i32 -> HVX_Vector |
| map.insert( |
| "vandqrt_acc".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vandqrt_acc".to_string(), |
| short_name: "vandqrt_acc".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector, RustType::I32], |
| }, |
| ); |
| |
| // pred_and: HVX_Vector -> HVX_Vector -> HVX_Vector |
| map.insert( |
| "pred_and".to_string(), |
| BuiltinSignature { |
| full_name: "V6_pred_and".to_string(), |
| short_name: "pred_and".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| |
| // pred_and_n: HVX_Vector -> HVX_Vector -> HVX_Vector |
| map.insert( |
| "pred_and_n".to_string(), |
| BuiltinSignature { |
| full_name: "V6_pred_and_n".to_string(), |
| short_name: "pred_and_n".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| |
| // pred_or: HVX_Vector -> HVX_Vector -> HVX_Vector |
| map.insert( |
| "pred_or".to_string(), |
| BuiltinSignature { |
| full_name: "V6_pred_or".to_string(), |
| short_name: "pred_or".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| |
| // pred_or_n: HVX_Vector -> HVX_Vector -> HVX_Vector |
| map.insert( |
| "pred_or_n".to_string(), |
| BuiltinSignature { |
| full_name: "V6_pred_or_n".to_string(), |
| short_name: "pred_or_n".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| |
| // pred_xor: HVX_Vector -> HVX_Vector -> HVX_Vector |
| map.insert( |
| "pred_xor".to_string(), |
| BuiltinSignature { |
| full_name: "V6_pred_xor".to_string(), |
| short_name: "pred_xor".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| |
| // pred_not: HVX_Vector -> HVX_Vector |
| map.insert( |
| "pred_not".to_string(), |
| BuiltinSignature { |
| full_name: "V6_pred_not".to_string(), |
| short_name: "pred_not".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector], |
| }, |
| ); |
| |
| // pred_scalar2: i32 -> HVX_Vector |
| map.insert( |
| "pred_scalar2".to_string(), |
| BuiltinSignature { |
| full_name: "V6_pred_scalar2".to_string(), |
| short_name: "pred_scalar2".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::I32], |
| }, |
| ); |
| |
| // Conditional store operations |
| map.insert( |
| "vS32b_qpred_ai".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vS32b_qpred_ai".to_string(), |
| short_name: "vS32b_qpred_ai".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::MutPtrHvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| map.insert( |
| "vS32b_nqpred_ai".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vS32b_nqpred_ai".to_string(), |
| short_name: "vS32b_nqpred_ai".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::MutPtrHvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| map.insert( |
| "vS32b_nt_qpred_ai".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vS32b_nt_qpred_ai".to_string(), |
| short_name: "vS32b_nt_qpred_ai".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::MutPtrHvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| map.insert( |
| "vS32b_nt_nqpred_ai".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vS32b_nt_nqpred_ai".to_string(), |
| short_name: "vS32b_nt_nqpred_ai".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::MutPtrHvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| // Conditional accumulation operations |
| for (suffix, _elem) in [("b", "byte"), ("h", "halfword"), ("w", "word")] { |
| // vaddbq, vaddhq, vaddwq |
| map.insert( |
| format!("vadd{}q", suffix), |
| BuiltinSignature { |
| full_name: format!("V6_vadd{}q", suffix), |
| short_name: format!("vadd{}q", suffix), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| // vaddbnq, vaddhnq, vaddwnq |
| map.insert( |
| format!("vadd{}nq", suffix), |
| BuiltinSignature { |
| full_name: format!("V6_vadd{}nq", suffix), |
| short_name: format!("vadd{}nq", suffix), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| } |
| |
| // Comparison operations with accumulation |
| // veqb_and, veqb_or, veqb_xor, etc. |
| for elem in ["b", "h", "w", "ub", "uh", "uw"] { |
| for op in ["and", "or", "xor"] { |
| // veq*_and, veq*_or, veq*_xor |
| map.insert( |
| format!("veq{}_{}", elem, op), |
| BuiltinSignature { |
| full_name: format!("V6_veq{}_{}", elem, op), |
| short_name: format!("veq{}_{}", elem, op), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| // vgt*_and, vgt*_or, vgt*_xor |
| map.insert( |
| format!("vgt{}_{}", elem, op), |
| BuiltinSignature { |
| full_name: format!("V6_vgt{}_{}", elem, op), |
| short_name: format!("vgt{}_{}", elem, op), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| } |
| } |
| |
| // Floating-point comparison operations (hf = half-float, sf = single-float) |
| for elem in ["hf", "sf"] { |
| // Basic comparison: vgt* |
| map.insert( |
| format!("vgt{}", elem), |
| BuiltinSignature { |
| full_name: format!("V6_vgt{}", elem), |
| short_name: format!("vgt{}", elem), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| |
| for op in ["and", "or", "xor"] { |
| // vgt*_and, vgt*_or, vgt*_xor |
| map.insert( |
| format!("vgt{}_{}", elem, op), |
| BuiltinSignature { |
| full_name: format!("V6_vgt{}_{}", elem, op), |
| short_name: format!("vgt{}_{}", elem, op), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| } |
| } |
| |
| // Prefix operations with predicate |
| for elem in ["b", "h", "w"] { |
| map.insert( |
| format!("vprefixq{}", elem), |
| BuiltinSignature { |
| full_name: format!("V6_vprefixq{}", elem), |
| short_name: format!("vprefixq{}", elem), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector], |
| }, |
| ); |
| } |
| |
| // Scatter operations with predicate |
| map.insert( |
| "vscattermhq".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vscattermhq".to_string(), |
| short_name: "vscattermhq".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::I32, |
| RustType::I32, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| map.insert( |
| "vscattermhwq".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vscattermhwq".to_string(), |
| short_name: "vscattermhwq".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::I32, |
| RustType::I32, |
| RustType::HvxVectorPair, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| map.insert( |
| "vscattermwq".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vscattermwq".to_string(), |
| short_name: "vscattermwq".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::I32, |
| RustType::I32, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| // Add with carry saturation |
| map.insert( |
| "vaddcarrysat".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vaddcarrysat".to_string(), |
| short_name: "vaddcarrysat".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| // Gather operations with predicate |
| map.insert( |
| "vgathermhq".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vgathermhq".to_string(), |
| short_name: "vgathermhq".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::MutPtrHvxVector, |
| RustType::HvxVector, |
| RustType::I32, |
| RustType::I32, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| map.insert( |
| "vgathermhwq".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vgathermhwq".to_string(), |
| short_name: "vgathermhwq".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::MutPtrHvxVector, |
| RustType::HvxVector, |
| RustType::I32, |
| RustType::I32, |
| RustType::HvxVectorPair, |
| ], |
| }, |
| ); |
| |
| map.insert( |
| "vgathermwq".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vgathermwq".to_string(), |
| short_name: "vgathermwq".to_string(), |
| return_type: RustType::Unit, |
| param_types: vec![ |
| RustType::MutPtrHvxVector, |
| RustType::HvxVector, |
| RustType::I32, |
| RustType::I32, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| // Basic comparison operations (without accumulation) |
| for elem in ["b", "h", "w", "ub", "uh", "uw"] { |
| // vgt* - greater than |
| map.insert( |
| format!("vgt{}", elem), |
| BuiltinSignature { |
| full_name: format!("V6_vgt{}", elem), |
| short_name: format!("vgt{}", elem), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| // veq* - equal |
| map.insert( |
| format!("veq{}", elem), |
| BuiltinSignature { |
| full_name: format!("V6_veq{}", elem), |
| short_name: format!("veq{}", elem), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| } |
| |
| // Conditional subtraction operations (vsub*q, vsub*nq) |
| for elem in ["b", "h", "w"] { |
| map.insert( |
| format!("vsub{}q", elem), |
| BuiltinSignature { |
| full_name: format!("V6_vsub{}q", elem), |
| short_name: format!("vsub{}q", elem), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| map.insert( |
| format!("vsub{}nq", elem), |
| BuiltinSignature { |
| full_name: format!("V6_vsub{}nq", elem), |
| short_name: format!("vsub{}nq", elem), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| } |
| |
| // vmux - vector mux (select based on predicate) |
| map.insert( |
| "vmux".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vmux".to_string(), |
| short_name: "vmux".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| // vswap - vector swap based on predicate |
| map.insert( |
| "vswap".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vswap".to_string(), |
| short_name: "vswap".to_string(), |
| return_type: RustType::HvxVectorPair, |
| param_types: vec![ |
| RustType::HvxVector, |
| RustType::HvxVector, |
| RustType::HvxVector, |
| ], |
| }, |
| ); |
| |
| // shuffeq operations - take vectors (internal pred representation) and return vector |
| for elem in ["h", "w"] { |
| map.insert( |
| format!("shuffeq{}", elem), |
| BuiltinSignature { |
| full_name: format!("V6_shuffeq{}", elem), |
| short_name: format!("shuffeq{}", elem), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| } |
| |
| // Predicate AND with vector operations |
| map.insert( |
| "vandvqv".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vandvqv".to_string(), |
| short_name: "vandvqv".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| |
| map.insert( |
| "vandvnqv".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vandvnqv".to_string(), |
| short_name: "vandvnqv".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector], |
| }, |
| ); |
| |
| // vandnqrt and vandnqrt_acc |
| map.insert( |
| "vandnqrt".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vandnqrt".to_string(), |
| short_name: "vandnqrt".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::I32], |
| }, |
| ); |
| |
| map.insert( |
| "vandnqrt_acc".to_string(), |
| BuiltinSignature { |
| full_name: "V6_vandnqrt_acc".to_string(), |
| short_name: "vandnqrt_acc".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::HvxVector, RustType::HvxVector, RustType::I32], |
| }, |
| ); |
| |
| // pred_scalar2v2 |
| map.insert( |
| "pred_scalar2v2".to_string(), |
| BuiltinSignature { |
| full_name: "V6_pred_scalar2v2".to_string(), |
| short_name: "pred_scalar2v2".to_string(), |
| return_type: RustType::HvxVector, |
| param_types: vec![RustType::I32], |
| }, |
| ); |
| |
| map |
| } |
| |
| /// Generate extern declarations for all intrinsics for a specific vector mode |
| fn generate_extern_block(intrinsics: &[IntrinsicInfo], mode: VectorMode) -> String { |
| let mut output = String::new(); |
| |
| // Collect unique builtins to avoid duplicates |
| let mut seen_builtins: HashSet<String> = HashSet::new(); |
| let mut decls: Vec<(String, String, RustType, Vec<RustType>)> = Vec::new(); |
| |
| // First, add simple intrinsics |
| for info in intrinsics.iter().filter(|i| !i.is_compound) { |
| if seen_builtins.contains(&info.builtin_name) { |
| continue; |
| } |
| seen_builtins.insert(info.builtin_name.clone()); |
| |
| let param_types: Vec<RustType> = info.params.iter().map(|(_, t)| t.clone()).collect(); |
| decls.push(( |
| info.builtin_name.clone(), |
| info.instr_name.clone(), |
| info.return_type.clone(), |
| param_types, |
| )); |
| } |
| |
| // Then, collect all builtins used in compound expressions |
| let helper_sigs = get_compound_helper_signatures(); |
| let mut compound_builtins: HashSet<String> = HashSet::new(); |
| |
| for info in intrinsics.iter().filter(|i| i.is_compound) { |
| if let Some(ref expr) = info.compound_expr { |
| collect_builtins_from_expr(expr, &mut compound_builtins); |
| } |
| } |
| |
| // Add compound helper builtins |
| let mut missing_builtins = Vec::new(); |
| for builtin_name in compound_builtins { |
| let full_name = format!("V6_{}", builtin_name); |
| if seen_builtins.contains(&full_name) { |
| continue; |
| } |
| seen_builtins.insert(full_name.clone()); |
| |
| if let Some(sig) = helper_sigs.get(&builtin_name) { |
| decls.push(( |
| sig.full_name.clone(), |
| sig.short_name.clone(), |
| sig.return_type.clone(), |
| sig.param_types.clone(), |
| )); |
| } else { |
| missing_builtins.push(builtin_name); |
| } |
| } |
| |
| // Report missing builtins (for development purposes) |
| if !missing_builtins.is_empty() { |
| eprintln!("Warning: Missing helper signatures for compound builtins:"); |
| for name in &missing_builtins { |
| eprintln!(" - {}", name); |
| } |
| } |
| |
| // Sort by builtin name for consistent output |
| decls.sort_by(|a, b| a.0.cmp(&b.0)); |
| |
| // Generate intrinsic declarations for the specified mode |
| output.push_str(&format!( |
| "// LLVM intrinsic declarations for {}-byte vector mode\n", |
| mode.bytes() |
| )); |
| output.push_str("#[allow(improper_ctypes)]\n"); |
| output.push_str("unsafe extern \"unadjusted\" {\n"); |
| |
| for (builtin_name, instr_name, return_type, param_types) in &decls { |
| let base_link = builtin_name.replace('_', "."); |
| // 128-byte mode uses .128B suffix, 64-byte mode doesn't |
| let link_name = if builtin_name.starts_with("V6_") && mode == VectorMode::V128 { |
| format!("llvm.hexagon.{}.128B", base_link) |
| } else { |
| format!("llvm.hexagon.{}", base_link) |
| }; |
| |
| let params_str = if param_types.is_empty() { |
| String::new() |
| } else { |
| param_types |
| .iter() |
| .map(|t| format!("_: {}", t.to_extern_str())) |
| .collect::<Vec<_>>() |
| .join(", ") |
| }; |
| |
| let return_str = if *return_type == RustType::Unit { |
| " -> ()".to_string() |
| } else { |
| format!(" -> {}", return_type.to_extern_str()) |
| }; |
| |
| output.push_str(&format!( |
| " #[link_name = \"{}\"]\n fn {}({}){};\n", |
| link_name, instr_name, params_str, return_str |
| )); |
| } |
| |
| output.push_str("}\n"); |
| output |
| } |
| |
| /// Generate Rust code for a compound expression |
| /// `params` maps parameter names to their types in the function signature |
| /// Get the type of an expression |
| fn get_expr_type( |
| expr: &CompoundExpr, |
| params: &HashMap<String, RustType>, |
| helper_sigs: &HashMap<String, BuiltinSignature>, |
| ) -> Option<RustType> { |
| match expr { |
| CompoundExpr::BuiltinCall(name, _) => { |
| helper_sigs.get(name).map(|sig| sig.return_type.clone()) |
| } |
| CompoundExpr::Param(name) => params.get(name).cloned(), |
| CompoundExpr::IntLiteral(_) => Some(RustType::I32), |
| } |
| } |
| |
| fn generate_compound_expr_code( |
| expr: &CompoundExpr, |
| params: &HashMap<String, RustType>, |
| helper_sigs: &HashMap<String, BuiltinSignature>, |
| ) -> String { |
| match expr { |
| CompoundExpr::BuiltinCall(name, args) => { |
| // Get the expected parameter types for this builtin |
| let expected_types = helper_sigs |
| .get(name) |
| .map(|sig| sig.param_types.clone()) |
| .unwrap_or_default(); |
| |
| let args_code: Vec<String> = args |
| .iter() |
| .enumerate() |
| .map(|(i, arg)| { |
| let arg_code = generate_compound_expr_code(arg, params, helper_sigs); |
| |
| // Check if we need to transmute this argument |
| let expected_type = expected_types.get(i); |
| let actual_type = get_expr_type(arg, params, helper_sigs); |
| |
| // If the builtin expects HvxVector but the arg is HvxVectorPred, transmute |
| if expected_type == Some(&RustType::HvxVector) |
| && actual_type == Some(RustType::HvxVectorPred) |
| { |
| format!( |
| "core::mem::transmute::<HvxVectorPred, HvxVector>({})", |
| arg_code |
| ) |
| } else { |
| arg_code |
| } |
| }) |
| .collect(); |
| format!("{}({})", name, args_code.join(", ")) |
| } |
| CompoundExpr::Param(name) => name.clone(), |
| CompoundExpr::IntLiteral(n) => n.to_string(), |
| } |
| } |
| |
| /// Get the primary instruction name from a compound expression (innermost significant op) |
| fn get_compound_primary_instr(expr: &CompoundExpr) -> Option<String> { |
| match expr { |
| CompoundExpr::BuiltinCall(name, args) => { |
| // For vandqrt wrapper, look inside |
| if name == "vandqrt" && !args.is_empty() { |
| if let Some(inner) = get_compound_primary_instr(&args[0]) { |
| return Some(inner); |
| } |
| } |
| // For store operations, use the store name |
| if name.starts_with("vS32b") { |
| return Some(name.clone()); |
| } |
| // For conditional accumulation, use the add name |
| if name.starts_with("vadd") && (name.ends_with("q") || name.ends_with("nq")) { |
| return Some(name.clone()); |
| } |
| // For predicate operations |
| if name.starts_with("pred_") { |
| return Some(name.clone()); |
| } |
| // For comparison operations with accumulation |
| if (name.starts_with("veq") || name.starts_with("vgt")) |
| && (name.ends_with("_and") || name.ends_with("_or") || name.ends_with("_xor")) |
| { |
| return Some(name.clone()); |
| } |
| Some(name.clone()) |
| } |
| _ => None, |
| } |
| } |
| |
| /// Get override implementations for specific compound intrinsics. |
| /// Some C macros rely on implicit type conversions that don't work with |
| /// our stricter Rust types, so we provide corrected implementations. |
| fn get_compound_overrides() -> HashMap<&'static str, &'static str> { |
| let mut map = HashMap::new(); |
| |
| // Q6_V_vand_QR: takes pred, returns vec |
| // Use transmute to convert pred to vec for LLVM, call vandvrt |
| map.insert( |
| "Q6_V_vand_QR", |
| "vandvrt(core::mem::transmute::<HvxVectorPred, HvxVector>(qu), rt)", |
| ); |
| |
| // Q6_V_vandor_VQR: takes vec and pred, returns vec |
| map.insert( |
| "Q6_V_vandor_VQR", |
| "vandvrt_acc(vx, core::mem::transmute::<HvxVectorPred, HvxVector>(qu), rt)", |
| ); |
| |
| // Q6_Q_vand_VR: takes vec, returns pred |
| map.insert( |
| "Q6_Q_vand_VR", |
| "core::mem::transmute::<HvxVector, HvxVectorPred>(vandqrt(vu, rt))", |
| ); |
| |
| // Q6_Q_vandor_QVR: takes pred and vec, returns pred |
| map.insert( |
| "Q6_Q_vandor_QVR", |
| "core::mem::transmute::<HvxVector, HvxVectorPred>(vandqrt_acc(core::mem::transmute::<HvxVectorPred, HvxVector>(qx), vu, rt))", |
| ); |
| |
| map |
| } |
| |
| /// Generate wrapper functions for all intrinsics |
| fn generate_functions(intrinsics: &[IntrinsicInfo]) -> String { |
| let mut output = String::new(); |
| let simd_mappings = get_simd_intrinsic_mappings(); |
| |
| // Generate simple intrinsics |
| for info in intrinsics.iter().filter(|i| !i.is_compound) { |
| let rust_name = &info.q6_name; |
| |
| // Generate doc comment |
| output.push_str(&format!("/// `{}`\n", info.asm_syntax)); |
| output.push_str("///\n"); |
| output.push_str(&format!("/// Instruction Type: {}\n", info.instr_type)); |
| output.push_str(&format!("/// Execution Slots: {}\n", info.exec_slots)); |
| |
| // Generate attributes |
| output.push_str("#[inline(always)]\n"); |
| output.push_str(&format!( |
| "#[cfg_attr(target_arch = \"hexagon\", target_feature(enable = \"hvxv{}\"))]\n", |
| info.min_arch |
| )); |
| |
| // Check if we should use simd intrinsic instead |
| let use_simd = simd_mappings.get(info.instr_name.as_str()); |
| |
| // assert_instr uses the original instruction name |
| output.push_str(&format!( |
| "#[cfg_attr(test, assert_instr({}))]\n", |
| info.instr_name |
| )); |
| |
| output.push_str(&format!( |
| "#[unstable(feature = \"stdarch_hexagon\", issue = \"{}\")]\n", |
| TRACKING_ISSUE |
| )); |
| |
| // Generate function signature |
| let params_str = info |
| .params |
| .iter() |
| .map(|(name, ty)| format!("{}: {}", name, ty.to_rust_str())) |
| .collect::<Vec<_>>() |
| .join(", "); |
| |
| let return_str = if info.return_type == RustType::Unit { |
| String::new() |
| } else { |
| format!(" -> {}", info.return_type.to_rust_str()) |
| }; |
| |
| output.push_str(&format!( |
| "pub unsafe fn {}({}){} {{\n", |
| rust_name, params_str, return_str |
| )); |
| |
| // Generate function body |
| let args_str = info |
| .params |
| .iter() |
| .map(|(name, _)| name.as_str()) |
| .collect::<Vec<_>>() |
| .join(", "); |
| |
| if let Some(simd_fn) = use_simd { |
| // Use architecture-independent simd intrinsic |
| output.push_str(&format!(" {}({})\n", simd_fn, args_str)); |
| } else { |
| // Use the LLVM intrinsic |
| output.push_str(&format!(" {}({})\n", info.instr_name, args_str)); |
| } |
| |
| output.push_str("}\n\n"); |
| } |
| |
| // Generate compound intrinsics |
| let helper_sigs = get_compound_helper_signatures(); |
| let overrides = get_compound_overrides(); |
| for info in intrinsics.iter().filter(|i| i.is_compound) { |
| if let Some(ref compound_expr) = info.compound_expr { |
| let rust_name = &info.q6_name; |
| |
| // Get the primary instruction for assert_instr |
| let _primary_instr = get_compound_primary_instr(compound_expr) |
| .unwrap_or_else(|| info.instr_name.clone()); |
| |
| // Generate doc comment |
| output.push_str(&format!("/// `{}`\n", info.asm_syntax)); |
| output.push_str("///\n"); |
| output.push_str( |
| "/// This is a compound operation composed of multiple HVX instructions.\n", |
| ); |
| if !info.instr_type.is_empty() { |
| output.push_str(&format!("/// Instruction Type: {}\n", info.instr_type)); |
| } |
| if !info.exec_slots.is_empty() { |
| output.push_str(&format!("/// Execution Slots: {}\n", info.exec_slots)); |
| } |
| |
| // Generate attributes |
| output.push_str("#[inline(always)]\n"); |
| output.push_str(&format!( |
| "#[cfg_attr(target_arch = \"hexagon\", target_feature(enable = \"hvxv{}\"))]\n", |
| info.min_arch |
| )); |
| |
| // For compound ops, we skip assert_instr since they emit multiple instructions |
| // output.push_str(&format!( |
| // "#[cfg_attr(test, assert_instr({}))]\n", |
| // primary_instr |
| // )); |
| |
| output.push_str(&format!( |
| "#[unstable(feature = \"stdarch_hexagon\", issue = \"{}\")]\n", |
| TRACKING_ISSUE |
| )); |
| |
| // Generate function signature |
| let params_str = info |
| .params |
| .iter() |
| .map(|(name, ty)| format!("{}: {}", name, ty.to_rust_str())) |
| .collect::<Vec<_>>() |
| .join(", "); |
| |
| let return_str = if info.return_type == RustType::Unit { |
| String::new() |
| } else { |
| format!(" -> {}", info.return_type.to_rust_str()) |
| }; |
| |
| output.push_str(&format!( |
| "pub unsafe fn {}({}){} {{\n", |
| rust_name, params_str, return_str |
| )); |
| |
| // Check if we have an override for this intrinsic |
| let body = if let Some(override_body) = overrides.get(info.q6_name.as_str()) { |
| override_body.to_string() |
| } else { |
| // Build param type map for expression code generation |
| let param_types: HashMap<String, RustType> = info.params.iter().cloned().collect(); |
| // Generate function body from compound expression |
| let expr_body = |
| generate_compound_expr_code(compound_expr, ¶m_types, &helper_sigs); |
| |
| // Check if we need to transmute the result |
| let expr_return_type = get_expr_type(compound_expr, ¶m_types, &helper_sigs); |
| if info.return_type == RustType::HvxVectorPred |
| && expr_return_type == Some(RustType::HvxVector) |
| { |
| format!( |
| "core::mem::transmute::<HvxVector, HvxVectorPred>({})", |
| expr_body |
| ) |
| } else { |
| expr_body |
| } |
| }; |
| output.push_str(&format!(" {}\n", body)); |
| |
| output.push_str("}\n\n"); |
| } |
| } |
| |
| output |
| } |
| |
| /// Generate a module file for a specific vector mode |
| fn generate_module_file( |
| intrinsics: &[IntrinsicInfo], |
| output_path: &Path, |
| mode: VectorMode, |
| ) -> Result<(), String> { |
| let mut output = |
| File::create(output_path).map_err(|e| format!("Failed to create output: {}", e))?; |
| |
| writeln!(output, "{}", generate_module_doc(mode)).map_err(|e| e.to_string())?; |
| writeln!(output, "{}", generate_types(mode)).map_err(|e| e.to_string())?; |
| writeln!(output, "{}", generate_extern_block(intrinsics, mode)).map_err(|e| e.to_string())?; |
| writeln!(output, "{}", generate_functions(intrinsics)).map_err(|e| e.to_string())?; |
| |
| // Ensure file is flushed before running rustfmt |
| drop(output); |
| |
| // Run rustfmt on the generated file |
| let status = std::process::Command::new("rustfmt") |
| .arg(output_path) |
| .status() |
| .map_err(|e| format!("Failed to run rustfmt: {}", e))?; |
| |
| if !status.success() { |
| return Err("rustfmt failed".to_string()); |
| } |
| |
| Ok(()) |
| } |
| |
| fn main() -> Result<(), String> { |
| println!("=== Hexagon HVX Code Generator ===\n"); |
| |
| // Get the crate directory first (needed for both reading header and writing output) |
| let crate_dir = std::env::var("CARGO_MANIFEST_DIR") |
| .map(std::path::PathBuf::from) |
| .unwrap_or_else(|_| std::env::current_dir().unwrap()); |
| |
| // Read and parse the local LLVM header |
| println!("Step 1: Reading LLVM HVX header..."); |
| let header_content = read_header(&crate_dir)?; |
| println!(" Read {} bytes", header_content.len()); |
| |
| println!("\nStep 2: Parsing intrinsic definitions..."); |
| let all_intrinsics = parse_header(&header_content); |
| println!(" Found {} intrinsic definitions", all_intrinsics.len()); |
| |
| // Filter out intrinsics requiring architecture versions not yet supported by rustc |
| let intrinsics: Vec<_> = all_intrinsics |
| .into_iter() |
| .filter(|i| i.min_arch <= MAX_SUPPORTED_ARCH) |
| .collect(); |
| let filtered_count = intrinsics.len(); |
| println!( |
| " Filtered to {} intrinsics (max supported: hvxv{})", |
| filtered_count, MAX_SUPPORTED_ARCH |
| ); |
| |
| // Count simple vs compound |
| let simple_count = intrinsics.iter().filter(|i| !i.is_compound).count(); |
| let compound_count = intrinsics.iter().filter(|i| i.is_compound).count(); |
| println!(" Simple intrinsics: {}", simple_count); |
| println!(" Compound intrinsics: {}", compound_count); |
| |
| // Print some sample intrinsics for verification |
| println!("\n Sample simple intrinsics:"); |
| for info in intrinsics.iter().filter(|i| !i.is_compound).take(5) { |
| println!( |
| " {} -> {} ({})", |
| info.q6_name, info.builtin_name, info.asm_syntax |
| ); |
| } |
| |
| println!("\n Sample compound intrinsics:"); |
| for info in intrinsics.iter().filter(|i| i.is_compound).take(5) { |
| println!(" {} ({})", info.q6_name, info.asm_syntax); |
| } |
| |
| // Count architecture versions |
| let mut arch_counts: HashMap<u32, usize> = HashMap::new(); |
| for info in &intrinsics { |
| *arch_counts.entry(info.min_arch).or_insert(0) += 1; |
| } |
| println!("\n By architecture version:"); |
| let mut archs: Vec<_> = arch_counts.iter().collect(); |
| archs.sort_by_key(|(k, _)| *k); |
| for (arch, count) in archs { |
| println!(" HVX v{}: {} intrinsics", arch, count); |
| } |
| |
| // Generate output files |
| let hexagon_dir = crate_dir.join("../core_arch/src/hexagon"); |
| |
| // Generate v64.rs (64-byte vector mode) |
| let v64_path = hexagon_dir.join("v64.rs"); |
| println!("\nStep 3: Generating v64.rs (64-byte mode)..."); |
| generate_module_file(&intrinsics, &v64_path, VectorMode::V64)?; |
| println!(" Output: {}", v64_path.display()); |
| |
| // Generate v128.rs (128-byte vector mode) |
| let v128_path = hexagon_dir.join("v128.rs"); |
| println!("\nStep 4: Generating v128.rs (128-byte mode)..."); |
| generate_module_file(&intrinsics, &v128_path, VectorMode::V128)?; |
| println!(" Output: {}", v128_path.display()); |
| |
| println!("\n=== Results ==="); |
| println!( |
| " Generated {} simple wrapper functions per module", |
| simple_count |
| ); |
| println!( |
| " Generated {} compound wrapper functions per module", |
| compound_count |
| ); |
| println!( |
| " Total: {} functions per module", |
| simple_count + compound_count |
| ); |
| println!(" Output files: v64.rs, v128.rs"); |
| |
| Ok(()) |
| } |