use self::Suffix::*;
use self::TargetFeature::*;
use std::env;
use std::fmt::{self, Write as _};
use std::fs::File;
use std::io::prelude::*;
use std::io::{self, BufReader};
use std::path::PathBuf;

const IN: &str = "neon.spec";
const ARM_OUT: &str = "generated.rs";
const AARCH64_OUT: &str = "generated.rs";

const UINT_TYPES: [&str; 6] = [
    "uint8x8_t",
    "uint8x16_t",
    "uint16x4_t",
    "uint16x8_t",
    "uint32x2_t",
    "uint32x4_t",
];

const UINT_TYPES_64: [&str; 2] = ["uint64x1_t", "uint64x2_t"];

const INT_TYPES: [&str; 6] = [
    "int8x8_t",
    "int8x16_t",
    "int16x4_t",
    "int16x8_t",
    "int32x2_t",
    "int32x4_t",
];

const INT_TYPES_64: [&str; 2] = ["int64x1_t", "int64x2_t"];

const FLOAT_TYPES: [&str; 2] = [
    //"float8x8_t", not supported by rust
    //"float8x16_t", not supported by rust
    //"float16x4_t", not supported by rust
    //"float16x8_t", not supported by rust
    "float32x2_t",
    "float32x4_t",
];

const FLOAT_TYPES_64: [&str; 2] = [
    //"float8x8_t", not supported by rust
    //"float8x16_t", not supported by rust
    //"float16x4_t", not supported by rust
    //"float16x8_t", not supported by rust
    "float64x1_t",
    "float64x2_t",
];

fn type_len(t: &str) -> usize {
    let s: Vec<_> = t.split('x').collect();
    if s.len() == 2 {
        match &s[1][0..2] {
            "1_" => 1,
            "2_" => 2,
            "4_" => 4,
            "8_" => 8,
            "16" => 16,
            _ => panic!("unknown type: {t}"),
        }
    } else if s.len() == 3 {
        s[1].parse::<usize>().unwrap() * type_sub_len(t)
    } else {
        1
    }
}

fn type_sub_len(t: &str) -> usize {
    let s: Vec<_> = t.split('x').collect();
    if s.len() != 3 {
        1
    } else {
        match s[2] {
            "2_t" => 2,
            "3_t" => 3,
            "4_t" => 4,
            _ => panic!("unknown type len: {t}"),
        }
    }
}

fn type_bits(t: &str) -> usize {
    match t {
        "int8x8_t" | "int8x16_t" | "uint8x8_t" | "uint8x16_t" | "poly8x8_t" | "poly8x16_t"
        | "i8" | "u8" => 8,
        "int16x4_t" | "int16x8_t" | "uint16x4_t" | "uint16x8_t" | "poly16x4_t" | "poly16x8_t"
        | "i16" | "u16" => 16,
        "int32x2_t" | "int32x4_t" | "uint32x2_t" | "uint32x4_t" | "i32" | "u32" | "float32x2_t"
        | "float32x4_t" | "f32" => 32,
        "int64x1_t" | "int64x2_t" | "uint64x1_t" | "uint64x2_t" | "poly64x1_t" | "poly64x2_t"
        | "i64" | "u64" | "float64x1_t" | "float64x2_t" | "f64" => 64,
        _ => panic!("unknown type: {t}"),
    }
}

fn type_exp_len(t: &str, base_len: usize) -> usize {
    let t = type_to_sub_type(t);
    let len = type_len(&t) / base_len;
    match len {
        1 => 0,
        2 => 1,
        4 => 2,
        8 => 3,
        16 => 4,
        _ => panic!("unknown type: {t}"),
    }
}

fn type_bits_exp_len(t: &str) -> usize {
    match t {
        "int8x8_t" | "int8x16_t" | "uint8x8_t" | "uint8x16_t" | "poly8x8_t" | "poly8x16_t"
        | "i8" | "u8" => 3,
        "int16x4_t" | "int16x8_t" | "uint16x4_t" | "uint16x8_t" | "poly16x4_t" | "poly16x8_t"
        | "i16" | "u16" => 4,
        "int32x2_t" | "int32x4_t" | "uint32x2_t" | "uint32x4_t" | "i32" | "u32" => 5,
        "int64x1_t" | "int64x2_t" | "uint64x1_t" | "uint64x2_t" | "poly64x1_t" | "poly64x2_t"
        | "i64" | "u64" => 6,
        _ => panic!("unknown type: {t}"),
    }
}

fn type_to_suffix(t: &str) -> &str {
    match t {
        "int8x8_t" => "_s8",
        "int8x16_t" => "q_s8",
        "int16x4_t" => "_s16",
        "int16x8_t" => "q_s16",
        "int32x2_t" => "_s32",
        "int32x4_t" => "q_s32",
        "int64x1_t" => "_s64",
        "int64x2_t" => "q_s64",
        "uint8x8_t" => "_u8",
        "uint8x16_t" => "q_u8",
        "uint16x4_t" => "_u16",
        "uint16x8_t" => "q_u16",
        "uint32x2_t" => "_u32",
        "uint32x4_t" => "q_u32",
        "uint64x1_t" => "_u64",
        "uint64x2_t" => "q_u64",
        "float16x4_t" => "_f16",
        "float16x8_t" => "q_f16",
        "float32x2_t" => "_f32",
        "float32x4_t" => "q_f32",
        "float64x1_t" => "_f64",
        "float64x2_t" => "q_f64",
        "poly8x8_t" => "_p8",
        "poly8x16_t" => "q_p8",
        "poly16x4_t" => "_p16",
        "poly16x8_t" => "q_p16",
        "poly64x1_t" => "_p64",
        "poly64x2_t" => "q_p64",
        "int8x8x2_t" => "_s8_x2",
        "int8x8x3_t" => "_s8_x3",
        "int8x8x4_t" => "_s8_x4",
        "int16x4x2_t" => "_s16_x2",
        "int16x4x3_t" => "_s16_x3",
        "int16x4x4_t" => "_s16_x4",
        "int32x2x2_t" => "_s32_x2",
        "int32x2x3_t" => "_s32_x3",
        "int32x2x4_t" => "_s32_x4",
        "int64x1x2_t" => "_s64_x2",
        "int64x1x3_t" => "_s64_x3",
        "int64x1x4_t" => "_s64_x4",
        "uint8x8x2_t" => "_u8_x2",
        "uint8x8x3_t" => "_u8_x3",
        "uint8x8x4_t" => "_u8_x4",
        "uint16x4x2_t" => "_u16_x2",
        "uint16x4x3_t" => "_u16_x3",
        "uint16x4x4_t" => "_u16_x4",
        "uint32x2x2_t" => "_u32_x2",
        "uint32x2x3_t" => "_u32_x3",
        "uint32x2x4_t" => "_u32_x4",
        "uint64x1x2_t" => "_u64_x2",
        "uint64x1x3_t" => "_u64_x3",
        "uint64x1x4_t" => "_u64_x4",
        "poly8x8x2_t" => "_p8_x2",
        "poly8x8x3_t" => "_p8_x3",
        "poly8x8x4_t" => "_p8_x4",
        "poly16x4x2_t" => "_p16_x2",
        "poly16x4x3_t" => "_p16_x3",
        "poly16x4x4_t" => "_p16_x4",
        "poly64x1x2_t" => "_p64_x2",
        "poly64x1x3_t" => "_p64_x3",
        "poly64x1x4_t" => "_p64_x4",
        "float32x2x2_t" => "_f32_x2",
        "float32x2x3_t" => "_f32_x3",
        "float32x2x4_t" => "_f32_x4",
        "float64x1x2_t" => "_f64_x2",
        "float64x1x3_t" => "_f64_x3",
        "float64x1x4_t" => "_f64_x4",
        "int8x16x2_t" => "q_s8_x2",
        "int8x16x3_t" => "q_s8_x3",
        "int8x16x4_t" => "q_s8_x4",
        "int16x8x2_t" => "q_s16_x2",
        "int16x8x3_t" => "q_s16_x3",
        "int16x8x4_t" => "q_s16_x4",
        "int32x4x2_t" => "q_s32_x2",
        "int32x4x3_t" => "q_s32_x3",
        "int32x4x4_t" => "q_s32_x4",
        "int64x2x2_t" => "q_s64_x2",
        "int64x2x3_t" => "q_s64_x3",
        "int64x2x4_t" => "q_s64_x4",
        "uint8x16x2_t" => "q_u8_x2",
        "uint8x16x3_t" => "q_u8_x3",
        "uint8x16x4_t" => "q_u8_x4",
        "uint16x8x2_t" => "q_u16_x2",
        "uint16x8x3_t" => "q_u16_x3",
        "uint16x8x4_t" => "q_u16_x4",
        "uint32x4x2_t" => "q_u32_x2",
        "uint32x4x3_t" => "q_u32_x3",
        "uint32x4x4_t" => "q_u32_x4",
        "uint64x2x2_t" => "q_u64_x2",
        "uint64x2x3_t" => "q_u64_x3",
        "uint64x2x4_t" => "q_u64_x4",
        "poly8x16x2_t" => "q_p8_x2",
        "poly8x16x3_t" => "q_p8_x3",
        "poly8x16x4_t" => "q_p8_x4",
        "poly16x8x2_t" => "q_p16_x2",
        "poly16x8x3_t" => "q_p16_x3",
        "poly16x8x4_t" => "q_p16_x4",
        "poly64x2x2_t" => "q_p64_x2",
        "poly64x2x3_t" => "q_p64_x3",
        "poly64x2x4_t" => "q_p64_x4",
        "float32x4x2_t" => "q_f32_x2",
        "float32x4x3_t" => "q_f32_x3",
        "float32x4x4_t" => "q_f32_x4",
        "float64x2x2_t" => "q_f64_x2",
        "float64x2x3_t" => "q_f64_x3",
        "float64x2x4_t" => "q_f64_x4",
        "i8" => "b_s8",
        "i16" => "h_s16",
        "i32" => "s_s32",
        "i64" => "d_s64",
        "u8" => "b_u8",
        "u16" => "h_u16",
        "u32" => "s_u32",
        "u64" => "d_u64",
        "f32" => "s_f32",
        "f64" => "d_f64",
        "p8" => "b_p8",
        "p16" => "h_p16",
        "p128" => "q_p128",
        _ => panic!("unknown type: {t}"),
    }
}

fn type_to_dup_suffix(t: &str) -> String {
    let s: Vec<_> = type_to_suffix(t).split('_').collect();
    assert_eq!(s.len(), 2);
    format!("{}_dup_{}", s[0], s[1])
}

fn type_to_lane_suffix(t: &str) -> String {
    let s: Vec<_> = type_to_suffix(t).split('_').collect();
    assert_eq!(s.len(), 2);
    format!("{}_lane_{}", s[0], s[1])
}

fn type_to_n_suffix(t: &str) -> &str {
    match t {
        "int8x8_t" => "_n_s8",
        "int8x16_t" => "q_n_s8",
        "int16x4_t" => "_n_s16",
        "int16x8_t" => "q_n_s16",
        "int32x2_t" => "_n_s32",
        "int32x4_t" => "q_n_s32",
        "int64x1_t" => "_n_s64",
        "int64x2_t" => "q_n_s64",
        "uint8x8_t" => "_n_u8",
        "uint8x16_t" => "q_n_u8",
        "uint16x4_t" => "_n_u16",
        "uint16x8_t" => "q_n_u16",
        "uint32x2_t" => "_n_u32",
        "uint32x4_t" => "q_n_u32",
        "uint64x1_t" => "_n_u64",
        "uint64x2_t" => "q_n_u64",
        "float16x4_t" => "_n_f16",
        "float16x8_t" => "q_n_f16",
        "float32x2_t" => "_n_f32",
        "float32x4_t" => "q_n_f32",
        "float64x1_t" => "_n_f64",
        "float64x2_t" => "q_n_f64",
        "poly8x8_t" => "_n_p8",
        "poly8x16_t" => "q_n_p8",
        "poly16x4_t" => "_n_p16",
        "poly16x8_t" => "q_n_p16",
        "poly64x1_t" => "_n_p64",
        "poly64x2_t" => "q_n_p64",
        "i8" => "b_n_s8",
        "i16" => "h_n_s16",
        "i32" => "s_n_s32",
        "i64" => "d_n_s64",
        "u8" => "b_n_u8",
        "u16" => "h_n_u16",
        "u32" => "s_n_u32",
        "u64" => "d_n_u64",
        _ => panic!("unknown type: {t}"),
    }
}

fn type_to_noq_n_suffix(t: &str) -> &str {
    match t {
        "int8x8_t" | "int8x16_t" => "_n_s8",
        "int16x4_t" | "int16x8_t" => "_n_s16",
        "int32x2_t" | "int32x4_t" => "_n_s32",
        "int64x1_t" | "int64x2_t" => "_n_s64",
        "uint8x8_t" | "uint8x16_t" => "_n_u8",
        "uint16x4_t" | "uint16x8_t" => "_n_u16",
        "uint32x2_t" | "uint32x4_t" => "_n_u32",
        "uint64x1_t" | "uint64x2_t" => "_n_u64",
        "float16x4_t" | "float16x8_t" => "_n_f16",
        "float32x2_t" | "float32x4_t" => "_n_f32",
        "float64x1_t" | "float64x2_t" => "_n_f64",
        "poly8x8_t" | "poly8x16_t" => "_n_p8",
        "poly16x4_t" | "poly16x8_t" => "_n_p16",
        "poly64x1_t" | "poly64x2_t" => "_n_p64",
        "i8" => "b_n_s8",
        "i16" => "h_n_s16",
        "i32" => "s_n_s32",
        "i64" => "d_n_s64",
        "u8" => "b_n_u8",
        "u16" => "h_n_u16",
        "u32" => "s_n_u32",
        "u64" => "d_n_u64",
        _ => panic!("unknown type: {t}"),
    }
}

fn type_to_lane_suffixes<'a>(out_t: &'a str, in_t: &'a str, re_to_out: bool) -> String {
    let mut str = String::new();
    let suf = type_to_suffix(out_t);
    if !suf.starts_with('_') {
        str.push_str(&suf[0..1]);
    }
    str.push_str("_lane");
    if !re_to_out {
        str.push_str(type_to_suffix(in_t));
    } else {
        if type_to_suffix(in_t).starts_with('q') {
            str.push('q');
        };
        let suf2 = type_to_noq_suffix(out_t);
        str.push_str(suf2);
    }
    str
}

fn type_to_rot_suffix(c_name: &str, suf: &str) -> String {
    let ns: Vec<_> = c_name.split('_').collect();
    assert_eq!(ns.len(), 2);
    if let Some(suf) = suf.strip_prefix('q') {
        format!("{}q_{}{}", ns[0], ns[1], suf)
    } else {
        format!("{c_name}{suf}")
    }
}

fn type_to_signed(t: &str) -> String {
    let s = t.replace("uint", "int");
    let s = s.replace("poly", "int");
    s
}

fn type_to_unsigned(t: &str) -> String {
    if t.contains("uint") {
        return t.to_string();
    }
    let s = t.replace("int", "uint");
    let s = s.replace("poly", "uint");
    s
}

fn type_to_double_suffixes<'a>(out_t: &'a str, in_t: &'a str) -> String {
    let mut str = String::new();
    let suf = type_to_suffix(in_t);
    if suf.starts_with('q') && type_to_suffix(out_t).starts_with('q') {
        str.push('q');
    }
    if !suf.starts_with('_') && !suf.starts_with('q') {
        str.push_str(&suf[0..1]);
    }
    str.push_str(type_to_noq_suffix(out_t));
    str.push_str(type_to_noq_suffix(in_t));
    str
}

