//! Run-time feature detection for Aarch64 on Linux.

use super::{auxvec, cpuinfo};
use crate::detect::{bit, cache, Feature};

/// Try to read the features from the auxiliary vector, and if that fails, try
/// to read them from /proc/cpuinfo.
pub(crate) fn detect_features() -> cache::Initializer {
    if let Ok(auxv) = auxvec::auxv() {
        let hwcap: AtHwcap = auxv.into();
        return hwcap.cache();
    }
    if let Ok(c) = cpuinfo::CpuInfo::new() {
        let hwcap: AtHwcap = c.into();
        return hwcap.cache();
    }
    cache::Initializer::default()
}

/// These values are part of the platform-specific [asm/hwcap.h][hwcap] .
///
/// [hwcap]: https://github.com/torvalds/linux/blob/master/arch/arm64/include/uapi/asm/hwcap.h
struct AtHwcap {
    fp: bool,    // 0
    asimd: bool, // 1
    // evtstrm: bool, // 2
    aes: bool,     // 3
    pmull: bool,   // 4
    sha1: bool,    // 5
    sha2: bool,    // 6
    crc32: bool,   // 7
    atomics: bool, // 8
    fphp: bool,    // 9
    asimdhp: bool, // 10
    // cpuid: bool, // 11
    asimdrdm: bool, // 12
    // jscvt: bool, // 13
    // fcma: bool, // 14
    lrcpc: bool, // 15
    // dcpop: bool, // 16
    // sha3: bool, // 17
    // sm3: bool, // 18
    // sm4: bool, // 19
    asimddp: bool, // 20
    // sha512: bool, // 21
    sve: bool, // 22
}

impl From<auxvec::AuxVec> for AtHwcap {
    /// Reads AtHwcap from the auxiliary vector.
    fn from(auxv: auxvec::AuxVec) -> Self {
        AtHwcap {
            fp: bit::test(auxv.hwcap, 0),
            asimd: bit::test(auxv.hwcap, 1),
            // evtstrm: bit::test(auxv.hwcap, 2),
            aes: bit::test(auxv.hwcap, 3),
            pmull: bit::test(auxv.hwcap, 4),
            sha1: bit::test(auxv.hwcap, 5),
            sha2: bit::test(auxv.hwcap, 6),
            crc32: bit::test(auxv.hwcap, 7),
            atomics: bit::test(auxv.hwcap, 8),
            fphp: bit::test(auxv.hwcap, 9),
            asimdhp: bit::test(auxv.hwcap, 10),
            // cpuid: bit::test(auxv.hwcap, 11),
            asimdrdm: bit::test(auxv.hwcap, 12),
            // jscvt: bit::test(auxv.hwcap, 13),
            // fcma: bit::test(auxv.hwcap, 14),
            lrcpc: bit::test(auxv.hwcap, 15),
            // dcpop: bit::test(auxv.hwcap, 16),
            // sha3: bit::test(auxv.hwcap, 17),
            // sm3: bit::test(auxv.hwcap, 18),
            // sm4: bit::test(auxv.hwcap, 19),
            asimddp: bit::test(auxv.hwcap, 20),
            // sha512: bit::test(auxv.hwcap, 21),
            sve: bit::test(auxv.hwcap, 22),
        }
    }
}

impl From<cpuinfo::CpuInfo> for AtHwcap {
    /// Reads AtHwcap from /proc/cpuinfo .
    fn from(c: cpuinfo::CpuInfo) -> Self {
        let f = &c.field("Features");
        AtHwcap {
            // 64-bit names. FIXME: In 32-bit compatibility mode /proc/cpuinfo will
            // map some of the 64-bit names to some 32-bit feature names. This does not
            // cover that yet.
            fp: f.has("fp"),
            asimd: f.has("asimd"),
            // evtstrm: f.has("evtstrm"),
            aes: f.has("aes"),
            pmull: f.has("pmull"),
            sha1: f.has("sha1"),
            sha2: f.has("sha2"),
            crc32: f.has("crc32"),
            atomics: f.has("atomics"),
            fphp: f.has("fphp"),
            asimdhp: f.has("asimdhp"),
            // cpuid: f.has("cpuid"),
            asimdrdm: f.has("asimdrdm"),
            // jscvt: f.has("jscvt"),
            // fcma: f.has("fcma"),
            lrcpc: f.has("lrcpc"),
            // dcpop: f.has("dcpop"),
            // sha3: f.has("sha3"),
            // sm3: f.has("sm3"),
            // sm4: f.has("sm4"),
            asimddp: f.has("asimddp"),
            // sha512: f.has("sha512"),
            sve: f.has("sve"),
        }
    }
}

impl AtHwcap {
    /// Initializes the cache from the feature -bits.
    ///
    /// The features are enabled approximately like in LLVM host feature detection:
    /// https://github.com/llvm-mirror/llvm/blob/master/lib/Support/Host.cpp#L1273
    fn cache(self) -> cache::Initializer {
        let mut value = cache::Initializer::default();
        {
            let mut enable_feature = |f, enable| {
                if enable {
                    value.set(f as u32);
                }
            };

            enable_feature(Feature::fp, self.fp);
            // Half-float support requires float support
            enable_feature(Feature::fp16, self.fp && self.fphp);
            enable_feature(Feature::pmull, self.pmull);
            enable_feature(Feature::crc, self.crc32);
            enable_feature(Feature::lse, self.atomics);
            enable_feature(Feature::rcpc, self.lrcpc);

            // SIMD support requires float support - if half-floats are
            // supported, it also requires half-float support:
            let asimd = self.fp && self.asimd && (!self.fphp | self.asimdhp);
            enable_feature(Feature::asimd, asimd);
            // SIMD extensions require SIMD support:
            enable_feature(Feature::rdm, self.asimdrdm && asimd);
            enable_feature(Feature::dotprod, self.asimddp && asimd);
            enable_feature(Feature::sve, self.sve && asimd);

            // Crypto is specified as AES + PMULL + SHA1 + SHA2 per LLVM/hosts.cpp
            enable_feature(
                Feature::crypto,
                self.aes && self.pmull && self.sha1 && self.sha2,
            );
        }
        value
    }
}
