llvm-native-core 0.1.9

LLVM-native core semantic engine — IR, CodeGen, X86 MC, Clang frontend pipeline
//! AMDGPU Target Machine — subtarget features, ISA version detection,
//! and feature flags for GCN/RDNA architectures.
//!
//! Clean-room reconstruction from AMD ROCm and GFX ISA documentation.
//! Zero LLVM source code consultation.

use std::collections::HashMap;

// ═══════════════════════════════════════════════════════════════════════════
// ISA Version / GFX Level
// ═══════════════════════════════════════════════════════════════════════════

/// AMDGPU ISA version (GFX level).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum AmdgpuIsaVersion {
    /// GCN 1.0 — Southern Islands (Tahiti, Pitcairn, Cape Verde)
    GFX600,
    /// GCN 1.1 — Sea Islands (Bonaire, Hawaii)
    GFX700,
    /// GCN 1.2 — Volcanic Islands (Tonga, Fiji)
    GFX800,
    /// GCN 3 — Fiji, Polaris (GFX803)
    GFX803,
    /// GCN 5 — Vega 10 (GFX900)
    GFX900,
    /// GCN 5.1 — Vega 12 / Vega 20 (GFX906), Raven
    GFX906,
    /// GCN 5.2 — Vega 20 (GFX907), Arcturus (GFX908)
    GFX908,
    /// GCN 5.3 — Aldebaran (GFX90A)
    GFX90A,
    /// RDNA 1 — Navi 10 (GFX1010)
    GFX1010,
    /// RDNA 2 — Navi 21 (GFX1030), Van Gogh (GFX1031), etc.
    GFX1030,
    /// RDNA 3 — GFX1100
    GFX1100,
    /// RDNA 3.5 — GFX1150
    GFX1150,
}

impl AmdgpuIsaVersion {
    /// Parse a GFX version string like "gfx900" or "gfx1030".
    pub fn from_str(s: &str) -> Option<Self> {
        match s.to_lowercase().as_str() {
            "gfx600" => Some(Self::GFX600),
            "gfx700" => Some(Self::GFX700),
            "gfx800" | "gfx802" => Some(Self::GFX800),
            "gfx803" => Some(Self::GFX803),
            "gfx900" => Some(Self::GFX900),
            "gfx906" => Some(Self::GFX906),
            "gfx908" => Some(Self::GFX908),
            "gfx90a" => Some(Self::GFX90A),
            "gfx1010" => Some(Self::GFX1010),
            "gfx1030" => Some(Self::GFX1030),
            "gfx1100" => Some(Self::GFX1100),
            "gfx1150" => Some(Self::GFX1150),
            _ => None,
        }
    }