fn type_to_double_n_suffixes<'a>(out_t: &'a str, in_t: &'a str) -> String {
    let mut str = String::new();
    let suf = type_to_suffix(in_t);
    if suf.starts_with('q') && type_to_suffix(out_t).starts_with('q') {
        str.push('q');
    }
    if !suf.starts_with('_') && !suf.starts_with('q') {
        str.push_str(&suf[0..1]);
    }
    str.push_str("_n");
    str.push_str(type_to_noq_suffix(out_t));
    str.push_str(type_to_noq_suffix(in_t));
    str
}

fn type_to_noq_double_suffixes<'a>(out_t: &'a str, in_t: &'a str) -> String {
    let mut str = String::new();
    str.push_str(type_to_noq_suffix(out_t));
    str.push_str(type_to_noq_suffix(in_t));
    str
}

fn type_to_noq_suffix(t: &str) -> &str {
    match t {
        "int8x8_t" | "int8x16_t" | "i8" => "_s8",
        "int16x4_t" | "int16x8_t" | "i16" => "_s16",
        "int32x2_t" | "int32x4_t" | "i32" => "_s32",
        "int64x1_t" | "int64x2_t" | "i64" => "_s64",
        "uint8x8_t" | "uint8x16_t" | "u8" => "_u8",
        "uint16x4_t" | "uint16x8_t" | "u16" => "_u16",
        "uint32x2_t" | "uint32x4_t" | "u32" => "_u32",
        "uint64x1_t" | "uint64x2_t" | "u64" => "_u64",
        "float16x4_t" | "float16x8_t" => "_f16",
        "float32x2_t" | "float32x4_t" | "f32" => "_f32",
        "float64x1_t" | "float64x2_t" | "f64" => "_f64",
        "poly8x8_t" | "poly8x16_t" => "_p8",
        "poly16x4_t" | "poly16x8_t" => "_p16",
        "poly64x1_t" | "poly64x2_t" | "p64" => "_p64",
        "p128" => "_p128",
        _ => panic!("unknown type: {t}"),
    }
}

#[derive(Clone, Copy)]
enum Suffix {
    Normal,
    Double,
    NoQ,
    NoQDouble,
    NSuffix,
    DoubleN,
    NoQNSuffix,
    OutSuffix,
    OutNSuffix,
    OutNox,
    In1Nox,
    OutDupNox,
    OutLaneNox,
    In1LaneNox,
    Lane,
    In2,
    In2Lane,
    OutLane,
    Rot,
    RotLane,
}

#[derive(Clone, Copy)]
enum TargetFeature {
    Default,
    ArmV7,
    Vfp4,
    FPArmV8,
    AES,
    FCMA,
    Dotprod,
    I8MM,
    SHA3,
    RDM,
    SM4,
    FTTS,
}

impl TargetFeature {
    /// A string for use with `#[target_feature(...)]`.
    fn as_target_feature_arg_aarch64(&self) -> &str {
        match *self {
            // Features included with AArch64 NEON.
            Self::Default => "neon",
            Self::ArmV7 => "neon",
            Self::Vfp4 => "neon",
            Self::FPArmV8 => "neon",
            // Optional features.
            Self::AES => "neon,aes",
            Self::FCMA => "neon,fcma",
            Self::Dotprod => "neon,dotprod",
            Self::I8MM => "neon,i8mm",
            Self::SHA3 => "neon,sha3",
            Self::RDM => "rdm",
            Self::SM4 => "neon,sm4",
            Self::FTTS => "neon,frintts",
        }
    }

    /// A string for use with #[simd_test(...)] (or `is_aarch64_feature_detected!(...)`).
    fn as_simd_test_arg_aarch64(&self) -> &str {
        self.as_target_feature_arg_aarch64()
    }

    /// A string for use with `#[target_feature(...)]`.
    fn as_target_feature_arg_arm(&self) -> &str {
        match *self {
            Self::Default => "neon,v7",
            Self::ArmV7 => "neon,v7",
            Self::Vfp4 => "neon,vfp4",
            Self::FPArmV8 => "neon,fp-armv8,v8",
            Self::AES => "neon,v8,aes",
            Self::FCMA => "neon,v8,fcma",
            Self::Dotprod => "neon,v8,dotprod",
            Self::I8MM => "neon,v8,i8mm",
            // Features not supported on 32-bit "arm".
            Self::SHA3 => unimplemented!(),
            Self::RDM => unimplemented!(),
            Self::SM4 => unimplemented!(),
            Self::FTTS => unimplemented!(),
        }
    }

    /// A stability attribute for the intrinsic.
    fn stability(&self, aarch64: bool) -> &str {
        // 32-bit intrinsics are all unstable for now
        if !aarch64 {
            return "unstable(feature = \"stdarch_arm_neon_intrinsics\", issue = \"111800\")";
        }
        match *self {
            Default | ArmV7 | Vfp4 | FPArmV8 | AES => {
                "stable(feature = \"neon_intrinsics\", since = \"1.59.0\")"
            }
            FCMA => "unstable(feature = \"stdarch_neon_fcma\", issue = \"117222\")",
            Dotprod => "unstable(feature = \"stdarch_neon_dotprod\", issue = \"117224\")",
            I8MM => "unstable(feature = \"stdarch_neon_i8mm\", issue = \"117223\")",
            SHA3 => "unstable(feature = \"stdarch_neon_sha3\", issue = \"117225\")",
            RDM => "stable(feature = \"rdm_intrinsics\", since = \"1.62.0\")",
            SM4 => "unstable(feature = \"stdarch_neon_sm4\", issue = \"117226\")",
            FTTS => "unstable(feature = \"stdarch_neon_ftts\", issue = \"117227\")",
        }
    }

    /// A string for use with #[simd_test(...)] (or `is_arm_feature_detected!(...)`).
    fn as_simd_test_arg_arm(&self) -> &str {
        // TODO: Ideally, these would match the target_feature strings (as for AArch64).
        match *self {
            // We typically specify the "v7" or "v8" target_features for codegen, but we can't test
            // them at runtime. However, in many cases we can test a specific named feature, and
            // this is sufficient. For example, Neon always requires at least Armv7.

            // "v7" extensions.
            Self::Default => "neon",
            Self::ArmV7 => "neon",

            // TODO: We can specify these features for code generation, but they have no runtime
            // detection, so we can't provide an accurate string for simd_test. For now, we use a
            // common Armv8 feature as a proxy, but we should improve std_detect support here and
            // update these accordingly.
            Self::Vfp4 => "neon,crc",
            Self::FPArmV8 => "neon,crc",

            // "v8" extensions.
            Self::AES => "neon,aes",
            Self::FCMA => "neon,fcma",
            Self::Dotprod => "neon,dotprod",
            Self::I8MM => "neon,i8mm",

            // Features not supported on 32-bit "arm".
            Self::SHA3 => unimplemented!(),
            Self::RDM => unimplemented!(),
            Self::SM4 => unimplemented!(),
            Self::FTTS => unimplemented!(),
        }
    }

    fn attr(name: &str, value: impl fmt::Display) -> String {
        format!(r#"#[{name}(enable = "{value}")]"#)
    }

    fn attr_for_arch(arch: &str, name: &str, value: impl fmt::Display) -> String {
        format!(r#"#[cfg_attr(target_arch = "{arch}", {name}(enable = "{value}"))]"#)
    }

    /// Generate target_feature attributes for a test that will compile for both "arm" and "aarch64".
    fn to_target_feature_attr_shared(&self) -> Lines {
        let arm = self.as_target_feature_arg_arm().split(',');
        let aarch64 = self.as_target_feature_arg_aarch64().split(',');

        // Combine common features into an unconditional `target_feature` annotation, but guard
        // others behind `cfg_attr`.
        // TODO: It's much simpler to emit separate, guarded attributes for each architecture (as
        // for `simd_test`). However, this has an unfortunate impact on documentation, since
        // rustdoc can't currently look inside `cfg_attr` (stdarch/issues/1268).
        let mut aarch64: Vec<_> = aarch64.collect();
        let (both, arm): (Vec<_>, Vec<_>) = arm.partition(|v| aarch64.contains(v));
        aarch64.retain(|v| !both.contains(v));
        let mut lines = Vec::new();
        if !both.is_empty() {
            lines.push(Self::attr("target_feature", both.join(",")));
        };
        if !arm.is_empty() {
            lines.push(Self::attr_for_arch("arm", "target_feature", arm.join(",")));
        }
        if !aarch64.is_empty() {
            lines.push(Self::attr_for_arch(
                "aarch64",
                "target_feature",
                aarch64.join(","),
            ));
        }
        lines.into()
    }

    /// Generate a target_feature attribute for a test that will compile only for "aarch64".
    fn to_target_feature_attr_aarch64(&self) -> Lines {
        Lines::single(Self::attr(
            "target_feature",
            self.as_target_feature_arg_aarch64(),
        ))
    }

    /// Generate a target_feature attribute for a test that will compile only for "arm".
    fn to_target_feature_attr_arm(&self) -> Lines {
        Lines::single(Self::attr(
            "target_feature",
            self.as_target_feature_arg_arm(),
        ))
    }

    /// Generate simd_test attributes for a test that will compile for both "arm" and "aarch64".
    fn to_simd_test_attr_shared(&self) -> Lines {
        let arm = self.as_simd_test_arg_arm();
        let aarch64 = self.as_simd_test_arg_aarch64();
        if arm == aarch64 {
            Lines::single(Self::attr("simd_test", arm))
        } else {
            vec![
                Self::attr_for_arch("arm", "simd_test", arm),
                Self::attr_for_arch("aarch64", "simd_test", aarch64),
            ]
            .into()
        }
    }

    /// Generate a simd_test attribute for a test that will compile only for "aarch64".
    fn to_simd_test_attr_aarch64(&self) -> Lines {
        Lines::single(Self::attr("simd_test", self.as_simd_test_arg_aarch64()))
    }
}

/// Complete lines of generated source.
///
/// This enables common generation tasks to be factored out without precluding basic
/// context-specific formatting.
///
/// The convention in this generator is to prefix (not suffix) lines with a newline, so the
/// implementation of `std::fmt::Display` behaves in the same way.
struct Lines {
    indent: usize,
    lines: Vec<String>,
}

impl Lines {
    fn indented(self, indent: usize) -> Self {
        Self {
            indent: indent + self.indent,
            ..self
        }
    }

    fn single(line: String) -> Self {
        Self::from(vec![line])
    }
}

impl From<Vec<String>> for Lines {
    fn from(lines: Vec<String>) -> Self {
        Self { indent: 0, lines }
    }
}

impl std::fmt::Display for Lines {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
        for line in self.lines.iter() {
            write!(f, "\n{:width$}{line}", "", width = self.indent)?;
        }
        Ok(())
    }
}

#[derive(Clone, Copy)]
enum Fntype {
    Normal,
    Load,
    Store,
}

fn type_to_global_type(t: &str) -> &str {
    match t {
        "int8x8_t" | "int8x8x2_t" | "int8x8x3_t" | "int8x8x4_t" => "i8x8",
        "int8x16_t" | "int8x16x2_t" | "int8x16x3_t" | "int8x16x4_t" => "i8x16",
        "int16x4_t" | "int16x4x2_t" | "int16x4x3_t" | "int16x4x4_t" => "i16x4",
        "int16x8_t" | "int16x8x2_t" | "int16x8x3_t" | "int16x8x4_t" => "i16x8",
        "int32x2_t" | "int32x2x2_t" | "int32x2x3_t" | "int32x2x4_t" => "i32x2",
        "int32x4_t" | "int32x4x2_t" | "int32x4x3_t" | "int32x4x4_t" => "i32x4",
        "int64x1_t" | "int64x1x2_t" | "int64x1x3_t" | "int64x1x4_t" => "i64x1",
        "int64x2_t" | "int64x2x2_t" | "int64x2x3_t" | "int64x2x4_t" => "i64x2",
        "uint8x8_t" | "uint8x8x2_t" | "uint8x8x3_t" | "uint8x8x4_t" => "u8x8",
        "uint8x16_t" | "uint8x16x2_t" | "uint8x16x3_t" | "uint8x16x4_t" => "u8x16",
        "uint16x4_t" | "uint16x4x2_t" | "uint16x4x3_t" | "uint16x4x4_t" => "u16x4",
        "uint16x8_t" | "uint16x8x2_t" | "uint16x8x3_t" | "uint16x8x4_t" => "u16x8",
        "uint32x2_t" | "uint32x2x2_t" | "uint32x2x3_t" | "uint32x2x4_t" => "u32x2",
        "uint32x4_t" | "uint32x4x2_t" | "uint32x4x3_t" | "uint32x4x4_t" => "u32x4",
        "uint64x1_t" | "uint64x1x2_t" | "uint64x1x3_t" | "uint64x1x4_t" => "u64x1",
        "uint64x2_t" | "uint64x2x2_t" | "uint64x2x3_t" | "uint64x2x4_t" => "u64x2",
        "float16x4_t" => "f16x4",
        "float16x8_t" => "f16x8",
        "float32x2_t" | "float32x2x2_t" | "float32x2x3_t" | "float32x2x4_t" => "f32x2",
        "float32x4_t" | "float32x4x2_t" | "float32x4x3_t" | "float32x4x4_t" => "f32x4",
        "float64x1_t" | "float64x1x2_t" | "float64x1x3_t" | "float64x1x4_t" => "f64",
        "float64x2_t" | "float64x2x2_t" | "float64x2x3_t" | "float64x2x4_t" => "f64x2",
        "poly8x8_t" | "poly8x8x2_t" | "poly8x8x3_t" | "poly8x8x4_t" => "i8x8",
        "poly8x16_t" | "poly8x16x2_t" | "poly8x16x3_t" | "poly8x16x4_t" => "i8x16",
        "poly16x4_t" | "poly16x4x2_t" | "poly16x4x3_t" | "poly16x4x4_t" => "i16x4",
        "poly16x8_t" | "poly16x8x2_t" | "poly16x8x3_t" | "poly16x8x4_t" => "i16x8",
        "poly64x1_t" | "poly64x1x2_t" | "poly64x1x3_t" | "poly64x1x4_t" => "i64x1",
        "poly64x2_t" | "poly64x2x2_t" | "poly64x2x3_t" | "poly64x2x4_t" => "i64x2",
        "i8" => "i8",
        "i16" => "i16",
        "i32" => "i32",
        "i64" => "i64",
        "u8" => "u8",
        "u16" => "u16",
        "u32" => "u32",
        "u64" => "u64",
        "f32" => "f32",
        "f64" => "f64",
        "p8" => "p8",
        "p16" => "p16",
        "p64" => "p64",
        "p128" => "p128",
        _ => panic!("unknown type: {t}"),
    }
}

fn type_to_sub_type(t: &str) -> String {
    let s: Vec<_> = t.split('x').collect();
    match s.len() {
        2 => String::from(t),
        3 => format!("{}x{}_t", s[0], s[1]),
        _ => panic!("unknown type: {t}"),
    }
}

fn type_to_native_type(t: &str) -> String {
    let s: Vec<_> = t.split('x').collect();
    match s.len() {
        1 => {
            assert!(t.contains("*const") || t.contains("*mut"));
            let sub: Vec<_> = t.split(' ').collect();
            String::from(sub[1])
        }
        2 | 3 => match &s[0][0..3] {
            "int" => format!("i{}", &s[0][3..]),
            "uin" => format!("u{}", &s[0][4..]),
            "flo" => format!("f{}", &s[0][5..]),
            "pol" => format!("u{}", &s[0][4..]),
            _ => panic!("unknown type: {t}"),
        },
        _ => panic!("unknown type: {t}"),
    }
}

fn native_type_to_type(t: &str) -> &str {
    match t {
        "i8" => "int8x8_t",
        "i16" => "int16x4_t",
        "i32" => "int32x2_t",
        "i64" => "int64x1_t",
        "u8" => "uint8x8_t",
        "u16" => "uint16x4_t",
        "u32" => "uint32x2_t",
        "u64" => "uint64x1_t",
        "f16" => "float16x4_t",
        "f32" => "float32x2_t",
        "f64" => "float64x1_t",
        _ => panic!("unknown type: {t}"),
    }
}

fn native_type_to_long_type(t: &str) -> &str {
    match t {
        "i8" => "int8x16_t",
        "i16" => "int16x8_t",
        "i32" => "int32x4_t",
        "i64" => "int64x2_t",
        "u8" => "uint8x16_t",
        "u16" => "uint16x8_t",
        "u32" => "uint32x4_t",
        "u64" => "uint64x2_t",
        "f16" => "float16x8_t",
        "f32" => "float32x4_t",
        "f64" => "float64x2_t",
        _ => panic!("unknown type: {t}"),
    }
}

fn type_to_half(t: &str) -> &str {
    match t {
        "int8x16_t" => "int8x8_t",
        "int16x8_t" => "int16x4_t",
        "int32x4_t" => "int32x2_t",
        "int64x2_t" => "int64x1_t",
        "uint8x16_t" => "uint8x8_t",
        "uint16x8_t" => "uint16x4_t",
        "uint32x4_t" => "uint32x2_t",
        "uint64x2_t" => "uint64x1_t",
        "poly8x16_t" => "poly8x8_t",
        "poly16x8_t" => "poly16x4_t",
        "float32x4_t" => "float32x2_t",
        "float64x2_t" => "float64x1_t",
        _ => panic!("unknown half type for {t}"),
    }
}

fn type_with_merged_lanes(t: &str, elements_per_lane: usize) -> String {
    assert_eq!(type_len(t) % elements_per_lane, 0);
    let prefix_len = t
        .find(|c: char| c.is_ascii_digit())
        .unwrap_or_else(|| t.len());
    format!(
        "{prefix}{bits}x{len}_t",
        prefix = &t[0..prefix_len],
        bits = type_bits(t) * elements_per_lane,
        len = type_len(t) / elements_per_lane
    )
}

fn asc(start: i32, len: usize) -> String {
    let mut s = String::from("[");
    for i in 0..len {
        if i != 0 {
            s.push_str(", ");
        }
        let n = start + i as i32;
        s.push_str(&n.to_string());
    }
    s.push_str("]");
    s
}

fn transpose1(x: usize) -> &'static str {
    match x {
        2 => "[0, 2]",
        4 => "[0, 4, 2, 6]",
        8 => "[0, 8, 2, 10, 4, 12, 6, 14]",
        16 => "[0, 16, 2, 18, 4, 20, 6, 22, 8, 24, 10, 26, 12, 28, 14, 30]",
        _ => panic!("unknown transpose order of len {x}"),
    }
}

