use std::collections::HashMap;

use crate::shader_parser::Shader;
use anyhow::{Context, Result};
use regex::Regex;

/// Attempts to parse the code dump as an ir3 shader.
pub fn parse(code: &str) -> Result<Shader> {
    lazy_static! {
        static ref SHA1_RE: Regex =
            Regex::new("Native code for .* ([[:alpha:]]+) shader.*with sha1 ([0-9a-f]+):").unwrap();
        static ref STATS_RE: Regex =
            Regex::new("MESA: info: .* prog [[:digit:]]+/[[:digit:]]+: (.*)").unwrap();
    }
    let captures = SHA1_RE
        .captures(code)
        .with_context(|| format!("Parsing shader header in {code}"))?;
    let stage = &captures[1];
    let sha1 = &captures[2];

    let mut stats = HashMap::new();
    for line in code.lines() {
        if let Some(captures) = STATS_RE.captures(line) {
            let stats_list = &captures[1];

            for stat in stats_list.split(", ") {
                let (count, stat) = stat.split_once(" ").unwrap();
                stats.insert(stat.to_owned(), str::parse::<u64>(count).unwrap());
            }
        }
    }
    stats.insert(
        "max_parallel_sam".to_string(),
        max_parallel_sam(code) as u64,
    );

    stats.insert("double_rate_f16".to_string(), double_rate_f16(code) as u64);

    Ok(Shader {
        sha1: sha1.to_owned(),
        stage: stage.to_owned(),
        code: code.to_owned(),
        stats,
    })
}

fn post_preamble_instrs(code: &str) -> &str {
    if let Some((_, after)) = code.split_once("(sy)(ss)shpe") {
        after
    } else {
        code
    }
}

fn max_parallel_sam(code: &str) -> usize {
    let mut max_parallel_sam = 0;
    let mut parallel_sam = 0;

    for line in post_preamble_instrs(code).lines() {
        let line = line.strip_prefix("MESA: info: ").unwrap_or(line);

        if line.starts_with("(sy)") || line.contains("(jp)") || line.starts_with("jump") {
            max_parallel_sam = max_parallel_sam.max(parallel_sam);
            parallel_sam = 0;
        }

        let line = line.strip_prefix("(sy)").unwrap_or(line);
        let line = line.strip_prefix("(ss)").unwrap_or(line);
        let line = line.strip_prefix("(jp)").unwrap_or(line);
        if line.starts_with("sam") || line.starts_with("isam") || line.starts_with("gather") {
            parallel_sam += 1;
        }
    }
    max_parallel_sam.max(parallel_sam)
}

fn double_rate_f16(code: &str) -> usize {
    post_preamble_instrs(code)
        .lines()
        .filter(|line| {
            // if we've got a half float dest and a src, we're half float.
            line.split("hr").count() + line.split("hc").count() - 2 >= 2
        })
        .count()
}
#[cfg(test)]
mod tests {
    use crate::shader_parser;

    use super::*;

    static SINGLE_IR3_SHADER: &str = include_str!("test_data/single-ir3-shader.txt");