    /// String representation of this ISA version.
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::GFX600 => "gfx600",
            Self::GFX700 => "gfx700",
            Self::GFX800 => "gfx802",
            Self::GFX803 => "gfx803",
            Self::GFX900 => "gfx900",
            Self::GFX906 => "gfx906",
            Self::GFX908 => "gfx908",
            Self::GFX90A => "gfx90a",
            Self::GFX1010 => "gfx1010",
            Self::GFX1030 => "gfx1030",
            Self::GFX1100 => "gfx1100",
            Self::GFX1150 => "gfx1150",
        }
    }

    /// Major architecture generation.
    pub fn generation(&self) -> AmdgpuGeneration {
        match self {
            Self::GFX600 | Self::GFX700 => AmdgpuGeneration::GCN1,
            Self::GFX800 | Self::GFX803 => AmdgpuGeneration::GCN2,
            Self::GFX900 | Self::GFX906 | Self::GFX908 | Self::GFX90A => AmdgpuGeneration::GCN5,
            Self::GFX1010 | Self::GFX1030 => AmdgpuGeneration::RDNA1,
            Self::GFX1100 | Self::GFX1150 => AmdgpuGeneration::RDNA3,
        }
    }

    /// Whether this ISA supports wave32 mode.
    pub fn supports_wave32(&self) -> bool {
        self.generation() >= AmdgpuGeneration::RDNA1
    }

    /// Whether this ISA has AGPRs (accumulation registers).
    pub fn has_agprs(&self) -> bool {
        *self >= Self::GFX908
    }

    /// Whether this ISA supports packed FP16 math (VOP3P).
    pub fn has_packed_fp16(&self) -> bool {
        *self >= Self::GFX900
    }

    /// Max number of SGPRs available.
    pub fn max_sgprs(&self) -> u32 {
        match self {
            Self::GFX600 | Self::GFX700 => 104,
            Self::GFX800 | Self::GFX803 => 104,
            _ => 104,
        }
    }

    /// Max number of VGPRs available.
    pub fn max_vgprs(&self) -> u32 {
        if self.supports_wave32() {
            256
        } else {
            256
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// Architecture Generation
// ═══════════════════════════════════════════════════════════════════════════

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum AmdgpuGeneration {
    GCN1,
    GCN2,
    GCN3,
    GCN5,
    RDNA1,
    RDNA2,
    RDNA3,
}

// ═══════════════════════════════════════════════════════════════════════════
// Subtarget Features
// ═══════════════════════════════════════════════════════════════════════════

/// AMDGPU subtarget feature flags.
#[derive(Debug, Clone)]
pub struct AmdgpuSubtargetFeatures {
    /// ISA version.
    pub isa_version: AmdgpuIsaVersion,
    /// Feature flags.
    pub features: HashMap<String, bool>,
}

impl AmdgpuSubtargetFeatures {
    pub fn new(isa: AmdgpuIsaVersion) -> Self {
        let mut features = HashMap::new();
        // Set defaults based on ISA
        features.insert("s-memrealtime".into(), true);
        features.insert("s-memtime-inst".into(), isa >= AmdgpuIsaVersion::GFX800);
        features.insert("flat-for-global".into(), isa >= AmdgpuIsaVersion::GFX700);
        features.insert("flat-scratch".into(), isa >= AmdgpuIsaVersion::GFX803);
        features.insert("xnack".into(), false);
        features.insert("sram-ecc".into(), false);
        features.insert("wavefrontsize64".into(), !isa.supports_wave32());
        features.insert("wavefrontsize32".into(), isa.supports_wave32());
        features.insert("cumode".into(), false);
        features.insert(
            "architected-flat-scratch".into(),
            isa >= AmdgpuIsaVersion::GFX1010,
        );
        features.insert("packed-fp16".into(), isa.has_packed_fp16());
        features.insert("agprs".into(), isa.has_agprs());
        features.insert("dot2-insts".into(), isa >= AmdgpuIsaVersion::GFX906);
        features.insert("dot7-insts".into(), isa >= AmdgpuIsaVersion::GFX1010);
        features.insert("dot8-insts".into(), isa >= AmdgpuIsaVersion::GFX1030);

        Self {
            isa_version: isa,
            features,
        }
    }

    /// Check if a feature is enabled.
    pub fn has(&self, feature: &str) -> bool {
        self.features.get(feature).copied().unwrap_or(false)
    }

    /// Set a feature.
    pub fn set(&mut self, feature: &str, enabled: bool) {
        self.features.insert(feature.to_string(), enabled);
    }

    /// Get the default wavefront size.
    pub fn wavefront_size(&self) -> u32 {
        if self.has("wavefrontsize32") {
            32
        } else {
            64
        }
    }

    /// Get the triple string for this target.
    pub fn triple_string(&self) -> String {
        format!("amdgcn-amd-amdhsa-{}", self.isa_version.as_str())
    }

    /// Parse a feature string like "+sram-ecc,-xnack".
    pub fn parse_feature_string(&mut self, s: &str) {
        for part in s.split(',') {
            let part = part.trim();
            if part.is_empty() {
                continue;
            }
            if let Some(feat) = part.strip_prefix('+') {
                self.features.insert(feat.to_string(), true);
            } else if let Some(feat) = part.strip_prefix('-') {
                self.features.insert(feat.to_string(), false);
            }
        }
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// AMDGPU Target Machine
// ═══════════════════════════════════════════════════════════════════════════

/// Complete AMDGPU target machine definition.
#[derive(Debug, Clone)]
pub struct AmdgpuTargetMachine {
    pub isa_version: AmdgpuIsaVersion,
    pub features: AmdgpuSubtargetFeatures,
    pub cpu: String,
    pub triple: String,
}

impl AmdgpuTargetMachine {
    pub fn new(isa: AmdgpuIsaVersion) -> Self {
        let features = AmdgpuSubtargetFeatures::new(isa);
        let triple = features.triple_string();
        Self {
            isa_version: isa,
            features,
            cpu: isa.as_str().to_string(),
            triple,
        }
    }

    /// Create from a CPU name string like "gfx900".
    pub fn from_cpu(cpu: &str) -> Option<Self> {
        AmdgpuIsaVersion::from_str(cpu).map(Self::new)
    }

    /// Get the feature string for LLVM IR attributes.
    pub fn feature_string(&self) -> String {
        let mut parts = Vec::new();
        for (feat, enabled) in &self.features.features {
            if *enabled {
                parts.push(format!("+{}", feat));
            } else {
                parts.push(format!("-{}", feat));
            }
        }
        parts.join(",")
    }

    pub fn is_gcn(&self) -> bool {
        matches!(
            self.isa_version.generation(),
            AmdgpuGeneration::GCN1
                | AmdgpuGeneration::GCN2
                | AmdgpuGeneration::GCN3
                | AmdgpuGeneration::GCN5
        )
    }

    pub fn is_rdna(&self) -> bool {
        matches!(
            self.isa_version.generation(),
            AmdgpuGeneration::RDNA1 | AmdgpuGeneration::RDNA2 | AmdgpuGeneration::RDNA3
        )
    }
}

// ═══════════════════════════════════════════════════════════════════════════
// Tests
// ═══════════════════════════════════════════════════════════════════════════

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_isa_parse_gfx900() {
        let isa = AmdgpuIsaVersion::from_str("gfx900").unwrap();
        assert_eq!(isa, AmdgpuIsaVersion::GFX900);
        assert_eq!(isa.as_str(), "gfx900");
    }

    #[test]
    fn test_isa_parse_gfx1030() {
        let isa = AmdgpuIsaVersion::from_str("gfx1030").unwrap();
        assert_eq!(isa, AmdgpuIsaVersion::GFX1030);
        assert!(isa.supports_wave32());
    }

    #[test]
    fn test_isa_generation() {
        assert_eq!(
            AmdgpuIsaVersion::GFX900.generation(),
            AmdgpuGeneration::GCN5
        );
        assert_eq!(
            AmdgpuIsaVersion::GFX1010.generation(),
            AmdgpuGeneration::RDNA1
        );
    }

    #[test]
    fn test_subtarget_features_defaults() {
        let feats = AmdgpuSubtargetFeatures::new(AmdgpuIsaVersion::GFX900);
        assert!(feats.has("packed-fp16"));
        assert!(!feats.has("xnack"));
        assert_eq!(feats.wavefront_size(), 64);
    }

    #[test]
    fn test_subtarget_features_wave32() {
        let feats = AmdgpuSubtargetFeatures::new(AmdgpuIsaVersion::GFX1010);
        assert!(feats.has("wavefrontsize32"));
        assert!(feats.has("architected-flat-scratch"));
    }

    #[test]
    fn test_subtarget_parse_feature_string() {
        let mut feats = AmdgpuSubtargetFeatures::new(AmdgpuIsaVersion::GFX900);
        feats.parse_feature_string("+xnack,-packed-fp16");
        assert!(feats.has("xnack"));
        assert!(!feats.has("packed-fp16"));
    }

    #[test]
    fn test_target_machine_create() {
        let tm = AmdgpuTargetMachine::new(AmdgpuIsaVersion::GFX900);
        assert!(tm.is_gcn());
        assert!(!tm.is_rdna());
        assert!(tm.triple.contains("amdgcn"));
    }

    #[test]
    fn test_target_machine_from_cpu() {
        let tm = AmdgpuTargetMachine::from_cpu("gfx1010").unwrap();
        assert!(tm.is_rdna());
    }
}