fn transpose2(x: usize) -> &'static str {
    match x {
        2 => "[1, 3]",
        4 => "[1, 5, 3, 7]",
        8 => "[1, 9, 3, 11, 5, 13, 7, 15]",
        16 => "[1, 17, 3, 19, 5, 21, 7, 23, 9, 25, 11, 27, 13, 29, 15, 31]",
        _ => panic!("unknown transpose order of len {x}"),
    }
}

fn zip1(x: usize) -> &'static str {
    match x {
        2 => "[0, 2]",
        4 => "[0, 4, 1, 5]",
        8 => "[0, 8, 1, 9, 2, 10, 3, 11]",
        16 => "[0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23]",
        _ => panic!("unknown zip order of len {x}"),
    }
}

fn zip2(x: usize) -> &'static str {
    match x {
        2 => "[1, 3]",
        4 => "[2, 6, 3, 7]",
        8 => "[4, 12, 5, 13, 6, 14, 7, 15]",
        16 => "[8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31]",
        _ => panic!("unknown zip order of len {x}"),
    }
}

fn unzip1(x: usize) -> &'static str {
    match x {
        2 => "[0, 2]",
        4 => "[0, 2, 4, 6]",
        8 => "[0, 2, 4, 6, 8, 10, 12, 14]",
        16 => "[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30]",
        _ => panic!("unknown unzip order of len {x}"),
    }
}

fn unzip2(x: usize) -> &'static str {
    match x {
        2 => "[1, 3]",
        4 => "[1, 3, 5, 7]",
        8 => "[1, 3, 5, 7, 9, 11, 13, 15]",
        16 => "[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31]",
        _ => panic!("unknown unzip order of len {x}"),
    }
}

fn values(t: &str, vs: &[String]) -> String {
    if vs.len() == 1 && !t.contains('x') {
        format!(": {t} = {}", vs[0])
    } else if vs.len() == 1 && type_to_global_type(t) == "f64" {
        format!(": {} = {}", type_to_global_type(t), vs[0])
    } else {
        let s: Vec<_> = t.split('x').collect();
        if s.len() == 3 {
            format!(
                ": [{}; {}] = [{}]",
                type_to_native_type(t),
                type_len(t),
                vs.iter()
                    .map(|v| map_val(type_to_global_type(t), v))
                    //.map(|v| format!("{}{}", v, type_to_native_type(t)))
                    .collect::<Vec<_>>()
                    .join(", ")
            )
        } else {
            format!(
                ": {} = {}::new({})",
                type_to_global_type(t),
                type_to_global_type(t),
                vs.iter()
                    .map(|v| map_val(type_to_global_type(t), v))
                    //.map(|v| format!("{}{}", v, type_to_native_type(t)))
                    .collect::<Vec<_>>()
                    .join(", ")
            )
        }
    }
}

fn max_val(t: &str) -> &'static str {
    match &t[..3] {
        "u8x" => "0xFF",
        "u16" => "0xFF_FF",
        "u32" => "0xFF_FF_FF_FF",
        "u64" => "0xFF_FF_FF_FF_FF_FF_FF_FF",
        "i8x" => "0x7F",
        "i16" => "0x7F_FF",
        "i32" => "0x7F_FF_FF_FF",
        "i64" => "0x7F_FF_FF_FF_FF_FF_FF_FF",
        "f32" => "3.40282347e+38",
        "f64" => "1.7976931348623157e+308",
        _ => panic!("No TRUE for type {t}"),
    }
}

fn min_val(t: &str) -> &'static str {
    match &t[..3] {
        "u8x" => "0",
        "u16" => "0",
        "u32" => "0",
        "u64" => "0",
        "i8x" => "-128",
        "i16" => "-32768",
        "i32" => "-2147483648",
        "i64" => "-9223372036854775808",
        "f32" => "-3.40282347e+38",
        "f64" => "-1.7976931348623157e+308",
        _ => panic!("No TRUE for type {t}"),
    }
}

fn true_val(t: &str) -> &'static str {
    match &t[..3] {
        "u8x" => "0xFF",
        "u16" => "0xFF_FF",
        "u32" => "0xFF_FF_FF_FF",
        "u64" => "0xFF_FF_FF_FF_FF_FF_FF_FF",
        _ => panic!("No TRUE for type {t}"),
    }
}

fn ff_val(t: &str) -> &'static str {
    match &t[..3] {
        "u8x" => "0xFF",
        "u16" => "0xFF_FF",
        "u32" => "0xFF_FF_FF_FF",
        "u64" => "0xFF_FF_FF_FF_FF_FF_FF_FF",
        "i8x" => "0xFF",
        "i16" => "0xFF_FF",
        "i32" => "0xFF_FF_FF_FF",
        "i64" => "0xFF_FF_FF_FF_FF_FF_FF_FF",
        _ => panic!("No TRUE for type {t}"),
    }
}

fn false_val(_t: &str) -> &'static str {
    "0"
}

fn bits(t: &str) -> &'static str {
    match &t[..3] {
        "u8x" => "8",
        "u16" => "16",
        "u32" => "32",
        "u64" => "64",
        "i8x" => "8",
        "i16" => "16",
        "i32" => "32",
        "i64" => "64",
        "p8x" => "8",
        "p16" => "16",
        "p64" => "64",
        _ => panic!("Unknown bits for type {t}"),
    }
}

fn bits_minus_one(t: &str) -> &'static str {
    match &t[..3] {
        "u8x" => "7",
        "u16" => "15",
        "u32" => "31",
        "u64" => "63",
        "i8x" => "7",
        "i16" => "15",
        "i32" => "31",
        "i64" => "63",
        "p8x" => "7",
        "p16" => "15",
        "p64" => "63",
        _ => panic!("Unknown bits for type {t}"),
    }
}

fn half_bits(t: &str) -> &'static str {
    match &t[..3] {
        "u8x" => "4",
        "u16" => "8",
        "u32" => "16",
        "u64" => "32",
        "i8x" => "4",
        "i16" => "8",
        "i32" => "16",
        "i64" => "32",
        "p8x" => "4",
        "p16" => "8",
        "p64" => "32",
        _ => panic!("Unknown bits for type {t}"),
    }
}

fn type_len_str(t: &str) -> &'static str {
    match t {
        "int8x8_t" => "8",
        "int8x16_t" => "16",
        "int16x4_t" => "4",
        "int16x8_t" => "8",
        "int32x2_t" => "2",
        "int32x4_t" => "4",
        "int64x1_t" => "1",
        "int64x2_t" => "2",
        "uint8x8_t" => "8",
        "uint8x16_t" => "16",
        "uint16x4_t" => "4",
        "uint16x8_t" => "8",
        "uint32x2_t" => "2",
        "uint32x4_t" => "4",
        "uint64x1_t" => "1",
        "uint64x2_t" => "2",
        "float16x4_t" => "4",
        "float16x8_t" => "8",
        "float32x2_t" => "2",
        "float32x4_t" => "4",
        "float64x1_t" => "1",
        "float64x2_t" => "2",
        "poly8x8_t" => "8",
        "poly8x16_t" => "16",
        "poly16x4_t" => "4",
        "poly16x8_t" => "8",
        "poly64x1_t" => "1",
        "poly64x2_t" => "2",
        _ => panic!("unknown type: {t}"),
    }
}

fn type_len_minus_one_str(t: &str) -> &'static str {
    match t {
        "int8x8_t" => "7",
        "int8x16_t" => "15",
        "int16x4_t" => "3",
        "int16x8_t" => "7",
        "int32x2_t" => "1",
        "int32x4_t" => "3",
        "int64x1_t" => "0",
        "int64x2_t" => "1",
        "uint8x8_t" => "7",
        "uint8x16_t" => "15",
        "uint16x4_t" => "3",
        "uint16x8_t" => "7",
        "uint32x2_t" => "1",
        "uint32x4_t" => "3",
        "uint64x1_t" => "0",
        "uint64x2_t" => "1",
        "float16x4_t" => "3",
        "float16x8_t" => "7",
        "float32x2_t" => "1",
        "float32x4_t" => "3",
        "float64x1_t" => "0",
        "float64x2_t" => "1",
        "poly8x8_t" => "7",
        "poly8x16_t" => "15",
        "poly16x4_t" => "3",
        "poly16x8_t" => "7",
        "poly64x1_t" => "0",
        "poly64x2_t" => "1",
        _ => panic!("unknown type: {t}"),
    }
}

fn type_half_len_str(t: &str) -> &'static str {
    match t {
        "int8x8_t" => "4",
        "int8x16_t" => "8",
        "int16x4_t" => "2",
        "int16x8_t" => "4",
        "int32x2_t" => "1",
        "int32x4_t" => "2",
        "int64x1_t" => "0",
        "int64x2_t" => "1",
        "uint8x8_t" => "4",
        "uint8x16_t" => "8",
        "uint16x4_t" => "2",
        "uint16x8_t" => "4",
        "uint32x2_t" => "1",
        "uint32x4_t" => "2",
        "uint64x1_t" => "0",
        "uint64x2_t" => "1",
        "float16x4_t" => "2",
        "float16x8_t" => "4",
        "float32x2_t" => "1",
        "float32x4_t" => "2",
        "float64x1_t" => "0",
        "float64x2_t" => "1",
        "poly8x8_t" => "4",
        "poly8x16_t" => "8",
        "poly16x4_t" => "2",
        "poly16x8_t" => "4",
        "poly64x1_t" => "0",
        "poly64x2_t" => "1",
        _ => panic!("unknown type: {t}"),
    }
}

fn map_val<'v>(t: &str, v: &'v str) -> &'v str {
    match v {
        "FALSE" => false_val(t),
        "TRUE" => true_val(t),
        "MAX" => max_val(t),
        "MIN" => min_val(t),
        "FF" => ff_val(t),
        "BITS" => bits(t),
        "BITS_M1" => bits_minus_one(t),
        "HFBITS" => half_bits(t),
        "LEN" => type_len_str(t),
        "LEN_M1" => type_len_minus_one_str(t),
        "HFLEN" => type_half_len_str(t),
        o => o,
    }
}

fn type_to_ext(t: &str, v: bool, r: bool, pi8: bool) -> String {
    if !t.contains('x') {
        return t.replace('u', "i");
    }
    let native = type_to_native_type(t);
    let sub_ext = match type_sub_len(t) {
        1 => String::new(),
        _ if v => format!(
            ".p0v{}{}",
            &type_len(&type_to_sub_type(t)).to_string(),
            native
        ),
        _ if pi8 => format!(".p0i8"),
        _ => format!(".p0{native}"),
    };
    let sub_type = match &native[0..1] {
        "i" | "f" => native,
        "u" => native.replace('u', "i"),
        _ => panic!("unknown type: {t}"),
    };
    let ext = format!(
        "v{}{}{}",
        &type_len(&type_to_sub_type(t)).to_string(),
        sub_type,
        sub_ext
    );
    if r {
        let ss: Vec<_> = ext.split('.').collect();
        if ss.len() != 2 {
            ext
        } else {
            format!("{}.{}", ss[1], ss[0])
        }
    } else {
        ext
    }
}

fn ext(s: &str, in_t: &[&str; 3], out_t: &str) -> String {
    s.replace("_EXT_", &type_to_ext(in_t[0], false, false, false))
        .replace("_EXT2_", &type_to_ext(out_t, false, false, false))
        .replace("_EXT3_", &type_to_ext(in_t[1], false, false, false))
        .replace("_EXT4_", &type_to_ext(in_t[2], false, false, false))
        .replace("_EXTr3_", &type_to_ext(in_t[1], false, true, false))
        .replace("_EXTv2_", &type_to_ext(out_t, true, false, false))
        .replace("_EXTpi8_", &type_to_ext(in_t[1], false, false, true))
        .replace("_EXTpi82_", &type_to_ext(out_t, false, false, true))
        .replace("_EXTpi8r_", &type_to_ext(in_t[1], false, true, true))
}

fn is_vldx(name: &str) -> bool {
    let s: Vec<_> = name.split('_').collect();
    &name[0..3] == "vld"
        && name[3..4].parse::<i32>().unwrap() > 1
        && (s.last().unwrap().starts_with('s') || s.last().unwrap().starts_with('f'))
}

fn is_vstx(name: &str) -> bool {
    let s: Vec<_> = name.split('_').collect();
    s.len() == 2
        && &name[0..3] == "vst"
        && name[3..4].parse::<i32>().unwrap() > 1
        && (s[1].starts_with('s') || s[1].starts_with('f'))
}

fn create_doc_string(comment_string: &str, fn_name: &str) -> String {
    format!(
        r#"{}
///
/// [Arm's documentation](https://developer.arm.com/architectures/instruction-sets/intrinsics/{})"#,
        comment_string, fn_name
    )
}

