//! Run-time feature detection for aarch64 on Darwin (macOS/iOS/tvOS/watchOS/visionOS).
//!
//! <https://developer.apple.com/documentation/kernel/1387446-sysctlbyname/determining_instruction_set_characteristics>

use crate::detect::{Feature, cache};
use core::ffi::CStr;

#[inline]
fn _sysctlbyname(name: &CStr) -> bool {
    use libc;

    let mut enabled: i32 = 0;
    let mut enabled_len: usize = 4;
    let enabled_ptr = &mut enabled as *mut i32 as *mut libc::c_void;

    let ret = unsafe {
        libc::sysctlbyname(
            name.as_ptr(),
            enabled_ptr,
            &mut enabled_len,
            core::ptr::null_mut(),
            0,
        )
    };

    match ret {
        0 => enabled != 0,
        _ => false,
    }
}

/// Try to read the features using sysctlbyname.
pub(crate) fn detect_features() -> cache::Initializer {
    let mut value = cache::Initializer::default();

    let mut enable_feature = |f, enable| {
        if enable {
            value.set(f as u32);
        }
    };

    // Armv8.0 features not using the standard identifiers
    let fp = _sysctlbyname(c"hw.optional.floatingpoint");
    let asimd = _sysctlbyname(c"hw.optional.AdvSIMD");
    let crc = _sysctlbyname(c"hw.optional.armv8_crc32");

    // Armv8 and Armv9 features using the standard identifiers
    let aes = _sysctlbyname(c"hw.optional.arm.FEAT_AES");
    let bf16 = _sysctlbyname(c"hw.optional.arm.FEAT_BF16");
    let bti = _sysctlbyname(c"hw.optional.arm.FEAT_BTI");
    let dit = _sysctlbyname(c"hw.optional.arm.FEAT_DIT");
    let dpb = _sysctlbyname(c"hw.optional.arm.FEAT_DPB");
    let dpb2 = _sysctlbyname(c"hw.optional.arm.FEAT_DPB2");
    let dotprod = _sysctlbyname(c"hw.optional.arm.FEAT_DotProd");
    let ecv = _sysctlbyname(c"hw.optional.arm.FEAT_ECV");
    let fcma = _sysctlbyname(c"hw.optional.arm.FEAT_FCMA");
    let fhm = _sysctlbyname(c"hw.optional.arm.FEAT_FHM");
    let fp16 = _sysctlbyname(c"hw.optional.arm.FEAT_FP16");
    let frintts = _sysctlbyname(c"hw.optional.arm.FEAT_FRINTTS");
    let flagm = _sysctlbyname(c"hw.optional.arm.FEAT_FlagM");
    let flagm2 = _sysctlbyname(c"hw.optional.arm.FEAT_FlagM2");
    let i8mm = _sysctlbyname(c"hw.optional.arm.FEAT_I8MM");
    let jsconv = _sysctlbyname(c"hw.optional.arm.FEAT_JSCVT");
    let rcpc = _sysctlbyname(c"hw.optional.arm.FEAT_LRCPC");
    let rcpc2 = _sysctlbyname(c"hw.optional.arm.FEAT_LRCPC2");
    let lse = _sysctlbyname(c"hw.optional.arm.FEAT_LSE");
    let lse2 = _sysctlbyname(c"hw.optional.arm.FEAT_LSE2");
    let pauth = _sysctlbyname(c"hw.optional.arm.FEAT_PAuth");
    let pmull = _sysctlbyname(c"hw.optional.arm.FEAT_PMULL");
    let rdm = _sysctlbyname(c"hw.optional.arm.FEAT_RDM");
    let sb = _sysctlbyname(c"hw.optional.arm.FEAT_SB");
    let sha1 = _sysctlbyname(c"hw.optional.arm.FEAT_SHA1");
    let sha256 = _sysctlbyname(c"hw.optional.arm.FEAT_SHA256");
    let sha3 = _sysctlbyname(c"hw.optional.arm.FEAT_SHA3");
    let sha512 = _sysctlbyname(c"hw.optional.arm.FEAT_SHA512");
    let sme = _sysctlbyname(c"hw.optional.arm.FEAT_SME");
    let sme2 = _sysctlbyname(c"hw.optional.arm.FEAT_SME2");
    let sme_f64f64 = _sysctlbyname(c"hw.optional.arm.FEAT_SME_F64F64");
    let sme_i16i64 = _sysctlbyname(c"hw.optional.arm.FEAT_SME_I16I64");
    let ssbs = _sysctlbyname(c"hw.optional.arm.FEAT_SSBS");
    let wfxt = _sysctlbyname(c"hw.optional.arm.FEAT_WFxT");

    // The following features are not exposed by `is_aarch64_feature_detected`,
    // but *are* reported by `sysctl`. They are here as documentation that they
    // exist, and may potentially be exposed later.
    /*
    let afp = _sysctlbyname(c"hw.optional.arm.FEAT_AFP");
    let csv2 = _sysctlbyname(c"hw.optional.arm.FEAT_CSV2");
    let csv3 = _sysctlbyname(c"hw.optional.arm.FEAT_CSV3");
    let fpac = _sysctlbyname(c"hw.optional.arm.FEAT_FPAC");
    let pauth2 = _sysctlbyname(c"hw.optional.arm.FEAT_PAuth2");
    let rpres = _sysctlbyname(c"hw.optional.arm.FEAT_RPRES");
    let specres = _sysctlbyname(c"hw.optional.arm.FEAT_SPECRES");
     */

    // The following "features" are reported by `sysctl` but are mandatory parts
    // of SME or SME2, and so are not exposed separately by
    // `is_aarch64_feature_detected`.  They are here to document their
    // existence, in case they're needed in the future.
    /*
    let sme_b16f32 = _sysctlbyname(c"hw.optional.arm.SME_B16F32");
    let sme_bi32i32 = _sysctlbyname(c"hw.optional.arm.SME_BI32I32");
    let sme_f16f32 = _sysctlbyname(c"hw.optional.arm.SME_F16F32");
    let sme_f32f32 = _sysctlbyname(c"hw.optional.arm.SME_F32F32");
    let sme_i16i32 = _sysctlbyname(c"hw.optional.arm.SME_I16I32");
    let sme_i8i32 = _sysctlbyname(c"hw.optional.arm.SME_I8I32");
     */

    enable_feature(Feature::aes, aes && pmull);
    enable_feature(Feature::asimd, asimd);
    enable_feature(Feature::bf16, bf16);
    enable_feature(Feature::bti, bti);
    enable_feature(Feature::crc, crc);
    enable_feature(Feature::dit, dit);
    enable_feature(Feature::dotprod, dotprod);
    enable_feature(Feature::dpb, dpb);
    enable_feature(Feature::dpb2, dpb2);
    enable_feature(Feature::ecv, ecv);
    enable_feature(Feature::fcma, fcma);
    enable_feature(Feature::fhm, fhm);
    enable_feature(Feature::flagm, flagm);
    enable_feature(Feature::flagm2, flagm2);
    enable_feature(Feature::fp, fp);
    enable_feature(Feature::fp16, fp16);
    enable_feature(Feature::frintts, frintts);
    enable_feature(Feature::i8mm, i8mm);
    enable_feature(Feature::jsconv, jsconv);
    enable_feature(Feature::lse, lse);
    enable_feature(Feature::lse2, lse2);
    enable_feature(Feature::paca, pauth);
    enable_feature(Feature::pacg, pauth);
    enable_feature(Feature::pmull, aes && pmull);
    enable_feature(Feature::rcpc, rcpc);
    enable_feature(Feature::rcpc2, rcpc2);
    enable_feature(Feature::rdm, rdm);
    enable_feature(Feature::sb, sb);
    enable_feature(Feature::sha2, sha1 && sha256 && asimd);
    enable_feature(Feature::sha3, sha512 && sha3 && asimd);
    enable_feature(Feature::sme, sme);
    enable_feature(Feature::sme2, sme2);
    enable_feature(Feature::sme_f64f64, sme_f64f64);
    enable_feature(Feature::sme_i16i64, sme_i16i64);
    enable_feature(Feature::ssbs, ssbs);
    enable_feature(Feature::wfxt, wfxt);

    value
}