    #[test]
    fn test_parsing() {
        let bvert = "MESA: info:     div 32x4  %74 = fmul %18, %6.yyyy
MESA: info:     div 32x4  %77 = ffma %10, %6.xxxx, %74
MESA: info:     con 32x4  %30 = @load_const_ir3 (%5 (0x0)) (base=8)
MESA: info:     div 32x4  %80 = ffma %30, %6.zzzz, %77
MESA: info:     con 32x4  %42 = @load_const_ir3 (%5 (0x0)) (base=12)
MESA: info:     div 32x4  %83 = ffma %42, %6.wwww, %80
MESA: info:                     @store_output (%83, %5 (0x0)) (base=0, range=1, wrmask=xyzw, component=0, src_type=float32, io location=VARYING_SLOT_POS slots=1, xfb(), xfb2())
MESA: info:                     // succs: b7
MESA: info:     block b7:
MESA: info: }
MESA: info: 
MESA: info: Native code for unnamed BVERT shader (null) with sha1 f53df4f2509a8fcae820125c175eeebc8fa6954a:
MESA: info: ; num_ubos:           0
MESA: info: ; num_driver_params:  0
MESA: info: ; offsets:
MESA: info: ;   ubo_ranges                 c0.x (4 vec4)
MESA: info: ;   primitive_param            c4.x (1 vec4)
MESA: info: ; ubo_state:
MESA: info: ;   num_enabled:      1
MESA: info: ;   range[0]:
MESA: info: ;     block:          0
MESA: info: ;     bindless_base:  1
MESA: info: ;     offset:         c0.x
MESA: info: ;     size:           4 vec4 (0b -> 64b)
MESA: info: @in(r0.x)	in0 (wrmask=0xf)
MESA: info: shps #l6
MESA: info: getone #l6
MESA: info: mova1 a1.x, (r)0
MESA: info: (ss)nop
MESA: info: ldc.4.k.imm.base1 c[a1.x], 0, 0
MESA: info: (sy)(ss)shpe
MESA: info: l6:
MESA: info: (jp)mul.f r1.x, c1.x, r0.y
MESA: info: mul.f r2.y, c1.y, r0.y
MESA: info: mad.f32 r1.x, c0.x, r0.x, (last)r1.x
MESA: info: mad.f32 r2.y, c0.y, r0.x, (last)r2.y
MESA: info: mad.f32 r1.y, c2.x, r0.z, (last)r1.x
MESA: info: (rpt1)mul.f r2.z, (r)c1.z, (last)r0.y
MESA: info: mad.f32 r1.z, c2.y, r0.z, (last)r2.y
MESA: info: (rpt1)mad.f32 r2.z, (r)c0.z, r0.x, (r)(last)r2.z
MESA: info: (rpt1)mad.f32 r1.w, (r)c2.z, r0.z, (r)(last)r2.z
MESA: info: (rpt3)mad.f32 r3.x, (r)c3.x, r0.w, (r)(last)r1.y
MESA: info: end
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: ; BVERT: outputs: r3.x (VARYING_SLOT_POS)
MESA: info: ; BVERT: inputs: r0.x (VERT_ATTRIB_GENERIC0 slot=15 cm=f,il=0,b=0)
MESA: info: ; BVERT prog 128/2: 17 instr, 0 nops, 17 non-nops, 0 mov, 0 cov, 64 dwords
MESA: info: ; BVERT prog 128/2: 0 last-baryf, 0 last-helper, 0 half, 4 full, 8 constlen
MESA: info: ; BVERT prog 128/2: 1 cat0, 0 cat1, 4 cat2, 12 cat3, 0 cat4, 0 cat5, 0 cat6, 0 cat7
MESA: info: ; BVERT prog 128/2: 0 sstall, 0 (ss), 0 systall, 0 (sy), 0 loops
MESA: info: ; 6 preamble-instr, 1 early-preamble
MESA: info: ; pos: r3.x
MESA: info: 
MESA: info: NIR (final form) for FRAG shader (null):
MESA: info: shader: MESA_SHADER_FRAGMENT
MESA: info: source_blake3: {0xf1b871cf, 0x76ca91b9, 0x4f59ebd5, 0xddaf2287, 0xe5db1e77, 0x56d6cd04, 0x33467e6b, 0x83a69fd7}
MESA: info: inputs_read: 32
MESA: info: outputs_written: 4
MESA: info: subgroup_size: 0
MESA: info: bit_sizes_float: 0x20
MESA: info:     block b7:
MESA: info: }
MESA: info: 
MESA: info: Native code for unnamed VERT shader (null) with sha1 e0dd667ce9c1b0005aeccc32c88168d5b189c929:
MESA: info: ; num_ubos:           0
MESA: info: ; num_driver_params:  0
MESA: info: ; offsets:
MESA: info: ;   ubo_ranges                 c0.x (4 vec4)
MESA: info: ;   primitive_param            c4.x (1 vec4)
MESA: info: ; ubo_state:
MESA: info: ;   num_enabled:      1
MESA: info: ;   range[0]:
MESA: info: ;     block:          0
MESA: info: ;     bindless_base:  1
MESA: info: ;     offset:         c0.x
MESA: info: ;     size:           4 vec4 (0b -> 64b)
MESA: info: @in(r0.x)	in0 (wrmask=0xf)
MESA: info: shps #l6
MESA: info: getone #l6
MESA: info: mova1 a1.x, (r)0
MESA: info: (ss)nop
MESA: info: ldc.4.k.imm.base1 c[a1.x], 0, 0
MESA: info: (sy)(ss)shpe
MESA: info: l6:
MESA: info: (jp)mul.f r1.x, c1.x, r0.y
MESA: info: mul.f r2.y, c1.y, r0.y
MESA: info: mul.f r2.z, c1.z, r0.y
MESA: info: mul.f r2.w, c1.w, r0.y
MESA: info: mad.f32 r1.x, c0.x, r0.x, (last)r1.x
MESA: info: mad.f32 r2.y, c0.y, r0.x, (last)r2.y
MESA: info: mad.f32 r2.z, c0.z, r0.x, (last)r2.z
MESA: info: mad.f32 r2.w, c0.w, r0.x, (last)r2.w
MESA: info: mad.f32 r1.y, c2.x, r0.z, (last)r1.x
MESA: info: mad.f32 r1.z, c2.y, r0.z, (last)r2.y
MESA: info: mad.f32 r1.w, c2.z, r0.z, (last)r2.z
MESA: info: mad.f32 r2.x, c2.w, r0.z, (last)r2.w
MESA: info: (rpt3)mad.f32 r3.x, (r)c3.x, r0.w, (r)(last)r1.y
MESA: info: end
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: ; VERT: outputs: r3.x (VARYING_SLOT_POS) r0.x (VARYING_SLOT_VAR0)
MESA: info: ; VERT: inputs: r0.x (VERT_ATTRIB_GENERIC0 slot=15 cm=f,il=0,b=0)
MESA: info: ; VERT prog 132/1: 17 instr, 0 nops, 17 non-nops, 0 mov, 0 cov, 64 dwords
MESA: info: ; VERT prog 132/1: 0 last-baryf, 0 last-helper, 0 half, 4 full, 8 constlen
MESA: info: ; VERT prog 132/1: 1 cat0, 0 cat1, 4 cat2, 12 cat3, 0 cat4, 0 cat5, 0 cat6, 0 cat7
MESA: info: ; VERT prog 132/1: 0 sstall, 0 (ss), 0 systall, 0 (sy), 0 loops
MESA: info: ; 6 preamble-instr, 1 early-preamble
MESA: info: ; pos: r3.x
MESA: info: 
MESA: info: NIR (final form) for BVERT shader (null):
MESA: info: shader: MESA_SHADER_VERTEX";
        let result = shader_parser::find_shaders(bvert).unwrap();

        assert_eq!(
            result[0].sha1,
            "f53df4f2509a8fcae820125c175eeebc8fa6954a".to_string()
        );
        assert_eq!(result[0].stage, "BVERT".to_owned());
        assert_eq!(result[0].code, "MESA: info: Native code for unnamed BVERT shader (null) with sha1 f53df4f2509a8fcae820125c175eeebc8fa6954a:
MESA: info: ; num_ubos:           0
MESA: info: ; num_driver_params:  0
MESA: info: ; offsets:
MESA: info: ;   ubo_ranges                 c0.x (4 vec4)
MESA: info: ;   primitive_param            c4.x (1 vec4)
MESA: info: ; ubo_state:
MESA: info: ;   num_enabled:      1
MESA: info: ;   range[0]:
MESA: info: ;     block:          0
MESA: info: ;     bindless_base:  1
MESA: info: ;     offset:         c0.x
MESA: info: ;     size:           4 vec4 (0b -> 64b)
MESA: info: @in(r0.x)	in0 (wrmask=0xf)
MESA: info: shps #l6
MESA: info: getone #l6
MESA: info: mova1 a1.x, (r)0
MESA: info: (ss)nop
MESA: info: ldc.4.k.imm.base1 c[a1.x], 0, 0
MESA: info: (sy)(ss)shpe
MESA: info: l6:
MESA: info: (jp)mul.f r1.x, c1.x, r0.y
MESA: info: mul.f r2.y, c1.y, r0.y
MESA: info: mad.f32 r1.x, c0.x, r0.x, (last)r1.x
MESA: info: mad.f32 r2.y, c0.y, r0.x, (last)r2.y
MESA: info: mad.f32 r1.y, c2.x, r0.z, (last)r1.x
MESA: info: (rpt1)mul.f r2.z, (r)c1.z, (last)r0.y
MESA: info: mad.f32 r1.z, c2.y, r0.z, (last)r2.y
MESA: info: (rpt1)mad.f32 r2.z, (r)c0.z, r0.x, (r)(last)r2.z
MESA: info: (rpt1)mad.f32 r1.w, (r)c2.z, r0.z, (r)(last)r2.z
MESA: info: (rpt3)mad.f32 r3.x, (r)c3.x, r0.w, (r)(last)r1.y
MESA: info: end
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: ; BVERT: outputs: r3.x (VARYING_SLOT_POS)
MESA: info: ; BVERT: inputs: r0.x (VERT_ATTRIB_GENERIC0 slot=15 cm=f,il=0,b=0)
MESA: info: ; BVERT prog 128/2: 17 instr, 0 nops, 17 non-nops, 0 mov, 0 cov, 64 dwords
MESA: info: ; BVERT prog 128/2: 0 last-baryf, 0 last-helper, 0 half, 4 full, 8 constlen
MESA: info: ; BVERT prog 128/2: 1 cat0, 0 cat1, 4 cat2, 12 cat3, 0 cat4, 0 cat5, 0 cat6, 0 cat7
MESA: info: ; BVERT prog 128/2: 0 sstall, 0 (ss), 0 systall, 0 (sy), 0 loops
MESA: info: ; 6 preamble-instr, 1 early-preamble
MESA: info: ; pos: r3.x
".to_string()
        );

        assert_eq!(
            result[1].sha1,
            "e0dd667ce9c1b0005aeccc32c88168d5b189c929".to_string()
        );
        assert_eq!(result[1].stage, "VERT".to_owned());
        assert_eq!(result[1].code, "MESA: info: Native code for unnamed VERT shader (null) with sha1 e0dd667ce9c1b0005aeccc32c88168d5b189c929:
MESA: info: ; num_ubos:           0
MESA: info: ; num_driver_params:  0
MESA: info: ; offsets:
MESA: info: ;   ubo_ranges                 c0.x (4 vec4)
MESA: info: ;   primitive_param            c4.x (1 vec4)
MESA: info: ; ubo_state:
MESA: info: ;   num_enabled:      1
MESA: info: ;   range[0]:
MESA: info: ;     block:          0
MESA: info: ;     bindless_base:  1
MESA: info: ;     offset:         c0.x
MESA: info: ;     size:           4 vec4 (0b -> 64b)
MESA: info: @in(r0.x)	in0 (wrmask=0xf)
MESA: info: shps #l6
MESA: info: getone #l6
MESA: info: mova1 a1.x, (r)0
MESA: info: (ss)nop
MESA: info: ldc.4.k.imm.base1 c[a1.x], 0, 0
MESA: info: (sy)(ss)shpe
MESA: info: l6:
MESA: info: (jp)mul.f r1.x, c1.x, r0.y
MESA: info: mul.f r2.y, c1.y, r0.y
MESA: info: mul.f r2.z, c1.z, r0.y
MESA: info: mul.f r2.w, c1.w, r0.y
MESA: info: mad.f32 r1.x, c0.x, r0.x, (last)r1.x
MESA: info: mad.f32 r2.y, c0.y, r0.x, (last)r2.y
MESA: info: mad.f32 r2.z, c0.z, r0.x, (last)r2.z
MESA: info: mad.f32 r2.w, c0.w, r0.x, (last)r2.w
MESA: info: mad.f32 r1.y, c2.x, r0.z, (last)r1.x
MESA: info: mad.f32 r1.z, c2.y, r0.z, (last)r2.y
MESA: info: mad.f32 r1.w, c2.z, r0.z, (last)r2.z
MESA: info: mad.f32 r2.x, c2.w, r0.z, (last)r2.w
MESA: info: (rpt3)mad.f32 r3.x, (r)c3.x, r0.w, (r)(last)r1.y
MESA: info: end
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: nop
MESA: info: ; VERT: outputs: r3.x (VARYING_SLOT_POS) r0.x (VARYING_SLOT_VAR0)
MESA: info: ; VERT: inputs: r0.x (VERT_ATTRIB_GENERIC0 slot=15 cm=f,il=0,b=0)
MESA: info: ; VERT prog 132/1: 17 instr, 0 nops, 17 non-nops, 0 mov, 0 cov, 64 dwords
MESA: info: ; VERT prog 132/1: 0 last-baryf, 0 last-helper, 0 half, 4 full, 8 constlen
MESA: info: ; VERT prog 132/1: 1 cat0, 0 cat1, 4 cat2, 12 cat3, 0 cat4, 0 cat5, 0 cat6, 0 cat7
MESA: info: ; VERT prog 132/1: 0 sstall, 0 (ss), 0 systall, 0 (sy), 0 loops
MESA: info: ; 6 preamble-instr, 1 early-preamble
MESA: info: ; pos: r3.x
".to_string());
    }

    #[test]
    fn test_max_par_sam() {
        /* non-sample instruction */
        assert_eq!(
            max_parallel_sam(
                r#"MESA: info: sel.b32 r4.z, (last)r4.z, r13.x, (last)r12.w
MESA: info: sel.b32 r47.w, (last)r47.w, r3.z, (last)r3.y
"#
            ),
            0
        );

        assert_eq!(
            /* Lots of samples in parallel, presumably fills the fifo. */
            max_parallel_sam(
                r#"MESA: info: alias.tex.b32.1 r18.y, r0.y
MESA: info: alias.tex.b32.0 r18.z, r9.x
MESA: info: sam.3d.base2 (f32)(xyzw)r13.z, r18.x, s#7, t#6
MESA: info: nop
MESA: info: alias.tex.b32.1 r18.y, r4.z
MESA: info: alias.tex.b32.0 r18.z, r6.z
MESA: info: sam.3d.base2 (f32)(xyzw)r30.x, r18.x, s#7, t#6
MESA: info: alias.tex.b32.1 r47.x, r4.z
MESA: info: alias.tex.b32.0 r47.y, r6.z
MESA: info: sam.3d.base2 (f32)(xyzw)r31.x, r46.w, s#7, t#6
MESA: info: nop
MESA: info: mul.f r47.x, (last)r47.x, (last)r27.z
MESA: info: alias.tex.b32.1 r47.x, r0.y
MESA: info: alias.tex.b32.0 r47.y, r9.x
MESA: info: sam.3d.base2 (f32)(xyzw)r27.z, r46.w, s#7, t#6
MESA: info: alias.tex.b32.1 r45.z, r47.w
MESA: info: alias.tex.b32.0 r45.w, r47.y
MESA: info: sam.3d.base2 (f32)(xyzw)r6.w, r45.y, s#7, t#6
MESA: info: alias.tex.b32.1 r45.z, r0.x
MESA: info: alias.tex.b32.0 r45.w, r47.y
MESA: info: sam.3d.base2 (f32)(xyzw)r10.y, r45.y, s#7, t#6
MESA: info: nop
MESA: info: alias.tex.b32.1 r46.w, r47.z
MESA: info: alias.tex.b32.0 r47.x, r47.w
MESA: info: sam.3d.base2 (f32)(xyzw)r7.w, r46.w, s#7, t#6
MESA: info: alias.tex.b32.1 r46.w, r47.z
MESA: info: alias.tex.b32.0 r47.x, r0.x
MESA: info: sam.3d.base2 (f32)(xyzw)r11.y, r46.w, s#7, t#6
MESA: info: nop
MESA: info: mul.f r47.x, (last)r47.x, c4.w
MESA: info: (sy)add.f r31.x, (last)r31.x, (neg)r30.x
"#
            ),
            8
        );

        /* sy is a barrier between parallel sams. */
        assert_eq!(
            max_parallel_sam(
                r#"
MESA: info: saml.base2 (f32)(xy)r5.x, r4.z, r40.x, s#11, t#10
MESA: info: nop
MESA: info: alias.tex.b32.1 r9.w, r11.y
MESA: info: alias.tex.b32.0 r40.x, (0x0)
MESA: info: saml.base2 (f32)(xy)r11.z, r9.z, r40.x, s#11, t#10
MESA: info: (sy)add.f r10.x, (last)r10.x, c106.y
MESA: info: alias.tex.b32.0 r1.z, r12.z
MESA: info: sam.a.base2 (f32)(xyzw)r12.w, r1.x, t#16, a1.x
MESA: info: (sy)rcp r13.w, r13.z
"#
            ),
            2
        );

        /* Don't count the preamble */
        assert_eq!(
            max_parallel_sam(
                r#"
MESA: info: shps #l264
MESA: info: getone #l264
MESA: info: sam.base2 (f32)(xyzw)r63.x, r48.x, s#1, t#0
MESA: info: sam.base2 (f32)(xyzw)r63.x, r48.x, s#9, t#8
MESA: info: sam.base2 (f32)(xyzw)r63.x, r48.x, s#3, t#2
MESA: info: sam.base2 (f32)(xyzw)r63.x, r48.x, s#11, t#10
MESA: info: sam.base2 (f32)(xyzw)r63.x, r48.x, s#5, t#4
MESA: info: sam.base2 (f32)(xyzw)r63.x, r48.x, s#7, t#6
MESA: info: sam.base2 (f32)(xyzw)r63.x, r48.x, s#13, t#12
MESA: info: (ss)nop
MESA: info: (sy)(ss)shpe
MESA: info: l264:
MESA: info: alias.tex.b32.0 r0.y, (0x3f000000)
MESA: info: saml.base2 (f32)(xyzw)r2.w, r0.x, r2.z, s#1, t#0
"#
            ),
            1
        );

        /* Other cat5s */
        assert_eq!(
            max_parallel_sam(
                r#"
MESA: info: l7:
MESA: info: (jp)(rpt1)bary.f (ei)r0.z, (r)0, (last)r0.x
MESA: info: (rpt4)nop
MESA: info: alias.tex.b32.0 r40.x, (0x0)
MESA: info: saml.base2 (f32)(xyzw)r1.x, r0.z, r40.x, s#1, t#0
MESA: info: gather4a.base2 (f32)(xyz)r2.x, r0.z, s#1, t#0
MESA: info: alias.tex.b32.1 r40.x, (0xffffffff)
MESA: info: alias.tex.b32.0 r40.y, (0xffffffff)
MESA: info: gather4a.o.base2 (f32)(xzw)r2.w, r0.z, r40.x, s#1, t#0
MESA: info: (sy)max.f r3.w, r1.w, r2.x
"#
            ),
            3
        );

        /* don't try to count across jumps */
        assert_eq!(
            max_parallel_sam(
                r#"
MESA: info: alias.tex.b32.1 r0.y, (0x3f000000)
MESA: info: alias.tex.b32.0 r0.z, r4.x
MESA: info: saml.a.base2 (f32)(xyzw)r2.w, r0.x, r3.w, s#9, t#8
MESA: info: jump #l812
MESA: info: l312:
MESA: info: br !p0.x, #l318
MESA: info: cov.s32f32 r4.y, c18.z
MESA: info: (rpt4)nop
MESA: info: alias.tex.b32.0 r0.y, r2.y
MESA: info: saml.base2 (f32)(xyzw)r2.w, r0.x, r4.y, s#3, t#2
MESA: info: jump #l812
MESA: info: l318:"#
            ),
            1
        );

        /* sync doesn't hide the sam */
        assert_eq!(
            max_parallel_sam(
                r#"
MESA: info: (sy)sam.base2 (f32)(xyzw)r1.w, r0.x, s#3, t#2
"#
            ),
            1
        );

        /* sync resets us before counting the new sam */
        assert_eq!(
            max_parallel_sam(
                r#"
MESA: info: nop
MESA: info: alias.tex.b32.0 r0.w, (0x0)
MESA: info: sam.base2 (f32)(xyzw)r0.w, r0.z, s#1, t#0
MESA: info: nop
MESA: info: alias.tex.b32.0 r0.y, r1.x
MESA: info: (sy)sam.base2 (f32)(xyzw)r1.w, r0.x, s#3, t#2
"#
            ),
            1
        );
    }

    #[test]
    fn test_f16() {
        // test a full non-f16 shader.
        assert_eq!(double_rate_f16(SINGLE_IR3_SHADER), 0);

        // test a snippet of an f16 shader.
        assert_eq!(
            double_rate_f16(
                r#"MESA: info: rcp hr5.x, (last)hr5.x
MESA: info: (rpt1)nop
MESA: info: rcp hr4.z, (last)hr4.z
MESA: info: (rpt1)nop
MESA: info: rcp hr4.x, (last)hr4.x
MESA: info: (ss)mul.f hr5.y, (last)hr5.y, (last)hr5.x
MESA: info: mul.f hr5.w, (last)hr5.w, (last)hr4.z
MESA: info: mul.f hr5.z, (last)hr5.z, (last)hr4.x
MESA: info: (nop1) mul.f hr6.w, hr5.y, hc39.y
MESA: info: (nop1) mad.f16 hr6.w, hc39.z, hr5.w, (last)hr6.w
MESA: info: (nop2) mad.f16 hr6.w, hc39.w, hr5.z, (last)hr6.w
MESA: info: add.f hr5.z, (last)hr5.z, (neg)hr6.w
MESA: info: add.f hr5.w, (last)hr5.w, (neg)hr6.w
MESA: info: add.f hr5.y, (last)hr5.y, (neg)hr6.w
MESA: info: (sat)mad.f16 hr5.z, hc24.w, hr5.z, hr6.w
MESA: info: (sat)mad.f16 hr5.w, hc24.w, hr5.w, hr6.w
MESA: info: (sat)(nop3) mad.f16 hr5.y, hc24.w, hr5.y, (last)hr6.w
MESA: info: sqrt hr7.x, (last)hr5.z
"#
            ),
            16
        );
    }
}