#[allow(clippy::too_many_arguments)]
fn gen_aarch64(
    current_comment: &str,
    current_fn: &Option<String>,
    current_name: &str,
    current_aarch64: &Option<String>,
    link_aarch64: &Option<String>,
    const_aarch64: &Option<String>,
    constn: &Option<String>,
    in_t: &[&str; 3],
    out_t: &str,
    current_tests: &[(
        Vec<String>,
        Vec<String>,
        Vec<String>,
        Option<String>,
        Vec<String>,
    )],
    suffix: Suffix,
    para_num: i32,
    target: TargetFeature,
    fixed: &Vec<String>,
    multi_fn: &Vec<String>,
    fn_type: Fntype,
) -> (String, String) {
    let name = match suffix {
        Normal => format!("{current_name}{}", type_to_suffix(in_t[1])),
        NoQ => format!("{current_name}{}", type_to_noq_suffix(in_t[1])),
        Double => format!(
            "{}{}",
            current_name,
            type_to_double_suffixes(out_t, in_t[1])
        ),
        NoQDouble => format!(
            "{}{}",
            current_name,
            type_to_noq_double_suffixes(out_t, in_t[1])
        ),
        NSuffix => format!("{current_name}{}", type_to_n_suffix(in_t[1])),
        DoubleN => format!(
            "{}{}",
            current_name,
            type_to_double_n_suffixes(out_t, in_t[1])
        ),
        NoQNSuffix => format!("{current_name}{}", type_to_noq_n_suffix(in_t[1])),
        OutSuffix => format!("{current_name}{}", type_to_suffix(out_t)),
        OutNSuffix => format!("{current_name}{}", type_to_n_suffix(out_t)),
        OutNox => format!(
            "{}{}",
            current_name,
            type_to_suffix(&type_to_sub_type(out_t))
        ),
        In1Nox => format!(
            "{}{}",
            current_name,
            type_to_suffix(&type_to_sub_type(in_t[1]))
        ),
        OutDupNox => format!(
            "{}{}",
            current_name,
            type_to_dup_suffix(&type_to_sub_type(out_t))
        ),
        OutLaneNox => format!(
            "{}{}",
            current_name,
            type_to_lane_suffix(&type_to_sub_type(out_t))
        ),
        In1LaneNox => format!(
            "{}{}",
            current_name,
            type_to_lane_suffix(&type_to_sub_type(in_t[1]))
        ),
        Lane => format!(
            "{}{}",
            current_name,
            type_to_lane_suffixes(out_t, in_t[1], false)
        ),
        In2 => format!("{current_name}{}", type_to_suffix(in_t[2])),
        In2Lane => format!(
            "{}{}",
            current_name,
            type_to_lane_suffixes(out_t, in_t[2], false)
        ),
        OutLane => format!(
            "{}{}",
            current_name,
            type_to_lane_suffixes(out_t, in_t[2], true)
        ),
        Rot => type_to_rot_suffix(current_name, type_to_suffix(out_t)),
        RotLane => type_to_rot_suffix(current_name, &type_to_lane_suffixes(out_t, in_t[2], false)),
    };
    let current_fn = if let Some(current_fn) = current_fn.clone() {
        if link_aarch64.is_some() {
            panic!("[{name}] Can't specify link and fn at the same time.")
        }
        current_fn
    } else if link_aarch64.is_some() {
        format!("{name}_")
    } else {
        if multi_fn.is_empty() {
            panic!(
                "[{}] Either (multi) fn or link-aarch have to be specified.",
                name
            )
        }
        String::new()
    };
    let current_aarch64 = current_aarch64.clone().unwrap();
    let mut link_t: Vec<String> = vec![
        in_t[0].to_string(),
        in_t[1].to_string(),
        in_t[2].to_string(),
        out_t.to_string(),
    ];
    let mut ext_c = String::new();
    if let Some(mut link_aarch64) = link_aarch64.clone() {
        if link_aarch64.contains(':') {
            let links: Vec<_> = link_aarch64.split(':').map(|v| v.to_string()).collect();
            assert_eq!(links.len(), 5);
            link_aarch64 = links[0].to_string();
            link_t = vec![
                links[1].clone(),
                links[2].clone(),
                links[3].clone(),
                links[4].clone(),
            ];
        }
        let link_aarch64 = if link_aarch64.starts_with("llvm") {
            ext(&link_aarch64, in_t, out_t)
        } else {
            let mut link = String::from("llvm.aarch64.neon.");
            link.push_str(&link_aarch64);
            ext(&link, in_t, out_t)
        };
        let (ext_inputs, ext_output) = {
            if const_aarch64.is_some() {
                if !matches!(fn_type, Fntype::Normal) {
                    let ptr_type = match fn_type {
                        Fntype::Load => "*const i8",
                        Fntype::Store => "*mut i8",
                        _ => panic!("unsupported fn type"),
                    };
                    let sub = type_to_sub_type(in_t[1]);
                    (
                        match type_sub_len(in_t[1]) {
                            1 => format!("a: {sub}, n: i64, ptr: {ptr_type}"),
                            2 => format!("a: {sub}, b: {sub}, n: i64, ptr: {ptr_type}"),
                            3 => format!(
                                "a: {}, b: {}, c: {}, n: i64, ptr: {}",
                                sub, sub, sub, ptr_type
                            ),
                            4 => format!(
                                "a: {}, b: {}, c: {}, d: {}, n: i64, ptr: {}",
                                sub, sub, sub, sub, ptr_type
                            ),
                            _ => panic!("unsupported type: {}", in_t[1]),
                        },
                        if out_t != "void" {
                            format!(" -> {out_t}")
                        } else {
                            String::new()
                        },
                    )
                } else {
                    (
                        match para_num {
                            1 => format!("a: {}, n: i32", in_t[0]),
                            2 => format!("a: {}, b: {}, n: i32", in_t[0], in_t[1]),
                            3 => format!("a: {}, b: {}, c: {}, n: i32", in_t[0], in_t[1], in_t[2]),
                            _ => unimplemented!("unknown para_num"),
                        },
                        format!(" -> {out_t}"),
                    )
                }
            } else if matches!(fn_type, Fntype::Store) {
                let sub = type_to_sub_type(in_t[1]);
                let ptr_type = if is_vstx(&name) {
                    "i8".to_string()
                } else {
                    type_to_native_type(in_t[1])
                };
                let subs = match type_sub_len(in_t[1]) {
                    1 => format!("a: {sub}"),
                    2 => format!("a: {sub}, b: {sub}"),
                    3 => format!("a: {sub}, b: {sub}, c: {sub}"),
                    4 => format!("a: {sub}, b: {sub}, c: {sub}, d: {sub}"),
                    _ => panic!("unsupported type: {}", in_t[1]),
                };
                (format!("{subs}, ptr: *mut {ptr_type}"), String::new())
            } else if is_vldx(&name) {
                let ptr_type = if name.contains("dup") {
                    type_to_native_type(out_t)
                } else {
                    type_to_sub_type(out_t)
                };
                (format!("ptr: *const {ptr_type}"), format!(" -> {out_t}"))
            } else {
                (
                    match para_num {
                        1 => format!("a: {}", link_t[0]),
                        2 => format!("a: {}, b: {}", link_t[0], link_t[1]),
                        3 => format!("a: {}, b: {}, c: {}", link_t[0], link_t[1], link_t[2]),
                        _ => unimplemented!("unknown para_num"),
                    },
                    format!(" -> {}", link_t[3]),
                )
            }
        };
        ext_c = format!(
            r#"#[allow(improper_ctypes)]
    extern "unadjusted" {{
        #[cfg_attr(target_arch = "aarch64", link_name = "{}")]
        fn {}({}){};
    }}
    "#,
            link_aarch64, current_fn, ext_inputs, ext_output,
        );
    };
    let const_declare = if let Some(constn) = constn {
        if constn.contains(':') {
            let constns: Vec<_> = constn.split(':').map(|v| v.to_string()).collect();
            assert_eq!(constns.len(), 2);
            format!(r#"<const {}: i32, const {}: i32>"#, constns[0], constns[1])
        } else {
            format!(r#"<const {constn}: i32>"#)
        }
    } else {
        String::new()
    };
    let multi_calls = if !multi_fn.is_empty() {
        let mut calls = String::new();
        for i in 0..multi_fn.len() {
            if i > 0 {
                calls.push_str("\n    ");
            }
            calls.push_str(&get_call(
                &multi_fn[i],
                current_name,
                in_t,
                out_t,
                fixed,
                None,
                true,
            ));
        }
        calls
    } else {
        String::new()
    };
    let const_assert = if let Some(constn) = constn {
        if constn.contains(':') {
            let constns: Vec<_> = constn.split(':').map(|v| v.to_string()).collect();
            let const_test = current_tests[0].3.as_ref().unwrap();
            let const_tests: Vec<_> = const_test.split(':').map(|v| v.to_string()).collect();
            assert_eq!(constns.len(), 2);
            assert_eq!(const_tests.len(), 2);
            format!(
                r#", {} = {}, {} = {}"#,
                constns[0],
                map_val(in_t[1], &const_tests[0]),
                constns[1],
                map_val(in_t[1], &const_tests[1]),
            )
        } else {
            format!(
                r#", {} = {}"#,
                constn,
                map_val(in_t[1], current_tests[0].3.as_ref().unwrap())
            )
        }
    } else {
        String::new()
    };
    let const_legacy = if let Some(constn) = constn {
        if constn.contains(':') {
            format!(
                "\n#[rustc_legacy_const_generics({}, {})]",
                para_num - 1,
                para_num + 1
            )
        } else {
            format!("\n#[rustc_legacy_const_generics({para_num})]")
        }
    } else {
        String::new()
    };
    let fn_decl = {
        let fn_output = if out_t == "void" {
            String::new()
        } else {
            format!("-> {out_t} ")
        };
        let fn_inputs = match para_num {
            1 => format!("(a: {})", in_t[0]),
            2 => format!("(a: {}, b: {})", in_t[0], in_t[1]),
            3 => format!("(a: {}, b: {}, c: {})", in_t[0], in_t[1], in_t[2]),
            _ => panic!("unsupported parameter number"),
        };
        format!(
            "pub unsafe fn {}{}{} {}",
            name, const_declare, fn_inputs, fn_output
        )
    };
    let call_params = {
        if let (Some(const_aarch64), Some(_)) = (const_aarch64, link_aarch64) {
            if !matches!(fn_type, Fntype::Normal) {
                let subs = match type_sub_len(in_t[1]) {
                    1 => "b",
                    2 => "b.0, b.1",
                    3 => "b.0, b.1, b.2",
                    4 => "b.0, b.1, b.2, b.3",
                    _ => panic!("unsupported type: {}", in_t[1]),
                };
                format!(
                    r#"{}
    {}{}({}, {} as i64, a as _)"#,
                    multi_calls,
                    ext_c,
                    current_fn,
                    subs,
                    constn.as_deref().unwrap()
                )
            } else {
                match para_num {
                    1 => format!(
                        r#"{}
    {}{}(a, {})"#,
                        multi_calls, ext_c, current_fn, const_aarch64
                    ),
                    2 => format!(
                        r#"{}
    {}{}(a, b, {})"#,
                        multi_calls, ext_c, current_fn, const_aarch64
                    ),
                    _ => String::new(),
                }
            }
        } else if link_aarch64.is_some() && matches!(fn_type, Fntype::Store) {
            let cast = if is_vstx(&name) { " as _" } else { "" };
            match type_sub_len(in_t[1]) {
                1 => format!(r#"{ext_c}{current_fn}(b, a{cast})"#),
                2 => format!(r#"{ext_c}{current_fn}(b.0, b.1, a{cast})"#),
                3 => format!(r#"{ext_c}{current_fn}(b.0, b.1, b.2, a{cast})"#),
                4 => format!(r#"{ext_c}{current_fn}(b.0, b.1, b.2, b.3, a{cast})"#),
                _ => panic!("unsupported type: {}", in_t[1]),
            }
        } else if link_aarch64.is_some() && is_vldx(&name) {
            format!(r#"{ext_c}{current_fn}(a as _)"#,)
        } else {
            let trans: [&str; 2] = if link_t[3] != out_t {
                ["transmute(", ")"]
            } else {
                ["", ""]
            };
            match (multi_calls.len(), para_num, fixed.len()) {
                (0, 1, 0) => format!(r#"{ext_c}{}{current_fn}(a){}"#, trans[0], trans[1]),
                (0, 1, _) => {
                    let fixed: Vec<String> =
                        fixed.iter().take(type_len(in_t[0])).cloned().collect();
                    format!(
                        r#"let b{};
    {}{}{}(a, transmute(b)){}"#,
                        values(in_t[0], &fixed),
                        ext_c,
                        trans[0],
                        current_fn,
                        trans[1],
                    )
                }
                (0, 2, _) => format!(r#"{ext_c}{}{current_fn}(a, b){}"#, trans[0], trans[1],),
                (0, 3, _) => format!(r#"{ext_c}{current_fn}(a, b, c)"#,),
                (_, 1, _) => format!(r#"{ext_c}{multi_calls}"#,),
                (_, 2, _) => format!(r#"{ext_c}{multi_calls}"#,),
                (_, 3, _) => format!(r#"{ext_c}{multi_calls}"#,),
                (_, _, _) => String::new(),
            }
        }
    };
    let stable = target.stability(true);
    let function = format!(
        r#"
{function_doc}
#[inline]{target_feature}
#[cfg_attr(test, assert_instr({current_aarch64}{const_assert}))]{const_legacy}
#[{stable}]
{fn_decl}{{
    {call_params}
}}
"#,
        function_doc = create_doc_string(current_comment, &name),
        target_feature = target.to_target_feature_attr_aarch64()
    );
    let test = match fn_type {
        Fntype::Normal => gen_test(
            &name,
            in_t,
            out_t,
            current_tests,
            [type_len(in_t[0]), type_len(in_t[1]), type_len(in_t[2])],
            type_len(out_t),
            para_num,
            target.to_simd_test_attr_aarch64(),
        ),
        Fntype::Load => gen_load_test(&name, in_t, out_t, current_tests, type_len(out_t)),
        Fntype::Store => gen_store_test(&name, in_t, out_t, current_tests, type_len(in_t[1])),
    };
    (function, test)
}

fn gen_load_test(
    name: &str,
    in_t: &[&str; 3],
    out_t: &str,
    current_tests: &[(
        Vec<String>,
        Vec<String>,
        Vec<String>,
        Option<String>,
        Vec<String>,
    )],
    type_len: usize,
) -> String {
    let mut test = format!(
        r#"{simd_test}
    unsafe fn test_{name}() {{"#,
        simd_test = Default.to_simd_test_attr_shared().indented(4)
    );
    for (a, b, _, n, e) in current_tests {
        let a: Vec<String> = a.iter().take(type_len + 1).cloned().collect();
        let e: Vec<String> = e.iter().take(type_len).cloned().collect();
        let has_b = !b.is_empty();
        let has_n = n.is_some();
        let mut input = String::from("[");
        for i in 0..type_len + 1 {
            if i != 0 {
                input.push_str(", ");
            }
            input.push_str(&a[i])
        }
        input.push_str("]");
        let output = |v: &Vec<String>| {
            let mut output = String::from("[");
            for i in 0..type_sub_len(out_t) {
                if i != 0 {
                    output.push_str(", ");
                }
                let sub_len = type_len / type_sub_len(out_t);
                if type_to_global_type(out_t) != "f64" {
                    let mut sub_output = format!("{}::new(", type_to_global_type(out_t));
                    for j in 0..sub_len {
                        if j != 0 {
                            sub_output.push_str(", ");
                        }
                        sub_output.push_str(&v[i * sub_len + j]);
                    }
                    sub_output.push_str(")");
                    output.push_str(&sub_output);
                } else {
                    output.push_str(&v[i]);
                }
            }
            output.push_str("]");
            output
        };
        let input_b = if has_b {
            let b: Vec<String> = b.iter().take(type_len).cloned().collect();
            format!(
                r#"
        let b: [{}; {}] = {};"#,
                type_to_global_type(in_t[1]),
                type_sub_len(in_t[1]),
                output(&b),
            )
        } else {
            String::new()
        };
        let t = format!(
            r#"
        let a: [{}; {}] = {};{}
        let e: [{}; {}] = {};
        let r: [{}; {}] = transmute({}{}(a[1..].as_ptr(){}));
        assert_eq!(r, e);
"#,
            type_to_native_type(out_t),
            type_len + 1,
            input,
            input_b,
            type_to_global_type(out_t),
            type_sub_len(out_t),
            output(&e),
            type_to_global_type(out_t),
            type_sub_len(out_t),
            name,
            if has_n {
                format!("::<{}>", n.as_deref().unwrap())
            } else {
                String::new()
            },
            if has_b { ", transmute(b)" } else { "" },
        );
        test.push_str(&t);
    }
    test.push_str("    }\n");
    test
}

fn gen_store_test(
    name: &str,
    in_t: &[&str; 3],
    _out_t: &str,
    current_tests: &[(
        Vec<String>,
        Vec<String>,
        Vec<String>,
        Option<String>,
        Vec<String>,
    )],
    type_len: usize,
) -> String {
    let mut test = format!(
        r#"{simd_test}
    unsafe fn test_{name}() {{"#,
        simd_test = Default.to_simd_test_attr_shared().indented(4)
    );
    for (a, _, _, constn, e) in current_tests {
        let a: Vec<String> = a.iter().take(type_len + 1).cloned().collect();
        let e: Vec<String> = e.iter().take(type_len).cloned().collect();
        let mut input = String::from("[");
        for i in 0..type_len + 1 {
            if i != 0 {
                input.push_str(", ");
            }
            input.push_str(&a[i])
        }
        input.push_str("]");
        let mut output = String::from("[");
        for i in 0..type_len {
            if i != 0 {
                output.push_str(", ");
            }
            output.push_str(&e[i])
        }
        output.push_str("]");
        let const_n = constn
            .as_deref()
            .map_or(String::new(), |n| format!("::<{}>", n.to_string()));
        let t = format!(
            r#"
        let a: [{}; {}] = {};
        let e: [{}; {}] = {};
        let mut r: [{}; {}] = [0{}; {}];
        {}{}(r.as_mut_ptr(), core::ptr::read_unaligned(a[1..].as_ptr() as _));
        assert_eq!(r, e);
"#,
            type_to_native_type(in_t[1]),
            type_len + 1,
            input,
            type_to_native_type(in_t[1]),
            type_len,
            output,
            type_to_native_type(in_t[1]),
            type_len,
            type_to_native_type(in_t[1]),
            type_len,
            name,
            const_n,
        );
        test.push_str(&t);
    }
    test.push_str("    }\n");
    test
}

fn gen_test(
    name: &str,
    in_t: &[&str; 3],
    out_t: &str,
    current_tests: &[(
        Vec<String>,
        Vec<String>,
        Vec<String>,
        Option<String>,
        Vec<String>,
    )],
    len_in: [usize; 3],
    len_out: usize,
    para_num: i32,
    attributes: Lines,
) -> String {
    let mut test = attributes.indented(4).to_string();
    test.push_str(&format!("\n    unsafe fn test_{name}() {{"));
    for (a, b, c, n, e) in current_tests {
        let a: Vec<String> = a.iter().take(len_in[0]).cloned().collect();
        let b: Vec<String> = b.iter().take(len_in[1]).cloned().collect();
        let c: Vec<String> = c.iter().take(len_in[2]).cloned().collect();
        let e: Vec<String> = e.iter().take(len_out).cloned().collect();
        let const_value = if let Some(constn) = n {
            if constn.contains(':') {
                let constns: Vec<_> = constn.split(':').map(|v| v.to_string()).collect();
                format!(
                    r#"::<{}, {}>"#,
                    map_val(in_t[1], &constns[0]),
                    map_val(in_t[1], &constns[1])
                )
            } else {
                format!(r#"::<{}>"#, map_val(in_t[1], constn))
            }
        } else {
            String::new()
        };
        let r_type = match type_sub_len(out_t) {
            1 => type_to_global_type(out_t).to_string(),
            _ => format!("[{}; {}]", type_to_native_type(out_t), type_len(out_t)),
        };
        let mut call_params = String::new();
        assert!(matches!(para_num, 1..=3));
        for i in 0..para_num {
            let chr = char::from(b'a' + i as u8);
            let v = match i {
                0 => &a,
                1 => &b,
                2 => &c,
                _ => unreachable!(),
            };
            let param_type = in_t[i as usize];
            write!(test, "\n        let {chr}{};", values(param_type, v)).unwrap();
            if i != 0 {
                call_params.push_str(", ");
            }
            if param_type != type_to_global_type(param_type) {
                write!(call_params, "transmute({chr})").unwrap();
            } else {
                call_params.push(chr);
            }
        }
        let r_trans = if r_type != out_t {
            ("transmute(", ")")
        } else {
            ("", "")
        };
        write!(
            test,
            r#"
        let e{};
        let r: {r_type} = {}{name}{const_value}({call_params}){};
        assert_eq!(r, e);
"#,
            values(out_t, &e),
            r_trans.0,
            r_trans.1,
        )
        .unwrap();
    }
    test.push_str("    }\n");
    test
}

#[allow(clippy::too_many_arguments)]
fn gen_arm(
    current_comment: &str,
    current_fn: &Option<String>,
    current_name: &str,
    current_arm: &str,
    link_arm: &Option<String>,
    current_aarch64: &Option<String>,
    link_aarch64: &Option<String>,
    const_arm: &Option<String>,
    const_aarch64: &Option<String>,
    constn: &Option<String>,
    in_t: &[&str; 3],
    out_t: &str,
    current_tests: &[(
        Vec<String>,
        Vec<String>,
        Vec<String>,
        Option<String>,
        Vec<String>,
    )],
    suffix: Suffix,
    para_num: i32,
    target: TargetFeature,
    fixed: &Vec<String>,
    multi_fn: &Vec<String>,
    fn_type: Fntype,
    separate: bool,
) -> (String, String) {
    let name = match suffix {
        Normal => format!("{current_name}{}", type_to_suffix(in_t[1])),
        NoQ => format!("{current_name}{}", type_to_noq_suffix(in_t[1])),
        Double => format!(
            "{}{}",
            current_name,
            type_to_double_suffixes(out_t, in_t[1])
        ),
        NoQDouble => format!(
            "{}{}",
            current_name,
            type_to_noq_double_suffixes(out_t, in_t[1])
        ),
        NSuffix => format!("{current_name}{}", type_to_n_suffix(in_t[1])),
        DoubleN => format!(
            "{}{}",
            current_name,
            type_to_double_n_suffixes(out_t, in_t[1])
        ),
        NoQNSuffix => format!("{current_name}{}", type_to_noq_n_suffix(in_t[1])),
        OutSuffix => format!("{current_name}{}", type_to_suffix(out_t)),
        OutNSuffix => format!("{current_name}{}", type_to_n_suffix(out_t)),
        OutNox => format!(
            "{}{}",
            current_name,
            type_to_suffix(&type_to_sub_type(out_t))
        ),
        In1Nox => format!(
            "{}{}",
            current_name,
            type_to_suffix(&type_to_sub_type(in_t[1]))
        ),
        OutDupNox => format!(
            "{}{}",
            current_name,
            type_to_dup_suffix(&type_to_sub_type(out_t))
        ),
        OutLaneNox => format!(
            "{}{}",
            current_name,
            type_to_lane_suffix(&type_to_sub_type(out_t))
        ),
        In1LaneNox => format!(
            "{}{}",
            current_name,
            type_to_lane_suffix(&type_to_sub_type(in_t[1]))
        ),
        Lane => format!(
            "{}{}",
            current_name,
            type_to_lane_suffixes(out_t, in_t[1], false)
        ),
        In2 => format!("{current_name}{}", type_to_suffix(in_t[2])),
        In2Lane => format!(
            "{}{}",
            current_name,
            type_to_lane_suffixes(out_t, in_t[2], false)
        ),
        OutLane => format!(
            "{}{}",
            current_name,
            type_to_lane_suffixes(out_t, in_t[2], true)
        ),
        Rot => type_to_rot_suffix(current_name, type_to_suffix(out_t)),
        RotLane => type_to_rot_suffix(current_name, &type_to_lane_suffixes(out_t, in_t[2], false)),
    };
    let current_aarch64 = current_aarch64
        .clone()
        .unwrap_or_else(|| current_arm.to_string());
    let current_fn = if let Some(current_fn) = current_fn.clone() {
        if link_aarch64.is_some() || link_arm.is_some() {
            panic!(
                "[{}] Can't specify link and function at the same time. {} / {:?} / {:?}",
                name, current_fn, link_aarch64, link_arm
            )
        }
        current_fn
    } else if link_aarch64.is_some() || link_arm.is_some() {
        format!("{name}_")
    } else {
        if multi_fn.is_empty() {
            panic!(
                "[{}] Either fn or link-arm and link-aarch have to be specified.",
                name
            )
        }
        String::new()
    };
    let mut ext_c = String::new();
    let mut ext_c_arm = if multi_fn.is_empty() || link_arm.is_none() {
        String::new()
    } else {
        String::from(
            r#"
    "#,
        )
    };
    let mut ext_c_aarch64 = if multi_fn.is_empty() || link_aarch64.is_none() {
        String::new()
    } else {
        String::from(
            r#"
    "#,
        )
    };
    let mut link_arm_t: Vec<String> = vec![
        in_t[0].to_string(),
        in_t[1].to_string(),
        in_t[2].to_string(),
        out_t.to_string(),
    ];
    let mut link_aarch64_t: Vec<String> = vec![
        in_t[0].to_string(),
        in_t[1].to_string(),
        in_t[2].to_string(),
        out_t.to_string(),
    ];
    if let (Some(mut link_arm), Some(mut link_aarch64)) = (link_arm.clone(), link_aarch64.clone()) {
        if link_arm.contains(':') {
            let links: Vec<_> = link_arm.split(':').map(|v| v.to_string()).collect();
            assert_eq!(links.len(), 5);
            link_arm = links[0].to_string();
            link_arm_t = vec![
                links[1].clone(),
                links[2].clone(),
                links[3].clone(),
                links[4].clone(),
            ];
        }
        if link_aarch64.contains(':') {
            let links: Vec<_> = link_aarch64.split(':').map(|v| v.to_string()).collect();
            assert_eq!(links.len(), 5);
            link_aarch64 = links[0].to_string();
            link_aarch64_t = vec![
                links[1].clone(),
                links[2].clone(),
                links[3].clone(),
                links[4].clone(),
            ];
        }
        let link_arm = if link_arm.starts_with("llvm") {
            ext(&link_arm, in_t, out_t)
        } else {
            let mut link = String::from("llvm.arm.neon.");
            link.push_str(&link_arm);
            ext(&link, in_t, out_t)
        };
        let link_aarch64 = if link_aarch64.starts_with("llvm") {
            ext(&link_aarch64, in_t, out_t)
        } else {
            let mut link = String::from("llvm.aarch64.neon.");
            link.push_str(&link_aarch64);
            ext(&link, in_t, out_t)
        };
        if out_t == link_arm_t[3] && out_t == link_aarch64_t[3] {
            ext_c = format!(
                r#"#[allow(improper_ctypes)]
    extern "unadjusted" {{
        #[cfg_attr(target_arch = "arm", link_name = "{}")]
        #[cfg_attr(target_arch = "aarch64", link_name = "{}")]
        fn {}({}) -> {};
    }}
"#,
                link_arm,
                link_aarch64,
                current_fn,
                match para_num {
                    1 => format!("a: {}", in_t[0]),
                    2 => format!("a: {}, b: {}", in_t[0], in_t[1]),
                    3 => format!("a: {}, b: {}, c: {}", in_t[0], in_t[1], in_t[2]),
                    _ => unimplemented!("unknown para_num"),
                },
                out_t
            );
        };
        let (arm_ext_inputs, arm_ext_output) = {
            if let Some(const_arm) = const_arm {
                if !matches!(fn_type, Fntype::Normal) {
                    let ptr_type = match fn_type {
                        Fntype::Load => "*const i8",
                        Fntype::Store => "*mut i8",
                        _ => panic!("unsupported fn type"),
                    };
                    let sub_type = type_to_sub_type(in_t[1]);
                    let inputs = match type_sub_len(in_t[1]) {
                        1 => format!("a: {sub_type}"),
                        2 => format!("a: {sub_type}, b: {sub_type}",),
                        3 => format!("a: {sub_type}, b: {sub_type}, c: {sub_type}",),
                        4 => format!(
                            "a: {}, b: {}, c: {}, d: {}",
                            sub_type, sub_type, sub_type, sub_type,
                        ),
                        _ => panic!("unknown type: {}", in_t[1]),
                    };
                    let out = if out_t == "void" {
                        String::new()
                    } else {
                        format!(" -> {out_t}")
                    };
                    (format!("ptr: {ptr_type}, {inputs}, n: i32, size: i32"), out)
                } else {
                    let (_, const_type) = if const_arm.contains(':') {
                        let consts: Vec<_> =
                            const_arm.split(':').map(|v| v.trim().to_string()).collect();
                        (consts[0].clone(), consts[1].clone())
                    } else {
                        (
                            const_arm.to_string(),
                            in_t[para_num as usize - 1].to_string(),
                        )
                    };
                    (
                        match para_num {
                            1 => format!("a: {}, n: {const_type}", in_t[0]),
                            2 => format!("a: {}, b: {}, n: {const_type}", in_t[0], in_t[1]),
                            3 => format!(
                                "a: {}, b: {}, c: {}, n: {const_type}",
                                in_t[0], in_t[1], in_t[2]
                            ),
                            _ => unimplemented!("unknown para_num"),
                        },
                        format!(" -> {out_t}"),
                    )
                }
            } else if out_t != link_arm_t[3] {
                (
                    match para_num {
                        1 => format!("a: {}", link_arm_t[0]),
                        2 => format!("a: {}, b: {}", link_arm_t[0], link_arm_t[1]),
                        3 => format!(
                            "a: {}, b: {}, c: {}",
                            link_arm_t[0], link_arm_t[1], link_arm_t[2]
                        ),
                        _ => unimplemented!("unknown para_num"),
                    },
                    format!(" -> {}", link_arm_t[3]),
                )
            } else if matches!(fn_type, Fntype::Store) {
                let sub_type = type_to_sub_type(in_t[1]);
                let inputs = match type_sub_len(in_t[1]) {
                    1 => format!("a: {sub_type}"),
                    2 => format!("a: {sub_type}, b: {sub_type}",),
                    3 => format!("a: {sub_type}, b: {sub_type}, c: {sub_type}",),
                    4 => format!(
                        "a: {}, b: {}, c: {}, d: {}",
                        sub_type, sub_type, sub_type, sub_type,
                    ),
                    _ => panic!("unknown type: {}", in_t[1]),
                };
                let (ptr_type, size) = if is_vstx(&name) {
                    ("i8".to_string(), ", size: i32")
                } else {
                    (type_to_native_type(in_t[1]), "")
                };
                (
                    format!("ptr: *mut {ptr_type}, {inputs}{size}"),
                    String::new(),
                )
            } else if is_vldx(&name) {
                (format!("ptr: *const i8, size: i32"), format!(" -> {out_t}"))
            } else {
                (String::new(), String::new())
            }
        };
        ext_c_arm.push_str(&format!(
            r#"#[allow(improper_ctypes)]
    extern "unadjusted" {{
        #[cfg_attr(target_arch = "arm", link_name = "{}")]
        fn {}({}){};
    }}
"#,
            link_arm, current_fn, arm_ext_inputs, arm_ext_output,
        ));
        let (aarch64_ext_inputs, aarch64_ext_output) = {
            if let Some(const_aarch64) = const_aarch64 {
                if !matches!(fn_type, Fntype::Normal) {
                    let ptr_type = match fn_type {
                        Fntype::Load => "*const i8",
                        Fntype::Store => "*mut i8",
                        _ => panic!("unsupported fn type"),
                    };
                    let sub_type = type_to_sub_type(in_t[1]);
                    let mut inputs = match type_sub_len(in_t[1]) {
                        1 => format!("a: {sub_type}",),
                        2 => format!("a: {sub_type}, b: {sub_type}",),
                        3 => format!("a: {sub_type}, b: {sub_type}, c: {sub_type}",),
                        4 => format!(
                            "a: {}, b: {}, c: {}, d: {}",
                            sub_type, sub_type, sub_type, sub_type,
                        ),
                        _ => panic!("unknown type: {}", in_t[1]),
                    };
                    inputs.push_str(&format!(", n: i64, ptr: {ptr_type}"));
                    let out = if out_t == "void" {
                        String::new()
                    } else {
                        format!(" -> {out_t}")
                    };
                    (inputs, out)
                } else if const_aarch64.contains("dup-in_len-N as ttn") {
                    (
                        match para_num {
                            1 => format!("a: {}, n: {}", in_t[0], in_t[0]),
                            2 => format!("a: {}, b: {}, n: {}", in_t[0], in_t[1], in_t[1]),
                            3 => format!(
                                "a: {}, b: {}, c: {}, n: {}",
                                in_t[0], in_t[1], in_t[2], in_t[1]
                            ),
                            _ => unimplemented!("unknown para_num"),
                        },
                        format!(" -> {out_t}"),
                    )
                } else {
                    (
                        match para_num {
                            1 => format!("a: {}, n: i32", in_t[0]),
                            2 => format!("a: {}, b: {}, n: i32", in_t[0], in_t[1]),
                            3 => format!("a: {}, b: {}, c: {}, n: i32", in_t[0], in_t[1], in_t[2]),
                            _ => unimplemented!("unknown para_num"),
                        },
                        format!(" -> {out_t}"),
                    )
                }
            } else if out_t != link_aarch64_t[3] {
                (
                    match para_num {
                        1 => format!("a: {}", link_aarch64_t[0]),
                        2 => format!("a: {}, b: {}", link_aarch64_t[0], link_aarch64_t[1]),
                        3 => format!(
                            "a: {}, b: {}, c: {}",
                            link_aarch64_t[0], link_aarch64_t[1], link_aarch64_t[2]
                        ),
                        _ => unimplemented!("unknown para_num"),
                    },
                    format!(" -> {}", link_aarch64_t[3]),
                )
            } else if matches!(fn_type, Fntype::Store) {
                let sub_type = type_to_sub_type(in_t[1]);
                let mut inputs = match type_sub_len(in_t[1]) {
                    1 => format!("a: {sub_type}",),
                    2 => format!("a: {sub_type}, b: {sub_type}",),
                    3 => format!("a: {sub_type}, b: {sub_type}, c: {sub_type}",),
                    4 => format!(
                        "a: {}, b: {}, c: {}, d: {}",
                        sub_type, sub_type, sub_type, sub_type,
                    ),
                    _ => panic!("unknown type: {}", in_t[1]),
                };
                let ptr_type = if is_vstx(&name) {
                    "i8".to_string()
                } else {
                    type_to_native_type(in_t[1])
                };
                inputs.push_str(&format!(", ptr: *mut {ptr_type}"));
                (inputs, String::new())
            } else if is_vldx(&name) {
                let ptr_type = if name.contains("dup") {
                    type_to_native_type(out_t)
                } else {
                    type_to_sub_type(out_t)
                };
                (format!("ptr: *const {ptr_type}"), format!(" -> {out_t}"))
            } else {
                (String::new(), String::new())
            }
        };
        ext_c_aarch64.push_str(&format!(
            r#"#[allow(improper_ctypes)]
    extern "unadjusted" {{
        #[cfg_attr(target_arch = "aarch64", link_name = "{}")]
        fn {}({}){};
    }}
"#,
            link_aarch64, current_fn, aarch64_ext_inputs, aarch64_ext_output,
        ));
    };
    let const_declare = if let Some(constn) = constn {
        format!(r#"<const {constn}: i32>"#)
    } else {
        String::new()
    };
    let multi_calls = if !multi_fn.is_empty() {
        let mut calls = String::new();
        for i in 0..multi_fn.len() {
            if i > 0 {
                calls.push_str("\n    ");
            }
            calls.push_str(&get_call(
                &multi_fn[i],
                current_name,
                in_t,
                out_t,
                fixed,
                None,
                false,
            ));
        }
        calls
    } else {
        String::new()
    };
    let const_assert = if let Some(constn) = constn {
        format!(
            r#", {} = {}"#,
            constn,
            map_val(in_t[1], current_tests[0].3.as_ref().unwrap())
        )
    } else {
        String::new()
    };
    let const_legacy = if constn.is_some() {
        format!("\n#[rustc_legacy_const_generics({para_num})]")
    } else {
        String::new()
    };
    let fn_decl = {
        let fn_output = if out_t == "void" {
            String::new()
        } else {
            format!("-> {out_t} ")
        };
        let fn_inputs = match para_num {
            1 => format!("(a: {})", in_t[0]),
            2 => format!("(a: {}, b: {})", in_t[0], in_t[1]),
            3 => format!("(a: {}, b: {}, c: {})", in_t[0], in_t[1], in_t[2]),
            _ => panic!("unsupported parameter number"),
        };
        format!(
            "pub unsafe fn {}{}{} {}",
            name, const_declare, fn_inputs, fn_output
        )
    };
    let function = if separate {
        let call_arm = {
            let arm_params = if let (Some(const_arm), Some(_)) = (const_arm, link_arm) {
                if !matches!(fn_type, Fntype::Normal) {
                    let subs = match type_sub_len(in_t[1]) {
                        1 => "b",
                        2 => "b.0, b.1",
                        3 => "b.0, b.1, b.2",
                        4 => "b.0, b.1, b.2, b.3",
                        _ => "",
                    };
                    format!(
                        "{}(a as _, {}, {}, {})",
                        current_fn,
                        subs,
                        constn.as_deref().unwrap(),
                        type_bits(&type_to_sub_type(in_t[1])) / 8,
                    )
                } else {
                    let cnt = if const_arm.contains(':') {
                        let consts: Vec<_> =
                            const_arm.split(':').map(|v| v.trim().to_string()).collect();
                        consts[0].clone()
                    } else {
                        let const_arm = const_arm.replace("ttn", &type_to_native_type(in_t[1]));
                        let mut cnt = String::from(in_t[1]);
                        cnt.push_str("(");
                        for i in 0..type_len(in_t[1]) {
                            if i != 0 {
                                cnt.push_str(", ");
                            }
                            cnt.push_str(&const_arm);
                        }
                        cnt.push_str(")");
                        cnt
                    };
                    match para_num {
                        1 => format!("{current_fn}(a, {cnt})"),
                        2 => format!("{current_fn}(a, b, {cnt})"),
                        _ => String::new(),
                    }
                }
            } else if out_t != link_arm_t[3] {
                match para_num {
                    1 => format!("transmute({current_fn}(a))",),
                    2 => format!("transmute({current_fn}(transmute(a), transmute(b)))",),
                    _ => String::new(),
                }
            } else if matches!(fn_type, Fntype::Store) {
                let (cast, size) = if is_vstx(&name) {
                    (
                        " as _",
                        format!(", {}", type_bits(&type_to_sub_type(in_t[1])) / 8),
                    )
                } else {
                    ("", String::new())
                };
                match type_sub_len(in_t[1]) {
                    1 => format!("{current_fn}(a{cast}, b{size})"),
                    2 => format!("{current_fn}(a{cast}, b.0, b.1{size})"),
                    3 => format!("{current_fn}(a{cast}, b.0, b.1, b.2{size})"),
                    4 => format!("{current_fn}(a{cast}, b.0, b.1, b.2, b.3{size})"),
                    _ => String::new(),
                }
            } else if link_arm.is_some() && is_vldx(&name) {
                format!(
                    "{}(a as *const i8, {})",
                    current_fn,
                    type_bits(&type_to_sub_type(out_t)) / 8
                )
            } else {
                String::new()
            };
            format!(
                r#"{}{{
    {}{}{}
}}"#,
                fn_decl, multi_calls, ext_c_arm, arm_params
            )
        };
        let call_aarch64 = {
            let aarch64_params =
                if let (Some(const_aarch64), Some(_)) = (const_aarch64, link_aarch64) {
                    if !matches!(fn_type, Fntype::Normal) {
                        let subs = match type_sub_len(in_t[1]) {
                            1 => "b",
                            2 => "b.0, b.1",
                            3 => "b.0, b.1, b.2",
                            4 => "b.0, b.1, b.2, b.3",
                            _ => "",
                        };
                        format!(
                            "{}({}, {} as i64, a as _)",
                            current_fn,
                            subs,
                            constn.as_deref().unwrap()
                        )
                    } else if const_aarch64.contains("dup-in_len-N as ttn") {
                        let const_aarch64 = format!("N as {}", type_to_native_type(in_t[1]));
                        let mut cnt = String::from(in_t[1]);
                        cnt.push_str("(");
                        for i in 0..type_len(in_t[1]) {
                            if i != 0 {
                                cnt.push_str(", ");
                            }
                            cnt.push_str(&const_aarch64);
                        }
                        cnt.push_str(")");
                        format!("{current_fn}(a, {cnt})")
                    } else {
                        match para_num {
                            1 => format!("{current_fn}(a, {const_aarch64})"),
                            2 => format!("{current_fn}(a, b, {const_aarch64})"),
                            _ => String::new(),
                        }
                    }
                } else if out_t != link_aarch64_t[3] {
                    match para_num {
                        1 => format!("transmute({current_fn}(a))",),
                        2 => format!("transmute({current_fn}(a, b))",),
                        _ => String::new(),
                    }
                } else if matches!(fn_type, Fntype::Store) {
                    let cast = if is_vstx(&name) { " as _" } else { "" };
                    match type_sub_len(in_t[1]) {
                        1 => format!("{current_fn}(b, a{cast})"),
                        2 => format!("{current_fn}(b.0, b.1, a{cast})"),
                        3 => format!("{current_fn}(b.0, b.1, b.2, a{cast})"),
                        4 => format!("{current_fn}(b.0, b.1, b.2, b.3, a{cast})"),
                        _ => String::new(),
                    }
                } else if link_aarch64.is_some() && is_vldx(&name) {
                    format!("{current_fn}(a as _)")
                } else {
                    String::new()
                };
            format!(
                r#"{}{{
    {}{}{}
}}"#,
                fn_decl, multi_calls, ext_c_aarch64, aarch64_params
            )
        };
        let stable_aarch64 = target.stability(true);
        let stable_arm = target.stability(false);
        let function_doc = create_doc_string(current_comment, &name);
        format!(
            r#"
{function_doc}
#[inline]
#[cfg(target_arch = "arm")]{target_feature_arm}
#[cfg_attr(test, assert_instr({assert_arm}{const_assert}))]{const_legacy}
#[{stable_arm}]
{call_arm}

{function_doc}
#[inline]
#[cfg(not(target_arch = "arm"))]{target_feature_aarch64}
#[cfg_attr(test, assert_instr({assert_aarch64}{const_assert}))]{const_legacy}
#[{stable_aarch64}]
{call_aarch64}
"#,
            target_feature_arm = target.to_target_feature_attr_arm(),
            target_feature_aarch64 = target.to_target_feature_attr_aarch64(),
            assert_arm = expand_intrinsic(current_arm, in_t[1]),
            assert_aarch64 = expand_intrinsic(&current_aarch64, in_t[1]),
        )
    } else {
        let call = {
            let stmts = match (multi_calls.len(), para_num, fixed.len()) {
                (0, 1, 0) => format!(r#"{ext_c}{current_fn}(a)"#,),
                (0, 1, _) => {
                    let fixed: Vec<String> =
                        fixed.iter().take(type_len(in_t[0])).cloned().collect();
                    format!(
                        r#"let b{};
    {}{}(a, transmute(b))"#,
                        values(in_t[0], &fixed),
                        ext_c,
                        current_fn,
                    )
                }
                (0, 2, _) => format!(r#"{ext_c}{current_fn}(a, b)"#,),
                (0, 3, _) => format!(r#"{ext_c}{current_fn}(a, b, c)"#,),
                (_, 1, _) => format!(r#"{ext_c}{multi_calls}"#,),
                (_, 2, _) => format!(r#"{ext_c}{multi_calls}"#,),
                (_, 3, _) => format!(r#"{ext_c}{multi_calls}"#,),
                (_, _, _) => String::new(),
            };
            if stmts != String::new() {
                format!(
                    r#"{}{{
    {}
}}"#,
                    fn_decl, stmts
                )
            } else {
                String::new()
            }
        };
        let stable_aarch64 = target.stability(true);
        let stable_arm = target.stability(false);
        format!(
            r#"
{function_doc}
#[inline]{target_feature}
#[cfg_attr(all(test, target_arch = "arm"), assert_instr({assert_arm}{const_assert}))]
#[cfg_attr(all(test, target_arch = "aarch64"), assert_instr({assert_aarch64}{const_assert}))]{const_legacy}
#[cfg_attr(not(target_arch = "arm"), {stable_aarch64})]
#[cfg_attr(target_arch = "arm", {stable_arm})]
{call}
"#,
            function_doc = create_doc_string(current_comment, &name),
            assert_arm = expand_intrinsic(current_arm, in_t[1]),
            assert_aarch64 = expand_intrinsic(&current_aarch64, in_t[1]),
            target_feature = target.to_target_feature_attr_shared(),
        )
    };
    let test = match fn_type {
        Fntype::Normal => gen_test(
            &name,
            in_t,
            out_t,
            current_tests,
            [type_len(in_t[0]), type_len(in_t[1]), type_len(in_t[2])],
            type_len(out_t),
            para_num,
            target.to_simd_test_attr_shared(),
        ),
        Fntype::Load => gen_load_test(&name, in_t, out_t, current_tests, type_len(out_t)),
        Fntype::Store => gen_store_test(&name, in_t, out_t, current_tests, type_len(in_t[1])),
    };
    (function, test)
}

fn expand_intrinsic(intr: &str, t: &str) -> String {
    if intr.ends_with('.') {
        let ext = match t {
            "int8x8_t" => "i8",
            "int8x16_t" => "i8",
            "int16x4_t" => "i16",
            "int16x8_t" => "i16",
            "int32x2_t" => "i32",
            "int32x4_t" => "i32",
            "int64x1_t" => "i64",
            "int64x2_t" => "i64",
            "uint8x8_t" => "i8",
            "uint8x16_t" => "i8",
            "uint16x4_t" => "i16",
            "uint16x8_t" => "i16",
            "uint32x2_t" => "i32",
            "uint32x4_t" => "i32",
            "uint64x1_t" => "i64",
            "uint64x2_t" => "i64",
            "float16x4_t" => "f16",
            "float16x8_t" => "f16",
            "float32x2_t" => "f32",
            "float32x4_t" => "f32",
            "float64x1_t" => "f64",
            "float64x2_t" => "f64",
            "poly8x8_t" => "i8",
            "poly8x16_t" => "i8",
            "poly16x4_t" => "i16",
            "poly16x8_t" => "i16",
            /*
            "poly64x1_t" => "i64x1",
            "poly64x2_t" => "i64x2",
            */
            _ => panic!("unknown type for extension: {t}"),
        };
        format!(r#""{intr}{ext}""#)
    } else if intr.ends_with(".s") {
        let ext = match t {
            "int8x8_t" => "s8",
            "int8x16_t" => "s8",
            "int16x4_t" => "s16",
            "int16x8_t" => "s16",
            "int32x2_t" => "s32",
            "int32x4_t" => "s32",
            "int64x1_t" => "s64",
            "int64x2_t" => "s64",
            "uint8x8_t" => "u8",
            "uint8x16_t" => "u8",
            "uint16x4_t" => "u16",
            "uint16x8_t" => "u16",
            "uint32x2_t" => "u32",
            "uint32x4_t" => "u32",
            "uint64x1_t" => "u64",
            "uint64x2_t" => "u64",
            "poly8x8_t" => "p8",
            "poly8x16_t" => "p8",
            "poly16x4_t" => "p16",
            "poly16x8_t" => "p16",
            "float16x4_t" => "f16",
            "float16x8_t" => "f16",
            "float32x2_t" => "f32",
            "float32x4_t" => "f32",
            "float64x1_t" => "f64",
            "float64x2_t" => "f64",
            /*
            "poly64x1_t" => "i64x1",
            "poly64x2_t" => "i64x2",
            */
            _ => panic!("unknown type for extension: {t}"),
        };
        format!(r#""{}{ext}""#, &intr[..intr.len() - 1])
    } else if intr.ends_with(".l") {
        let ext = match t {
            "int8x8_t" => "8",
            "int8x16_t" => "8",
            "int16x4_t" => "16",
            "int16x8_t" => "16",
            "int32x2_t" => "32",
            "int32x4_t" => "32",
            "int64x1_t" => "64",
            "int64x2_t" => "64",
            "uint8x8_t" => "8",
            "uint8x16_t" => "8",
            "uint16x4_t" => "16",
            "uint16x8_t" => "16",
            "uint32x2_t" => "32",
            "uint32x4_t" => "32",
            "uint64x1_t" => "64",
            "uint64x2_t" => "64",
            "poly8x8_t" => "8",
            "poly8x16_t" => "8",
            "poly16x4_t" => "16",
            "poly16x8_t" => "16",
            "float16x4_t" => "16",
            "float16x8_t" => "16",
            "float32x2_t" => "32",
            "float32x4_t" => "32",
            "float64x1_t" => "64",
            "float64x2_t" => "64",
            "poly64x1_t" => "64",
            "poly64x2_t" => "64",
            _ => panic!("unknown type for extension: {t}"),
        };
        format!(r#""{}{ext}""#, &intr[..intr.len() - 1])
    } else {
        intr.to_string()
    }
}

fn get_call(
    in_str: &str,
    current_name: &str,
    in_t: &[&str; 3],
    out_t: &str,
    fixed: &Vec<String>,
    n: Option<i32>,
    aarch64: bool,
) -> String {
    let params: Vec<_> = in_str.split(',').map(|v| v.trim().to_string()).collect();
    assert!(!params.is_empty());
    let mut fn_name = params[0].clone();
    if fn_name == "a" {
        return String::from("a");
    }
    if fn_name == "transpose-1-in_len" {
        return transpose1(type_len(in_t[1])).to_string();
    }
    if fn_name == "transpose-2-in_len" {
        return transpose2(type_len(in_t[1])).to_string();
    }
    if fn_name == "zip-1-in_len" {
        return zip1(type_len(in_t[1])).to_string();
    }
    if fn_name == "zip-2-in_len" {
        return zip2(type_len(in_t[1])).to_string();
    }
    if fn_name == "unzip-1-in_len" {
        return unzip1(type_len(in_t[1])).to_string();
    }
    if fn_name == "unzip-2-in_len" {
        return unzip2(type_len(in_t[1])).to_string();
    }
    if fn_name.starts_with("dup") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        let len = match &*fn_format[1] {
            "out_len" => type_len(out_t),
            "in_len" => type_len(in_t[1]),
            "in0_len" => type_len(in_t[0]),
            "halflen" => type_len(in_t[1]) / 2,
            _ => 0,
        };
        let mut s = format!("[");
        for i in 0..len {
            if i != 0 {
                s.push_str(", ");
            }
            s.push_str(&fn_format[2]);
        }
        s.push_str("]");
        return s;
    }
    if fn_name.starts_with("asc") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        let start = match &*fn_format[1] {
            "0" => 0,
            "n" => n.unwrap(),
            "out_len" => type_len(out_t) as i32,
            "halflen" => (type_len(in_t[1]) / 2) as i32,
            s => s.parse::<i32>().unwrap(),
        };
        let len = match &*fn_format[2] {
            "out_len" => type_len(out_t),
            "in_len" => type_len(in_t[1]),
            "in0_len" => type_len(in_t[0]),
            "halflen" => type_len(in_t[1]) / 2,
            _ => 0,
        };
        return asc(start, len);
    }
    if fn_name.starts_with("base") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        assert_eq!(fn_format.len(), 3);
        let mut s = format!("[");
        let base_len = fn_format[1].parse::<usize>().unwrap();
        for i in 0..type_len(in_t[1]) / base_len {
            for j in 0..base_len {
                if i != 0 || j != 0 {
                    s.push_str(", ");
                }
                s.push_str(&format!("{base_len} * {} as u32", &fn_format[2]));
                if j != 0 {
                    s.push_str(&format!(" + {j}"));
                }
            }
        }
        s.push_str("]");
        return s;
    }
    if fn_name.starts_with("as") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        assert_eq!(fn_format.len(), 3);
        let t = match &*fn_format[2] {
            "in_ttn" => type_to_native_type(in_t[1]),
            _ => String::new(),
        };
        return format!("{} as {t}", &fn_format[1]);
    }
    if fn_name.starts_with("ins") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        let n = n.unwrap();
        let len = match &*fn_format[1] {
            "out_len" => type_len(out_t),
            "in_len" => type_len(in_t[1]),
            "in0_len" => type_len(in_t[0]),
            _ => 0,
        };
        let offset = match &*fn_format[2] {
            "out_len" => type_len(out_t),
            "in_len" => type_len(in_t[1]),
            "in0_len" => type_len(in_t[0]),
            _ => 0,
        };
        let mut s = format!("[");
        for i in 0..len {
            if i != 0 {
                s.push_str(", ");
            }
            if i == n as usize {
                s.push_str(&format!("{} + {} as u32", offset.to_string(), fn_format[3]));
            } else {
                s.push_str(&i.to_string());
            }
        }
        s.push_str("]");
        return s;
    }
    if fn_name.starts_with("static_assert_imm") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        let len = match &*fn_format[1] {
            "out_exp_len" => type_exp_len(out_t, 1),
            "out_bits_exp_len" => type_bits_exp_len(out_t),
            "in_exp_len" => type_exp_len(in_t[1], 1),
            "in_bits_exp_len" => type_bits_exp_len(in_t[1]),
            "in0_exp_len" => type_exp_len(in_t[0], 1),
            "in1_exp_len" => type_exp_len(in_t[1], 1),
            "in2_exp_len" => type_exp_len(in_t[2], 1),
            "in2_rot" => type_exp_len(in_t[2], 2),
            "in2_dot" => type_exp_len(in_t[2], 4),
            _ => 0,
        };
        if len == 0 {
            return format!(r#"static_assert!({} == 0);"#, fn_format[2]);
        } else {
            return format!(r#"static_assert_uimm_bits!({}, {len});"#, fn_format[2]);
        }
    }
    if fn_name.starts_with("static_assert") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        let lim1 = if fn_format[2] == "bits" {
            type_bits(in_t[1]).to_string()
        } else if fn_format[2] == "halfbits" {
            (type_bits(in_t[1]) / 2).to_string()
        } else {
            fn_format[2].clone()
        };
        let lim2 = if fn_format[3] == "bits" {
            type_bits(in_t[1]).to_string()
        } else if fn_format[3] == "halfbits" {
            (type_bits(in_t[1]) / 2).to_string()
        } else {
            fn_format[3].clone()
        };
        if lim1 == lim2 {
            return format!(r#"static_assert!({} == {lim1});"#, fn_format[1]);
        } else {
            return format!(
                r#"static_assert!({} >= {lim1} && {} <= {lim2});"#,
                fn_format[1], fn_format[1]
            );
        }
    }
    if fn_name.starts_with("fix_right_shift_imm") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        let lim = if fn_format[2] == "bits" {
            type_bits(in_t[1]).to_string()
        } else {
            fn_format[2].clone()
        };
        let fixed = if in_t[1].starts_with('u') {
            format!("return vdup{nself}(0);", nself = type_to_n_suffix(in_t[1]))
        } else {
            (lim.parse::<i32>().unwrap() - 1).to_string()
        };

        return format!(
            r#"let {name}: i32 = if {const_name} == {upper} {{ {fixed} }} else {{ N }};"#,
            name = fn_format[1].to_lowercase(),
            const_name = fn_format[1],
            upper = lim,
            fixed = fixed,
        );
    }

    if fn_name.starts_with("matchn") {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        let len = match &*fn_format[1] {
            "out_exp_len" => type_exp_len(out_t, 1),
            "in_exp_len" => type_exp_len(in_t[1], 1),
            "in0_exp_len" => type_exp_len(in_t[0], 1),
            _ => 0,
        };
        let mut call = format!("match {} & 0b{} {{\n", &fn_format[2], "1".repeat(len));
        let mut sub_call = String::new();
        for p in 1..params.len() {
            if !sub_call.is_empty() {
                sub_call.push_str(", ");
            }
            sub_call.push_str(&params[p]);
        }
        for i in 0..(2u32.pow(len as u32) as usize) {
            let sub_match = format!(
                "        {} => {},\n",
                i,
                get_call(
                    &sub_call,
                    current_name,
                    in_t,
                    out_t,
                    fixed,
                    Some(i as i32),
                    aarch64
                )
            );
            call.push_str(&sub_match);
        }
        call.push_str("        _ => unreachable_unchecked(),\n    }");
        return call;
    }
    let mut re: Option<(String, String)> = None;
    let mut param_str = String::new();
    let mut i = 1;
    while i < params.len() {
        let s = &params[i];
        if s.starts_with('{') {
            let mut sub_fn = String::new();
            let mut parentheses = 0;
            while i < params.len() {
                if !sub_fn.is_empty() {
                    sub_fn.push_str(", ");
                }
                sub_fn.push_str(&params[i]);
                let l = params[i].len();
                for j in 0..l {
                    if &params[i][j..j + 1] == "{" {
                        parentheses += 1;
                    } else {
                        break;
                    }
                }
                for j in 0..l {
                    if &params[i][l - j - 1..l - j] == "}" {
                        parentheses -= 1;
                    } else {
                        break;
                    }
                }
                if parentheses == 0 {
                    break;
                }
                i += 1;
            }
            let sub_call = get_call(
                &sub_fn[1..sub_fn.len() - 1],
                current_name,
                in_t,
                out_t,
                fixed,
                n.clone(),
                aarch64,
            );
            if !param_str.is_empty() {
                param_str.push_str(", ");
            }
            param_str.push_str(&sub_call);
        } else if s.contains(':') {
            let re_params: Vec<_> = s.split(':').map(|v| v.to_string()).collect();
            #[allow(clippy::if_same_then_else)]
            if re_params[1] == "" {
                re = Some((re_params[0].clone(), in_t[1].to_string()));
            } else if re_params[1] == "in_t" {
                re = Some((re_params[0].clone(), in_t[1].to_string()));
            } else if re_params[1] == "signed" {
                re = Some((re_params[0].clone(), type_to_signed(in_t[1])));
            } else if re_params[1] == "unsigned" {
                re = Some((re_params[0].clone(), type_to_unsigned(in_t[1])));
            } else if re_params[1] == "in_t0" {
                re = Some((re_params[0].clone(), in_t[0].to_string()));
            } else if re_params[1] == "in_t1" {
                re = Some((re_params[0].clone(), in_t[1].to_string()));
            } else if re_params[1] == "out_t" {
                re = Some((re_params[0].clone(), out_t.to_string()));
            } else if re_params[1] == "out_unsigned" {
                re = Some((re_params[0].clone(), type_to_unsigned(out_t).to_string()));
            } else if re_params[1] == "out_signed" {
                re = Some((re_params[0].clone(), type_to_signed(out_t).to_string()));
            } else if re_params[1] == "merge4_t2" {
                re = Some((re_params[0].clone(), type_with_merged_lanes(in_t[2], 4)));
            } else if re_params[1] == "half" {
                re = Some((re_params[0].clone(), type_to_half(in_t[1]).to_string()));
            } else if re_params[1] == "in_ntt" {
                re = Some((
                    re_params[0].clone(),
                    native_type_to_type(in_t[1]).to_string(),
                ));
            } else if re_params[1] == "in_long_ntt" {
                re = Some((
                    re_params[0].clone(),
                    native_type_to_long_type(in_t[1]).to_string(),
                ));
            } else if re_params[1] == "out_ntt" {
                re = Some((re_params[0].clone(), native_type_to_type(out_t).to_string()));
            } else if re_params[1] == "out_long_ntt" {
                re = Some((
                    re_params[0].clone(),
                    native_type_to_long_type(out_t).to_string(),
                ));
            } else {
                re = Some((re_params[0].clone(), re_params[1].clone()));
            }
        } else {
            if !param_str.is_empty() {
                param_str.push_str(", ");
            }
            param_str.push_str(s);
        }
        i += 1;
    }
    if fn_name == "fixed" {
        let (re_name, re_type) = re.unwrap();
        let fixed: Vec<String> = fixed.iter().take(type_len(in_t[1])).cloned().collect();
        return format!(r#"let {re_name}{};"#, values(&re_type, &fixed));
    }
    if fn_name == "fixed-half-right" {
        let fixed: Vec<String> = fixed.iter().take(type_len(in_t[1])).cloned().collect();
        let half = fixed[type_len(in_t[1]) / 2..]
            .iter()
            .fold(String::new(), |mut s, fix| {
                s.push_str(fix);
                s.push_str(", ");
                s
            });
        return format!(r#"[{}]"#, &half[..half.len() - 2]);
    }
    if fn_name == "a - b" {
        return fn_name;
    }
    if fn_name == "-a" {
        return fn_name;
    }
    if fn_name.contains('-') {
        let fn_format: Vec<_> = fn_name.split('-').map(|v| v.to_string()).collect();
        assert_eq!(fn_format.len(), 3);
        fn_name = if fn_format[0] == "self" {
            current_name.to_string()
        } else {
            fn_format[0].clone()
        };
        if fn_format[1] == "self" {
            fn_name.push_str(type_to_suffix(in_t[1]));
        } else if fn_format[1] == "nself" {
            fn_name.push_str(type_to_n_suffix(in_t[1]));
        } else if fn_format[1] == "nselfvfp4" {
            fn_name.push_str(type_to_n_suffix(in_t[1]));
            if !aarch64 {
                fn_name.push_str("_vfp4");
            }
        } else if fn_format[1] == "out" {
            fn_name.push_str(type_to_suffix(out_t));
        } else if fn_format[1] == "in0" {
            fn_name.push_str(type_to_suffix(in_t[0]));
        } else if fn_format[1] == "in2" {
            fn_name.push_str(type_to_suffix(in_t[2]));
        } else if fn_format[1] == "in2lane" {
            fn_name.push_str(&type_to_lane_suffixes(out_t, in_t[2], false));
        } else if fn_format[1] == "outlane" {
            fn_name.push_str(&type_to_lane_suffixes(out_t, in_t[2], true));
        } else if fn_format[1] == "signed" {
            fn_name.push_str(type_to_suffix(&type_to_signed(&String::from(in_t[1]))));
        } else if fn_format[1] == "outsigned" {
            fn_name.push_str(type_to_suffix(&type_to_signed(&String::from(out_t))));
        } else if fn_format[1] == "outsignednox" {
            fn_name.push_str(type_to_suffix(&type_to_sub_type(&type_to_signed(
                &String::from(out_t),
            ))));
        } else if fn_format[1] == "in1signednox" {
            fn_name.push_str(type_to_suffix(&type_to_sub_type(&type_to_signed(
                &String::from(in_t[1]),
            ))));
        } else if fn_format[1] == "outsigneddupnox" {
            fn_name.push_str(&type_to_dup_suffix(&type_to_sub_type(&type_to_signed(
                &String::from(out_t),
            ))));
        } else if fn_format[1] == "outsignedlanenox" {
            fn_name.push_str(&type_to_lane_suffix(&type_to_sub_type(&type_to_signed(
                &String::from(out_t),
            ))));
        } else if fn_format[1] == "in1signedlanenox" {
            fn_name.push_str(&type_to_lane_suffix(&type_to_sub_type(&type_to_signed(
                &String::from(in_t[1]),
            ))));
        } else if fn_format[1] == "unsigned" {
            fn_name.push_str(type_to_suffix(&type_to_unsigned(in_t[1])));
        } else if fn_format[1] == "doubleself" {
            fn_name.push_str(&type_to_double_suffixes(out_t, in_t[1]));
        } else if fn_format[1] == "noq_doubleself" {
            fn_name.push_str(&type_to_noq_double_suffixes(out_t, in_t[1]));
        } else if fn_format[1] == "noqself" {
            fn_name.push_str(type_to_noq_suffix(in_t[1]));
        } else if fn_format[1] == "noqsigned" {
            fn_name.push_str(type_to_noq_suffix(&type_to_signed(&String::from(in_t[1]))));
        } else if fn_format[1] == "nosuffix" {
        } else if fn_format[1] == "in_len" {
            fn_name.push_str(&type_len(in_t[1]).to_string());
        } else if fn_format[1] == "in0_len" {
            fn_name.push_str(&type_len(in_t[0]).to_string());
        } else if fn_format[1] == "out_len" {
            fn_name.push_str(&type_len(out_t).to_string());
        } else if fn_format[1] == "halflen" {
            fn_name.push_str(&(type_len(in_t[1]) / 2).to_string());
        } else if fn_format[1] == "nout" {
            fn_name.push_str(type_to_n_suffix(out_t));
        } else if fn_format[1] == "nin0" {
            fn_name.push_str(type_to_n_suffix(in_t[0]));
        } else if fn_format[1] == "nsigned" {
            fn_name.push_str(type_to_n_suffix(&type_to_signed(&String::from(in_t[1]))));
        } else if fn_format[1] == "in_ntt" {
            fn_name.push_str(type_to_suffix(native_type_to_type(in_t[1])));
        } else if fn_format[1] == "out_ntt" {
            fn_name.push_str(type_to_suffix(native_type_to_type(out_t)));
        } else if fn_format[1] == "rot" {
            fn_name = type_to_rot_suffix(&fn_name, type_to_suffix(out_t));
        } else {
            fn_name.push_str(&fn_format[1]);
        };
        if fn_format[2] == "ext" {
            fn_name.push_str("_");
        } else if fn_format[2] == "noext" {
        } else if fn_format[2].starts_with('<') {
            assert!(fn_format[2].ends_with('>'));
            let types: Vec<_> = fn_format[2][1..fn_format[2].len() - 1]
                .split(' ')
                .map(|v| v.to_string())
                .collect();
            assert_eq!(types.len(), 2);
            let type1 = if types[0] == "element_t" {
                type_to_native_type(in_t[1])
            } else {
                String::from(&types[0])
            };
            let type2 = if types[1] == "element_t" {
                type_to_native_type(in_t[1])
            } else {
                String::from(&types[1])
            };
            fn_name.push_str(&format!("::<{}, {}>", &type1, &type2));
        } else {
            fn_name.push_str(&fn_format[2]);
        }
    }
    if param_str.is_empty() {
        return fn_name.replace("out_t", out_t);
    }
    let fn_str = if let Some((re_name, re_type)) = re.clone() {
        format!(
            r#"let {}: {} = {}({});"#,
            re_name, re_type, fn_name, param_str
        )
    } else if fn_name.starts_with('*') {
        format!(r#"{fn_name} = {param_str};"#)
    } else {
        format!(r#"{fn_name}({param_str})"#)
    };
    return fn_str;
}

fn main() -> io::Result<()> {
    let args: Vec<String> = env::args().collect();
    let in_file = args.get(1).cloned().unwrap_or_else(|| IN.to_string());

    let f = File::open(in_file).expect("Failed to open neon.spec");
    let f = BufReader::new(f);

    let mut current_comment = String::new();
    let mut current_name: Option<String> = None;
    let mut current_fn: Option<String> = None;
    let mut current_arm: Option<String> = None;
    let mut current_aarch64: Option<String> = None;
    let mut link_arm: Option<String> = None;
    let mut link_aarch64: Option<String> = None;
    let mut const_arm: Option<String> = None;
    let mut const_aarch64: Option<String> = None;
    let mut constn: Option<String> = None;
    let mut para_num = 2;
    let mut suffix: Suffix = Normal;
    let mut a: Vec<String> = Vec::new();
    let mut b: Vec<String> = Vec::new();
    let mut c: Vec<String> = Vec::new();
    let mut n: Option<String> = None;
    let mut fixed: Vec<String> = Vec::new();
    let mut current_tests: Vec<(
        Vec<String>,
        Vec<String>,
        Vec<String>,
        Option<String>,
        Vec<String>,
    )> = Vec::new();
    let mut multi_fn: Vec<String> = Vec::new();
    let mut target: TargetFeature = Default;
    let mut fn_type: Fntype = Fntype::Normal;
    let mut separate = false;

    //
    // THIS FILE IS GENERATED FORM neon.spec DO NOT CHANGE IT MANUALLY
    //
    let mut out_arm = String::from(
        r#"// This code is automatically generated. DO NOT MODIFY.
//
// Instead, modify `crates/stdarch-gen/neon.spec` and run the following command to re-generate this file:
//
// ```
// OUT_DIR=`pwd`/crates/core_arch cargo run -p stdarch-gen -- crates/stdarch-gen/neon.spec
// ```
use super::*;
#[cfg(test)]
use stdarch_test::assert_instr;
"#,
    );
    let mut tests_arm = String::from(
        r#"
#[cfg(test)]
#[allow(overflowing_literals)]
mod test {
    use super::*;
    use crate::core_arch::simd::*;
    use std::mem::transmute;
    use stdarch_test::simd_test;
"#,
    );
    //
    // THIS FILE IS GENERATED FORM neon.spec DO NOT CHANGE IT MANUALLY
    //
    let mut out_aarch64 = String::from(
        r#"// This code is automatically generated. DO NOT MODIFY.
//
// Instead, modify `crates/stdarch-gen/neon.spec` and run the following command to re-generate this file:
//
// ```
// OUT_DIR=`pwd`/crates/core_arch cargo run -p stdarch-gen -- crates/stdarch-gen/neon.spec
// ```
use super::*;
#[cfg(test)]
use stdarch_test::assert_instr;
"#,
    );
    let mut tests_aarch64 = String::from(
        r#"
#[cfg(test)]
mod test {
    use super::*;
    use crate::core_arch::simd::*;
    use std::mem::transmute;
    use stdarch_test::simd_test;
"#,
    );

    for line in f.lines() {
        let line = line.unwrap();
        if line.is_empty() {
            continue;
        }
        if line.starts_with("/// ") {
            current_comment = line;
            current_name = None;
            current_fn = None;
            current_arm = None;
            current_aarch64 = None;
            link_aarch64 = None;
            link_arm = None;
            const_aarch64 = None;
            const_arm = None;
            current_tests = Vec::new();
            constn = None;
            para_num = 2;
            suffix = Normal;
            a = Vec::new();
            b = Vec::new();
            c = Vec::new();
            fixed = Vec::new();
            n = None;
            multi_fn = Vec::new();
            target = Default;
            fn_type = Fntype::Normal;
            separate = false;
        } else if line.starts_with("//") {
        } else if let Some(line) = line.strip_prefix("name = ") {
            current_name = Some(String::from(line));
        } else if let Some(line) = line.strip_prefix("fn = ") {
            current_fn = Some(String::from(line));
        } else if let Some(line) = line.strip_prefix("multi_fn = ") {
            multi_fn.push(String::from(line));
        } else if let Some(line) = line.strip_prefix("constn = ") {
            constn = Some(String::from(line));
        } else if let Some(line) = line.strip_prefix("arm = ") {
            current_arm = Some(String::from(line));
        } else if let Some(line) = line.strip_prefix("aarch64 = ") {
            current_aarch64 = Some(String::from(line));
        } else if line.starts_with("double-suffixes") {
            suffix = Double;
        } else if line.starts_with("no-q") {
            suffix = NoQ;
        } else if line.starts_with("noq-double-suffixes") {
            suffix = NoQDouble;
        } else if line.starts_with("n-suffix") {
            suffix = NSuffix;
        } else if line.starts_with("double-n-suffixes") {
            suffix = DoubleN;
        } else if line.starts_with("out-n-suffix") {
            suffix = OutNSuffix;
        } else if line.starts_with("noq-n-suffix") {
            suffix = NoQNSuffix;
        } else if line.starts_with("out-suffix") {
            suffix = OutSuffix;
        } else if line.starts_with("out-nox") {
            suffix = OutNox;
        } else if line.starts_with("in1-nox") {
            suffix = In1Nox;
        } else if line.starts_with("out-dup-nox") {
            suffix = OutDupNox;
        } else if line.starts_with("out-lane-nox") {
            suffix = OutLaneNox;
        } else if line.starts_with("in1-lane-nox") {
            suffix = In1LaneNox;
        } else if line.starts_with("lane-suffixes") {
            suffix = Lane;
        } else if line.starts_with("in2-suffix") {
            suffix = In2;
        } else if line.starts_with("in2-lane-suffixes") {
            suffix = In2Lane;
        } else if line.starts_with("out-lane-suffixes") {
            suffix = OutLane;
        } else if line.starts_with("rot-suffix") {
            suffix = Rot;
        } else if line.starts_with("rot-lane-suffixes") {
            suffix = RotLane;
        } else if let Some(line) = line.strip_prefix("a = ") {
            a = line.split(',').map(|v| v.trim().to_string()).collect();
        } else if let Some(line) = line.strip_prefix("b = ") {
            b = line.split(',').map(|v| v.trim().to_string()).collect();
        } else if let Some(line) = line.strip_prefix("c = ") {
            c = line.split(',').map(|v| v.trim().to_string()).collect();
        } else if let Some(line) = line.strip_prefix("n = ") {
            n = Some(String::from(line));
        } else if let Some(line) = line.strip_prefix("fixed = ") {
            fixed = line.split(',').map(|v| v.trim().to_string()).collect();
        } else if let Some(line) = line.strip_prefix("validate ") {
            let e = line.split(',').map(|v| v.trim().to_string()).collect();
            current_tests.push((a.clone(), b.clone(), c.clone(), n.clone(), e));
        } else if let Some(line) = line.strip_prefix("link-aarch64 = ") {
            link_aarch64 = Some(String::from(line));
        } else if let Some(line) = line.strip_prefix("const-aarch64 = ") {
            const_aarch64 = Some(String::from(line));
        } else if let Some(line) = line.strip_prefix("link-arm = ") {
            link_arm = Some(String::from(line));
        } else if let Some(line) = line.strip_prefix("const-arm = ") {
            const_arm = Some(String::from(line));
        } else if line.starts_with("load_fn") {
            fn_type = Fntype::Load;
        } else if line.starts_with("store_fn") {
            fn_type = Fntype::Store;
        } else if line.starts_with("arm-aarch64-separate") {
            separate = true;
        } else if let Some(line) = line.strip_prefix("target = ") {
            target = match Some(String::from(line)) {
                Some(input) => match input.as_str() {
                    "v7" => ArmV7,
                    "vfp4" => Vfp4,
                    "fp-armv8" => FPArmV8,
                    "aes" => AES,
                    "fcma" => FCMA,
                    "dotprod" => Dotprod,
                    "i8mm" => I8MM,
                    "sha3" => SHA3,
                    "rdm" => RDM,
                    "sm4" => SM4,
                    "frintts" => FTTS,
                    _ => Default,
                },
                _ => Default,
            }
        } else if let Some(line) = line.strip_prefix("generate ") {
            let types: Vec<String> = line
                .split(',')
                .map(|v| v.trim().to_string())
                .flat_map(|v| match v.as_str() {
                    "uint*_t" => UINT_TYPES.iter().map(|v| v.to_string()).collect(),
                    "uint64x*_t" => UINT_TYPES_64.iter().map(|v| v.to_string()).collect(),
                    "int*_t" => INT_TYPES.iter().map(|v| v.to_string()).collect(),
                    "int64x*_t" => INT_TYPES_64.iter().map(|v| v.to_string()).collect(),
                    "float*_t" => FLOAT_TYPES.iter().map(|v| v.to_string()).collect(),
                    "float64x*_t" => FLOAT_TYPES_64.iter().map(|v| v.to_string()).collect(),
                    _ => vec![v],
                })
                .collect();

            for line in types {
                let spec: Vec<&str> = line.split(':').map(|e| e.trim()).collect();
                let in_t: [&str; 3];
                let out_t;
                if spec.len() == 1 {
                    in_t = [spec[0], spec[0], spec[0]];
                    out_t = spec[0];
                } else if spec.len() == 2 {
                    in_t = [spec[0], spec[0], spec[0]];
                    out_t = spec[1];
                } else if spec.len() == 3 {
                    in_t = [spec[0], spec[1], spec[1]];
                    out_t = spec[2];
                } else if spec.len() == 4 {
                    in_t = [spec[0], spec[1], spec[2]];
                    out_t = spec[3];
                } else {
                    panic!("Bad spec: {line}")
                }
                if b.is_empty() {
                    if matches!(fn_type, Fntype::Store) {
                        para_num = 2;
                    } else {
                        para_num = 1;
                    }
                } else if !c.is_empty() {
                    para_num = 3;
                }
                let current_name = current_name.clone().unwrap();
                if let Some(current_arm) = current_arm.clone() {
                    let (function, test) = gen_arm(
                        &current_comment,
                        &current_fn,
                        &current_name,
                        &current_arm,
                        &link_arm,
                        &current_aarch64,
                        &link_aarch64,
                        &const_arm,
                        &const_aarch64,
                        &constn,
                        &in_t,
                        out_t,
                        &current_tests,
                        suffix,
                        para_num,
                        target,
                        &fixed,
                        &multi_fn,
                        fn_type,
                        separate,
                    );
                    out_arm.push_str(&function);
                    tests_arm.push_str(&test);
                } else {
                    let (function, test) = gen_aarch64(
                        &current_comment,
                        &current_fn,
                        &current_name,
                        &current_aarch64,
                        &link_aarch64,
                        &const_aarch64,
                        &constn,
                        &in_t,
                        out_t,
                        &current_tests,
                        suffix,
                        para_num,
                        target,
                        &fixed,
                        &multi_fn,
                        fn_type,
                    );
                    out_aarch64.push_str(&function);
                    tests_aarch64.push_str(&test);
                }
            }
        }
    }
    tests_arm.push('}');
    tests_arm.push('\n');
    tests_aarch64.push('}');
    tests_aarch64.push('\n');

    let arm_out_path: PathBuf =
        PathBuf::from(env::var("OUT_DIR").unwrap_or("crates/core_arch".to_string()))
            .join("src")
            .join("arm_shared")
            .join("neon");
    std::fs::create_dir_all(&arm_out_path)?;

    let mut file_arm = File::create(arm_out_path.join(ARM_OUT))?;
    file_arm.write_all(out_arm.as_bytes())?;
    file_arm.write_all(tests_arm.as_bytes())?;

    let aarch64_out_path: PathBuf =
        PathBuf::from(env::var("OUT_DIR").unwrap_or("crates/core_arch".to_string()))
            .join("src")
            .join("aarch64")
            .join("neon");
    std::fs::create_dir_all(&aarch64_out_path)?;

    let mut file_aarch = File::create(aarch64_out_path.join(AARCH64_OUT))?;
    file_aarch.write_all(out_aarch64.as_bytes())?;
    file_aarch.write_all(tests_aarch64.as_bytes())?;
    /*
    if let Err(e) = Command::new("rustfmt")
        .arg(&arm_out_path)
        .arg(&aarch64_out_path)
        .status() {
            eprintln!("Could not format `{}`: {e}", arm_out_path.to_str().unwrap());
            eprintln!("Could not format `{}`: {e}", aarch64_out_path.to_str().unwrap());
    };
    */
    Ok(())
}
