# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

Class(FixCodeAVX, RuleSet);
RewriteRules(FixCodeAVX, rec(
    fix_noneExp := Rule( noneExp, e -> e.t.zero()),

    vpermf128_8x32f_to_vextract := Rule( [assign, [deref, @(1)], [vpermf128_8x32f, @(2), @(3), @(4)]],
        e -> let( p := @(4).val.p,
                  a := [[@(2).val, [0]], [@(2).val, [1]], [@(3).val, [0]], [@(3).val, [1]]],
                  dst := tcast(TPtr(TVect(T_Real(32), 4)), @(1).val),
                  chain(
                      assign( deref(dst  ), ApplyFunc(vextract_4l_8x32f, a[p[1]])),
                      assign( deref(dst+1), ApplyFunc(vextract_4l_8x32f, a[p[2]]))
                  )
        )),

    flatten_chains := ARule( chain, [@(1, chain)], e -> [@(1).val.cmds] ),

    addsub_4x64f_to_mul := Rule( [addsub_4x64f, _0, @(1)],
        e -> mul(e.t.value([-1,1,-1,1]), @(1).val)),
    addsub_8x32f_to_mul := Rule( [addsub_8x32f, _0, @(1)],
        e -> mul(e.t.value([-1,1,-1,1,-1,1,-1,1]), @(1).val)),

    # replacing 32 byte load by 16 byte load which is faster for out of L1 problems.
    deref_to_doubleload := Rule(
        @@(1, deref, (x, cx) -> _avxT(x.t, cx.opts) and cx.isInside(assign) and Last(cx.assign).loc<>x and
                                cx.isInside(rdepth_marker) and Last(cx.rdepth_marker).depth=1),
        (e, cx) -> Cond(
            e.t = TVect(T_Real(64), 4) or e.t = TVect(TReal, 4),
                let( p := tcast(TPtr(TVect(T_Real(64), 2)), e.loc),
                     vinsert_2l_4x64f( vinsert_2l_4x64f(e.t.zero(), deref(p), [0]), deref(p + 1), [1])),
            e.t = TVect(T_Real(32), 8) or e.t = TVect(TReal, 8),
                let( p := tcast(TPtr(TVect(T_Real(32), 4)), e.loc),
                     vinsert_4l_8x32f( vinsert_4l_8x32f(e.t.zero(), deref(p), [0]), deref(p + 1), [1])),
            Error("unexpected type"))
    ),

    vloadu_8x32f_to_doubleload := Rule( @@(1, vloadu_8x32f, (x, cx) -> cx.isInside(rdepth_marker) and Last(cx.rdepth_marker).depth=1),
        (e, cx) -> vinsert_4l_8x32f( vinsert_4l_8x32f(e.t.zero(), vloadu_4x32f(e.args[1]), [0]), vloadu_4x32f(e.args[1] + 4), [1])
    ),

    vloadu_4x64f_to_doubleload := Rule( @@(1, vloadu_4x64f, (x, cx) -> cx.isInside(rdepth_marker) and Last(cx.rdepth_marker).depth=1),
        (e, cx) -> vinsert_2l_4x64f( vinsert_2l_4x64f(e.t.zero(), vloadu_2x64f(e.args[1]), [0]), vloadu_2x64f(e.args[1] + 2), [1])
    ),

));

RewriteRules(RulesStrengthReduce, rec(
    # a dupe
    #tcast_fold := Rule([tcast, @(1), [tcast, @(2), @(3)]], e->tcast(@(1).val, @(3).val)),

    avx_add_addsub_vzero := Rule([add, @(1), [@(2, [addsub_4x64f, addsub_8x32f]), _0, @(3)]],
        e -> ObjId(@(2).val)(@(1).val, @(3).val)),

    avx_addsub_vzero := Rule([@(1, [addsub_4x64f, addsub_8x32f]), @(2), _0], e -> @(2).val),

    # is it valid?
    avx_addsub_noneExp := Rule([@(1, [addsub_4x64f, addsub_8x32f]), @(2, noneExp), @(3)],
	e -> ObjId(@(1).val)(@(2).val.t.zero(), @(3).val)),

    avx_addsub_mul_mul_values := Rule([@(0, [add, sub, addsub_4x64f, addsub_8x32f]), [mul, @(1, Value), @(2)], [mul, @@(3, Value, (x, cx)->x=@(1).val and _avxT(@(0).val.t, cx.opts)), @(4)]],
        e -> mul(@(1).val, ObjId(@(0).val)(@(2).val, @(4).val))),

    # replace by mul const?
    avx_addsub_zero_neg := Rule( [@(1, [addsub_4x64f, addsub_8x32f]), _0, [neg, @(2)]],
        e -> neg(ObjId(@(1).val)(e.t.zero(), @(2).val))),

    avx_vunpack_4x64f_neg := Rule([@(1, [vunpacklo_4x64f, vunpackhi_4x64f]), [neg, @(2)], [neg, @(3)]],
       e -> neg(ObjId(e)(@(2).val, @(3).val))),

    #NOTE: this is real hack, unpacking from vdup(TVect(TReal,2),2) is simply a vdup(TReal, 4), how to do this right?
    # need the same for AVX_8x32f
    avx_vunpacklo_4x64f_vdup_2 := Rule([vunpacklo_4x64f, [@(1, vdup), nth, _2],
           [@(2, vdup, x -> x=@(1).val), @(3, nth, x -> x.loc _is var and not IsBound(x.loc.value)), _2]],
       e -> let(n := @(3).val, vdup(deref(nth(n.loc, 0).toPtr(e.t.t) + 2*n.idx), 4))),
    avx_vunpackhi_4x64f_vdup_2 := Rule([vunpackhi_4x64f, [@(1, vdup), nth, _2],
           [@(2, vdup, x -> x=@(1).val), @(3, nth, x -> x.loc _is var and not IsBound(x.loc.value)), _2]],
       e -> let(n := @(3).val, vdup(deref(nth(n.loc, 0).toPtr(e.t.t) + 2*n.idx+1), 4))),

    avx_vperm_neg := Rule([@(0, [vperm_4x64f, vperm_8x32f]), [neg, @(1)], @(2)],
        e -> neg(ObjId(e)(@(1).val, @(2).val))),

    avx_vshuffle_neg := Rule([@(0, [vshuffle_4x64f, vshuffle_8x32f]), [neg, @(1)], [neg, @(2)], @(3)],
       e -> neg(ObjId(@(0).val)(@(1).val, @(2).val, @(3).val))),

    avx_addsub_neg_neg := Rule([@(0, [addsub_4x64f, addsub_8x32f]), [neg, @(1)], [neg, @(2)]],
        e -> neg(ObjId(e)(@(1).val, @(2).val))),

    avx_fold_vshuffle := Rule(
	[@(1, [vshuffle_4x64f, vshuffle_8x32f]), @(2, Value, e->IsVecT(e.t)),
	                                         @(3, Value, e->IsVecT(e.t)), @(4)],
        e -> Value(@(2).val.t, @(1).val.semantic(@(2).val.v, @(3).val.v, @(4).val.p))),

    # op(v1*a, v2*b) where v1*v2=0 turns into op(v1,v2)*vblend(a,b)
    avx_addsub_to_blend := Rule([@(1, [add, sub, addsub_4x64f, addsub_8x32f]), [mul, @(2, Value), @(3)], [mul, @@(4, Value, (x, cx) -> _avxT(x.t, cx.opts) and x*@(2).val=x.t.zero()), @(5)]],
        e -> let( c1 := @(2).val,
                  c2 := @@(4).val,
                  c  := ObjId(e)(c1, c2),
                  b  := Cond( e.t.size = 4, vblend_4x64f, vblend_8x32f ),
                  c*b(@(3).val, @(5).val, List(c1.v, x -> Cond(x=0, 2, 1))))),

    avx_addsub_ushuffles := Rule([@(0, [add, sub]),
            [@(1, [vshuffle_4x64f, vshuffle_8x32f]), @(2), @(3).cond(x -> x=@(2).val), @(4)],
            [@(5, [vshuffle_4x64f, vshuffle_8x32f]), @(6), @(7).cond(x -> x=@(6).val), @(8).cond(x -> x = @(4).val and ObjId(@(5).val)=ObjId(@(1).val))]],
        e -> ObjId(@(1).val)(ObjId(e)(@(2).val, @(6).val), ObjId(e)(@(2).val, @(6).val), @(4).val)),
    avx_addsub_vperm := Rule([@(0, [add, sub]),
            [vperm_4x64f, @(1), @(2)], [vperm_4x64f, @(3), @(4).cond(x -> x = @(2).val)]],
        e -> vperm_4x64f(ObjId(e)(@(1).val, @(3).val), @(2).val)),

    avx_fmaddsub_4x64f := Rule(
	[@@(1, addsub_4x64f, (e,cx)->IsBound(cx.opts.vector) and cx.opts.vector.SIMD = "AVX3"),
	    @(2, mul, e->Length(e.args)=2), @(3)],
        e-> fmaddsub_4x64f(@(2).val.args[1], @(2).val.args[2], @(3).val)),

    avx_fmaddsub_8x32f := Rule(
	[@@(1, addsub_8x32f, (e,cx)->IsBound(cx.opts.vector) and cx.opts.vector.SIMD = "AVX3"),
	    @(2, mul, e->Length(e.args)=2), @(3)],
        e-> fmaddsub_8x32f(@(2).val.args[1], @(2).val.args[2], @(3).val)),

    avx_add_mul_to_addsub := Rule([add, [mul, @(1, Value), @(2)],
                                        [mul, @@(3, Value, (x, cx) -> _avxT(x.t, cx.opts) and
                                                ForAll([1..x.t.size], i -> x.v[i]*(1 - 2*(i mod 2))=@(1).val.v[i])), @(4)]],
        e -> mul(@(1).val, Cond(e.t.size=4, addsub_4x64f, addsub_8x32f)(@(2).val, @(4).val))),
    avx_sub_mul_to_addsub := Rule([sub, [mul, @(1, Value), @(2)],
                                        [mul, @@(3, Value, (x, cx) -> _avxT(x.t, cx.opts) and
                                                ForAll([1..x.t.size], i -> x.v[i]*(-1 + 2*(i mod 2))=@(1).val.v[i])), @(4)]],
        e -> mul(@(1).val, Cond(e.t.size=4, addsub_4x64f, addsub_8x32f)(@(2).val, @(4).val))),

    avx_drop_insert_mulmask := Rule( [mul, @(1, Value), [@(2, vinsert_2l_4x64f), _0, @,
            @(3).cond( x -> (x.p[1]=0 and @(1).val=@(2).val.t.value([1, 1, 0, 0])) or
                            (x.p[1]=1 and @(1).val=@(2).val.t.value([0, 0, 1, 1])))]],
        e -> @(2).val),

    avx_drop_store_mulmask_a := Rule( [@(1, vextract_2l_4x64f), [@(2, mul), @(3, Value), @(4)],
            @(5).cond( x -> (x.p[1]=0 and @(3).val=@(2).val.t.value([1, 1, 0, 0])) or
                            (x.p[1]=1 and @(3).val=@(2).val.t.value([0, 0, 1, 1])))],
        e -> vextract_2l_4x64f(@(4).val, @(5).val)),

    avx_drop_store_mulmask_b := Rule( [@(1, vextract_2l_4x64f), [@(2, [add, sub]), @(3), [mul, @(4, Value), @(5)]],
            @(6).cond( x -> (x.p[1]=0 and @(4).val=@(2).val.t.value([1, 1, 0, 0])) or
                            (x.p[1]=1 and @(4).val=@(2).val.t.value([0, 0, 1, 1])))],
        e -> vextract_2l_4x64f(ObjId(@(2).val)(@(3).val, @(5).val), @(6).val)),

    avx_drop_mulmask32f_a := Rule( [mul, @(1, Value), [@(2, vpermf128_8x32f), @(3), _0,
            @(4).cond( x -> x.p[2]>=3 and @(1).val=@(2).val.t.value([1,1,1,1,0,0,0,0]) )]],
        e -> @(2).val),

    avx_drop_mulmask32f_b := Rule( [vextract_4l_8x32f, [@(1, [add, sub]), @(2), [@(3, vperm_8x32f), [mul, @(4, Value), @(5)], @(6)]],
            @(7).cond( x -> (x.p[1]=0 and @(4).val = @(1).val.t.value([1,1,1,1,0,0,0,0])) or
                            (x.p[1]=1 and @(4).val = @(1).val.t.value([0,0,0,0,1,1,1,1])))],
        e -> vextract_4l_8x32f(ObjId(@(1).val)(@(2).val, vperm_8x32f(@(5).val, @(6).val)), @(7).val)),

    avx_drop_mulmask32f_c := Rule( [mul, @(1, Value), [@(2, vinsert_4l_8x32f), _0, [vload_2l_4x32f, _0, @(3)],
            @(4).cond( x -> x.p[1]=0 and @(1).val = @(2).val.t.value([1,1,0,0,0,0,0,0]) )]],
        e -> @(2).val ),

    avx_term_cxtr_8x32f := Rule( vcxtr_8x32f,
        e -> vblend_8x32f(e.args[1], vperm_8x32f(vupermf128_8x32f(e.args[1], [ 2, 1 ]), [3,4,1,2]), [1,1,2,2,2,2,1,1])),

    # complex vectorization loop rules
    # 4-way AVX
    AVX_4x64f_load_TComplex := Rule([@@(1, vpack, (e,cx)-> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa) and
        cx.opts.vector.isa = AVX_4x64f),
        [@(2,re), nth], @(3, im, e->e.args[1]=@(2).val.args[1]), @(4,re, e->e.args[1]=@(2).val.args[1]), @(5,im, e->e.args[1]=@(2).val.args[1])],

        e->let(dptr := @(2).val.args[1].toPtr(TVect(T_Real(64), 2)), src := vinsert_2l_4x64f(vzero_4x64f(),deref(dptr),[0]),
            vpermf128_4x64f(src, src, [1,1]))),

    AVX_4x64f_fold_shuffle := Rule([@(1, vshuffle_4x64f), [@(2, vpermf128_4x64f), @(3, vinsert_2l_4x64f), @(6), @(7)],
        @(4).cond(e->e=@(2).val), @(5, vparam, e->Length(Set(e.p))=1)],
        e->vdup(deref(@(3).val.args[2].toPtr(T_Real(64)) + (@(5).val.p[1]-1)), 4)),

    AVX_4x64f_load_TVect := Rule([@@(1, vpack, (e,cx)-> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa) and
        cx.opts.vector.isa = AVX_4x64f),
        @(2,nth,e->IsBound(e.loc.init)), @(3, nth, e->e.loc=@(2).val.loc), @(4,nth, e->e.loc=@(2).val.loc), @(5,nth, e->e.loc=@(2).val.loc)],
        e->deref(@(2).val.toPtr(TVect(T_Real(64), 4)))),

#    # 8-way AVX

    AVX_8x32f_broadcast_re := Rule([@(0, vshuffle_8x32f), [@@(1, vpack, (e,cx)-> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa) and cx.opts.vector.isa = AVX_8x32f),
                [@(2,re), nth], @(3, im, e->e.args[1]=@(2).val.args[1]),
                @(4,re, e->e.args[1]=@(2).val.args[1]), @(5,im, e->e.args[1]=@(2).val.args[1]),
                @(6,re, e->e.args[1]=@(2).val.args[1]), @(7,im, e->e.args[1]=@(2).val.args[1]),
                @(8,re, e->e.args[1]=@(2).val.args[1]), @(9,im, e->e.args[1]=@(2).val.args[1])],
            @(10).cond(e->e=@@(1).val), @(11,vparam, e->e.p=[1,1,3,3])],
        e->vdup(@(2).val, 8)),

    AVX_8x32f_broadcast_im := Rule([@(0, vshuffle_8x32f), [@@(1, vpack, (e,cx)-> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa) and cx.opts.vector.isa = AVX_8x32f),
                [@(2,re), nth], @(3, im, e->e.args[1]=@(2).val.args[1]),
                @(4,re, e->e.args[1]=@(2).val.args[1]), @(5,im, e->e.args[1]=@(2).val.args[1]),
                @(6,re, e->e.args[1]=@(2).val.args[1]), @(7,im, e->e.args[1]=@(2).val.args[1]),
                @(8,re, e->e.args[1]=@(2).val.args[1]), @(9,im, e->e.args[1]=@(2).val.args[1])],
            @(10).cond(e->e=@@(1).val), @(11,vparam, e->e.p=[2,2,4,4])],
        e->vdup(@(3).val, 8)),

    AVX_8x32f_load_TVect := Rule([@@(1, vpack, (e,cx)-> IsBound(cx.opts) and IsBound(cx.opts.vector) and IsBound(cx.opts.vector.isa) and
        cx.opts.vector.isa = AVX_8x32f),
        @(2,nth,e->IsBound(e.loc.init)), @(3, nth, e->e.loc=@(2).val.loc), @(4,nth, e->e.loc=@(2).val.loc), @(5,nth, e->e.loc=@(2).val.loc),
        @(6,nth,e->IsBound(e.loc.init)), @(7, nth, e->e.loc=@(2).val.loc), @(8,nth, e->e.loc=@(2).val.loc), @(9,nth, e->e.loc=@(2).val.loc)],
        e->deref(@(2).val.toPtr(TVect(T_Real(64), 8))))
));

AVXFixProblems := function(c, opts)
    local zerovar, emptyvar, cc;

#    emptyvar := var.fresh_t("ZERO", TVect(TReal, opts.vector.isa.v));
#    zerovar := var.fresh_t("ZERO", TVect(TReal, opts.vector.isa.v));
#
#    cc := SubstTopDown(c, [@(1, neg), @(2)], e->sub(zerovar, @(2).val));
#    cc := SubstTopDown(c, @(1, var, e->IsBound(e.value) and IsList(e.value.v) and ForAll(e.value.v, IsZero)), e->zerovar);
#
#    c := decl([zerovar, emptyvar], chain(assign(zerovar, bin_xor(emptyvar, emptyvar)), cc));
#
#    if Collect(c, vpack) <> [] then Error("Caught in FixProblems"); fi;
    c := UntilDone(c, MergedRuleSet(RulesStrengthReduce, FixCodeAVX), opts);

    return c;
end;

AVX_4x64f.fixProblems := AVXFixProblems;
AVX_8x32f.fixProblems := AVXFixProblems;


# Copyright 2018-2019, Carnegie Mellon University
# See LICENSE for details

TempArrayType := (child, y, x, index) ->
let(When(IsBound(child.a.t_in), child.a.t_in[index],
		let( X := Flat([x])[1], Y:= Flat([y])[1],
			Cond(
				IsBound(X.t.t) and X.t.t = TComplex, 		TComplex,
				IsBound(Y.t.t) and Y.t.t = TComplex, 		TComplex,
				ObjId(child) = ISumReduction, 				child.idval.t,
				ObjId(child) = PointWise, 					child.op.expr.t,
				X.t.t))));
TempArray := (y, x, child) -> let(cols := Flat([ Cols(child) ]),
	newType := TempArrayType(child, y, x, 1),
    StripList(
      List([ 1 .. Length(cols) ], (i) -> TempVec(TArray(TempArrayType(child,
               y, x, i), cols[i])))) );


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

BACValue:=[
[IntHexString("5601"), 1, 1, 1],
[IntHexString("3401"), 2, 6, 0],
[IntHexString("1801"), 3, 9, 0],
[IntHexString("0AC1"), 4, 12, 0],
[IntHexString("0521"), 5, 29, 0],
[IntHexString("0221"), 38, 33, 0],
[IntHexString("5601"), 7, 6, 1],
[IntHexString("5401"), 8, 14, 0],
[IntHexString("4801"), 9, 14, 0],
[IntHexString("3801"), 10, 14, 0],
[IntHexString("3001"), 11, 17, 0],
[IntHexString("2401"), 12, 18, 0],
[IntHexString("1C01"), 13, 20, 0],
[IntHexString("1601"), 29, 21, 0],
[IntHexString("5601"), 15, 14, 1],
[IntHexString("5401"), 16, 14, 0],
[IntHexString("5101"), 17, 15, 0],
[IntHexString("4801"), 18, 16, 0],
[IntHexString("3801"), 19, 17, 0],
[IntHexString("3401"), 20, 18, 0],
[IntHexString("3001"), 21, 19, 0],
[IntHexString("2801"), 22, 19, 0],
[IntHexString("2401"), 23, 20, 0],
[IntHexString("2201"), 24, 21, 0],
[IntHexString("1C01"), 25, 22, 0],
[IntHexString("1801"), 26, 23, 0],
[IntHexString("1601"), 27, 24, 0],
[IntHexString("1401"), 28, 25, 0],
[IntHexString("1201"), 29, 26, 0],
[IntHexString("1101"), 30, 27, 0],
[IntHexString("0AC1"), 31, 28, 0],
[IntHexString("09C1"), 32, 29, 0],
[IntHexString("08A1"), 33, 30, 0],
[IntHexString("0521"), 34, 31, 0],
[IntHexString("0441"), 35, 32, 0],
[IntHexString("02A1"), 36, 33, 0],
[IntHexString("0221"), 37, 34, 0],
[IntHexString("0141"), 38, 35, 0],
[IntHexString("0111"), 39, 36, 0],
[IntHexString("0085"), 40, 37, 0],
[IntHexString("0049"), 41, 38, 0],
[IntHexString("0025"), 42, 39, 0],
[IntHexString("0015"), 43, 40, 0],
[IntHexString("0009"), 44, 41, 0],
[IntHexString("0005"), 45, 42, 0],
[IntHexString("0001"), 45, 43, 0],
[IntHexString("5601"), 46, 46, 0]];

#index -> Qe, NMPS, NLPS, SWITCH
BACTable := FList(TArray(TInt, 4), [
[IntHexString("5601"), 1, 1, 1],
[IntHexString("3401"), 2, 6, 0],
[IntHexString("1801"), 3, 9, 0],
[IntHexString("0AC1"), 4, 12, 0],
[IntHexString("0521"), 5, 29, 0],
[IntHexString("0221"), 38, 33, 0],
[IntHexString("5601"), 7, 6, 1],
[IntHexString("5401"), 8, 14, 0],
[IntHexString("4801"), 9, 14, 0],
[IntHexString("3801"), 10, 14, 0],
[IntHexString("3001"), 11, 17, 0],
[IntHexString("2401"), 12, 18, 0],
[IntHexString("1C01"), 13, 20, 0],
[IntHexString("1601"), 29, 21, 0],
[IntHexString("5601"), 15, 14, 1],
[IntHexString("5401"), 16, 14, 0],
[IntHexString("5101"), 17, 15, 0],
[IntHexString("4801"), 18, 16, 0],
[IntHexString("3801"), 19, 17, 0],
[IntHexString("3401"), 20, 18, 0],
[IntHexString("3001"), 21, 19, 0],
[IntHexString("2801"), 22, 19, 0],
[IntHexString("2401"), 23, 20, 0],
[IntHexString("2201"), 24, 21, 0],
[IntHexString("1C01"), 25, 22, 0],
[IntHexString("1801"), 26, 23, 0],
[IntHexString("1601"), 27, 24, 0],
[IntHexString("1401"), 28, 25, 0],
[IntHexString("1201"), 29, 26, 0],
[IntHexString("1101"), 30, 27, 0],
[IntHexString("0AC1"), 31, 28, 0],
[IntHexString("09C1"), 32, 29, 0],
[IntHexString("08A1"), 33, 30, 0],
[IntHexString("0521"), 34, 31, 0],
[IntHexString("0441"), 35, 32, 0],
[IntHexString("02A1"), 36, 33, 0],
[IntHexString("0221"), 37, 34, 0],
[IntHexString("0141"), 38, 35, 0],
[IntHexString("0111"), 39, 36, 0],
[IntHexString("0085"), 40, 37, 0],
[IntHexString("0049"), 41, 38, 0],
[IntHexString("0025"), 42, 39, 0],
[IntHexString("0015"), 43, 40, 0],
[IntHexString("0009"), 44, 41, 0],
[IntHexString("0005"), 45, 42, 0],
[IntHexString("0001"), 45, 43, 0],
[IntHexString("5601"), 46, 46, 0]]);


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

# ADram tag, generic class for DRAM data layout optimizations
# params[1] = rb = row-buffer size in words
# params[2] = m = size of local memory in words
Class(ADram, AGenericTag, rec(
	updateParams := meth(self)
		Checked(Length(self.params)=2);
		self.rb := self.params[1];
		self.m := self.params[2];
	end,
));

# ACube tag: Tag meant for cubic memory mapping for DRAM,
# params[1] = k = cube-edge in words (k^3 = row-buffer size)
# params[2] = m = size of local memory in words
Class(ACube, AGenericTag, rec(
	isDMARead := false,
	isDMAWrite := false,
	updateParams := meth(self)
		Checked(Length(self.params)=2);
		self.k := self.params[1];
		self.m := self.params[2];
	end,
));

Class(ACubeRd, AGenericTag, rec(
	isDMARead := true,
	isDMAWrite := false,
	updateParams := meth(self)
		Checked(Length(self.params)=2);
		self.k := self.params[1];
		self.m := self.params[2];
	end,
));

Class(ACubeWr, AGenericTag, rec(
	isDMARead := false,
	isDMAWrite := true,
	updateParams := meth(self)
		Checked(Length(self.params)=2);
		self.k := self.params[1];
		self.m := self.params[2];
	end,
));

# ATile tag: Tag meant for 2D-tiled memory mapping for DRAM,
# params[1] = k = tile-width in words (k^2 = row-buffer size)
# params[2] = m = size of local memory in words
Class(ATile, AGenericTag, rec(
	isDMARead := false,
	isDMAWrite := false,
	updateParams := meth(self)
		Checked(Length(self.params)=2);
		self.k := self.params[1];
		self.m := self.params[2];
	end,
));

Class(ATileRd, ATile, rec(
	isDMARead := true,
	isDMAWrite := false,
	updateParams := meth(self)
		Checked(Length(self.params)=2);
		self.k := self.params[1];
		self.m := self.params[2];
	end,
));

Class(ATileWr, ATile, rec(
	isDMARead := false,
	isDMAWrite := true,
	updateParams := meth(self)
		Checked(Length(self.params)=2);
		self.k := self.params[1];
		self.m := self.params[2];
	end,
));

#  # size = scratchpad segment size
#  # nsgmts = number of segments, defaults to 1.
#  Class(ALStore, AGenericTag, rec(
#      updateParams := meth(self)
#          Checked(Length(self.params)=3);
#          self.size := self.params[1];
#          self.nsgmts := self.params[2];
#          self.linesize := self.params[3];
#      end,
#      isRegCx := false
#  ));
#
#  Class(ALStoreCx, ALStore, rec(
#      updateParams := meth(self)
#          Checked(Length(self.params)=3);
#          self.size := self.params[1]/2;
#          self.nsgmts := self.params[2];
#          self.linesize := self.params[3];
#      end,
#      isRegCx := true
#  ));
#
#  # APad tag.
#  #
#  # APad is a special buffering tag meant for things like scratchpads, hence
#  # the name. It takes four parameters. In order, they are:
#  # b - the block size in number of elements
#  # s - the segment size, in number of blocks
#  # u - the number of segments
#  # n - a string identifier
#  #
#  # The block size is the SMALLEST allowed transfer size when copying data.
#  # The breakdown rules which propagate the APad tag insure that sub-block
#  # numbers of contiguous elements are never moved when the tag is present.
#  #
#  # A segment is a discrete memory separate from other segments, in the case
#  # that the 'u' parameter is >1. In the case of DPA, each segment is
#  # connected to a different compute processor, and we do parallelization
#  # after the APad tag is dropped.
#  #
#  # The string identifier is for the platform writer. You can label things
#  # like "local memory" or "vector register file."
#  #
#  # Also, the software pipelining loop (rather than a standard ISum) is
#  # automatically used with the APad tag.
#
#  Class(APad, AGenericTag, rec(
#      applied := false,
#
#      b := (self) >> self.params[1],
#      s := (self) >> self.params[2],
#      u := (self) >> self.params[3],
#      bs := (self) >> self.params[1] * self.params[2],
#      bsu := (self) >> Product(DropLast(self.params, 1)),
#      n := (self) >> self.params[4],
#
#      apply := self >> CopyFields(self, rec(applied := true)),
#
#      updateParams := meth(self)
#          Checked(Length(self.params)=4);
#      end,
#
#      print := meth(self)
#          Print(self.name, "(", PrintCS([self.b(), self.s(), self.u()]),
#              ", \"", self.n(), "\")");
#      end
#  ));


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

SIMD_ISA_DB.hashAdd(TL(4, 2, 1, 2).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 1, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 1, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 2,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 1, 4).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := IxLxI_vtensor( TL(4, 2, 1, 4).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := IxLxI_vtensor( TL(4, 2, 1, 4).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 8, 1, 1).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(16, 8, 1, 1).withTags([ AVecReg(AVX_4x64f) ]),
          SIMD_ISA_Bases1( TL(8, 4, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
          IxLxI_vtensor( TL(4, 2, 1, 4).withTags([ AVecReg(AVX_4x64f) ]) ) ),
      origtree := IxLxI_kmn_km( TL(16, 8, 1, 1).withTags([ AVecReg(AVX_4x64f) ]),
          SIMD_ISA_Bases1( TL(8, 4, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
          IxLxI_vtensor( TL(4, 2, 1, 4).withTags([ AVecReg(AVX_4x64f) ]) ) ),
      measured := 12.4,
      globalUnrolling := 10000 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 1, 1).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 4, 1, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 4, 1, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 6.2000000000000002,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 1, 2).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := SIMD_ISA_Bases2( TL(8, 4, 1, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := SIMD_ISA_Bases2( TL(8, 4, 1, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 2, 1).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 4, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 4, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 12.4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 1, 2).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := SIMD_ISA_Bases2( TL(8, 2, 1, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := SIMD_ISA_Bases2( TL(8, 2, 1, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 2, 1).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 2, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 2, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 12.4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 4, 1, 1).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(16, 4, 1, 1).withTags([ AVecReg(AVX_4x64f) ]),
          SIMD_ISA_Bases2( TL(8, 4, 1, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
          SIMD_ISA_Bases1( TL(8, 4, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(16, 4, 1, 1).withTags([ AVecReg(AVX_4x64f) ]),
          SIMD_ISA_Bases2( TL(8, 4, 1, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
          SIMD_ISA_Bases1( TL(8, 4, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ) ),
      measured := 16.399999999999999,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 2, 2).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 2, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 2, 2).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 2, 1, 1).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(16, 2, 1, 1).withTags([ AVecReg(AVX_4x64f) ]),
          IxLxI_vtensor( TL(4, 2, 1, 4).withTags([ AVecReg(AVX_4x64f) ]) ),
          SIMD_ISA_Bases1( TL(8, 2, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(16, 2, 1, 1).withTags([ AVecReg(AVX_4x64f) ]),
          IxLxI_vtensor( TL(4, 2, 1, 4).withTags([ AVecReg(AVX_4x64f) ]) ),
          SIMD_ISA_Bases1( TL(8, 2, 2, 1).withTags([ AVecReg(AVX_4x64f) ]) ) ),
      measured := 12.4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 1, 1).withTags([ AVecReg(AVX_4x64f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 2, 1, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 2, 1, 1).withTags([ AVecReg(AVX_4x64f) ]) ),
      measured := 6.2000000000000002,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(16, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(8, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(16, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(8, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 12.4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_km( TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(64, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(64, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_n( TL(32, 8, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases2( TL(16, 8, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_kmn_km( TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
              SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
              IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
          SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(64, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_n( TL(32, 8, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases2( TL(16, 8, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_kmn_km( TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
              SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
              IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
          SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 57.599999999999994,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 4.5,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 8,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 2, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(8, 2, 2, 4).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(8, 2, 2, 4).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 8,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 8, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 8, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 8, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 36.0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 12.4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(64, 32, 1, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(64, 32, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      origtree := IxLxI_kmn_km( TL(64, 32, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      measured := 24.800000000000001,
      globalUnrolling := 10000 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 2,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 1.8,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(16, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(16, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 6.2000000000000002,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(64, 4, 1, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(64, 4, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          IxLxI_kmn_km( TL(32, 4, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_kmn_n( TL(16, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
              IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
              SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ) ) ),
      origtree := IxLxI_kmn_n( TL(64, 4, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          IxLxI_kmn_km( TL(32, 4, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_kmn_n( TL(16, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
              IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
              SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ) ) ),
      measured := 49.600000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(32, 8, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(32, 8, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases2( TL(16, 8, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_kmn_km( TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      origtree := IxLxI_kmn_n( TL(32, 8, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases2( TL(16, 8, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_kmn_km( TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      measured := 32.799999999999997,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(32, 8, 2, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(32, 8, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_km( TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(32, 8, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_km( TL(16, 8, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 49.600000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(64, 2, 1, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(64, 2, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_n( TL(8, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(64, 2, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_n( TL(8, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 8, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(16, 8, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(8, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_km( TL(16, 8, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(8, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 12.4,
      globalUnrolling := 10000 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 8, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(8, 4, 8, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 16, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 8, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_km( TL(8, 4, 8, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 16, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 8, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 43.200000000000003,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(32, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(32, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_n( TL(8, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(32, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_n( TL(8, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(32, 2, 2, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(32, 2, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(32, 2, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(8, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(8, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 8, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(8, 2, 8, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 8, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 16, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(8, 2, 8, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 8, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 16, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 43.200000000000003,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 6.2000000000000002,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 2, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(8, 4, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_km( TL(8, 4, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 10.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_vtensor( TL(4, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := IxLxI_vtensor( TL(4, 2, 1, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases2( TL(8, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases2( TL(8, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 2, 1, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 6.2000000000000002,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 2, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(8, 2, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(8, 2, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(4, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 10.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases2( TL(16, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases2( TL(16, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 8,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 4, 4, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_IxLxI_up( TL(16, 4, 4, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_IxLxI_up( TL(16, 4, 4, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 49.600000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(32, 16, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(32, 16, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      origtree := IxLxI_kmn_km( TL(32, 16, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      measured := 24.800000000000001,
      globalUnrolling := 10000 ) ]);
SIMD_ISA_DB.hashAdd(TL(32, 16, 2, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(32, 16, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_km( TL(32, 16, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 24.800000000000001,
      globalUnrolling := 10000 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 4, 1, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_IxLxI_down( TL(16, 4, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(16, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_IxLxI_down( TL(16, 4, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(16, 8, 1, 1).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 12.399999999999999,
      globalUnrolling := 10000 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 2, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(8, 4, 2, 4).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_km( TL(8, 4, 2, 4).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 8,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(16, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(16, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 8, 1, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases2( TL(16, 8, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases2( TL(16, 8, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 8,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(8, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(8, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 12.4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 9.0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(32, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(32, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases2( TL(16, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(32, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases2( TL(16, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 32.799999999999997,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(32, 4, 2, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_km( TL(32, 4, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_kmn_n( TL(16, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
            SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      origtree := IxLxI_kmn_km( TL(32, 4, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
          IxLxI_kmn_n( TL(16, 2, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
            SIMD_ISA_Bases1( TL(8, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      measured := 49.600000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 2, 1, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(16, 2, 1, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(16, 2, 1, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 6.2000000000000002,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(8, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases2( TL(8, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases2( TL(8, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(64, 16, 1, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(64, 16, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_km( TL(32, 16, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
              IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
              IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
          IxLxI_kmn_km( TL(32, 16, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      origtree := IxLxI_kmn_n( TL(64, 16, 1, 1).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_km( TL(32, 16, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_kmn_km( TL(8, 4, 1, 8).withTags([ AVecReg(AVX_8x32f) ]),
              IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ),
              IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
          IxLxI_kmn_km( TL(32, 16, 2, 1).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(16, 8, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ) ),
      measured := 49.600000000000001,
      globalUnrolling := 10000 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(16, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_km( TL(8, 4, 2, 4).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(16, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]),
          IxLxI_kmn_km( TL(8, 4, 2, 4).withTags([ AVecReg(AVX_8x32f) ]),
            SIMD_ISA_Bases1( TL(4, 2, 4, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
            IxLxI_vtensor( TL(4, 2, 2, 8).withTags([ AVecReg(AVX_8x32f) ]) ) ),
          SIMD_ISA_Bases1( TL(8, 4, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 32.799999999999997,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 2, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 2, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 2, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 4,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 4, 2).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 18.0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := IxLxI_vtensor( TL(4, 2, 1, 16).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 0,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases2( TL(16, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases2( TL(16, 2, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 8,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(4, 2, 16, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(4, 2, 16, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(4, 2, 16, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 7.2000000000000002,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      origtree := SIMD_ISA_Bases1( TL(16, 2, 4, 1).withTags([ AVecReg(AVX_8x32f) ]) ),
      measured := 24.800000000000001,
      globalUnrolling := 8 ) ]);
SIMD_ISA_DB.hashAdd(TL(16, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]), [ rec(
      ruletree := IxLxI_kmn_n( TL(16, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases2( TL(8, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(8, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      origtree := IxLxI_kmn_n( TL(16, 4, 1, 2).withTags([ AVecReg(AVX_8x32f) ]),
          SIMD_ISA_Bases2( TL(8, 4, 1, 4).withTags([ AVecReg(AVX_8x32f) ]) ),
          SIMD_ISA_Bases1( TL(8, 4, 2, 2).withTags([ AVecReg(AVX_8x32f) ]) ) ),
      measured := 16.399999999999999,
      globalUnrolling := 8 ) ]);


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

_testJam := (t) -> let(
    xx1 := Last(t.params[2].params[1]),
    xx2 := Last(t.params[3].params[1]),

    (xx1 <> 0 or xx2 <> 0)
    and let(
        M := t.params[1].dims()[1],
        K := t.getTag(ABB).params[1],
        n := When(xx1 <> 0, t.params[4][xx1], t.params[4][xx2]),

		n <= K/2
    )
);

_testIxA := (t) -> let(
    @par := @.cond(e -> e = GTPar),

	PatternMatch(t, [GT, @(1), @par, @par, \.\.\. ], empty_cx())
    and let(
        M := @(1).val.dims()[1],
        K := t.getTag(ABB).params[1],
        m := t.dims()[1],

        K > M
        and m > K
    )
);

_testIxAxI := (t) -> let(
    @ixi := @.cond(e -> e = XChain([1,0,2])),

    PatternMatch(t, [GT, @(1), @ixi, @ixi, \.\.\. ], empty_cx())
    and let(
        M := @(1).val.dims()[1],
        K := t.getTag(ABB).params[1],
        m := t.dims()[1],
        n := t.params[4][1],
        p := t.params[4][2],

		(K/2 < p
		and 1 < K/2)
		or (
			m > K
			and K > M*p
		)
    )
);

_testAxI := (t) -> let(
    @vec := @.cond(e -> e = GTVec),

    PatternMatch(t, [GT, @(1), @vec, @vec, \.\.\. ], empty_cx())
    and let(
        M := @(1).val.dims()[1],
        K := t.getTag(ABB).params[1],
        m := t.dims()[1],
        n := t.params[4][1],

		K/2 < n
		and 1 < K/2
    )
);

NewRulesFor(GT, rec(

    # splits a loop based on basic block size K
    # K is passed in with the tag ABB
    # I_N x A_M -> I_(N/K/M) x I_(K/M) x A_M

    GT_BB_IxA := rec(
        forTransposition := false,
        switch := false,
        applicable := (t) ->
            t.hasTag(ABB)
            and _testIxA(t),

        children := (self, t) >> let(
            K := t.getTag(ABB).params[1],
            spl := t.params[1],
            M := spl.dims()[1],
            n := t.params[4][1],

            r := (K / M),
            s := n / r,

            # if q <=1, just drop the tag, otherwise split the loop
            [[
                GT(
                    spl,
                    XChain([2,1,0]),
                    XChain([2,1,0]),
                    [r, s]
                ).withTags(Filtered(t.getTags(), e -> ObjId(e) <> ABB))
            ]]
        ),

        apply := (self, t, C, nt) >> C[1]
    ),

    # I_n x A_m x I_p, tagged with (K, mu)
    # 1) mp < K
    # 2) mp >= K
    #    a) m > K/mu
    #    b) m <= K/mu
    #    a) p > mu
    #    b) p <= mu

    GT_BB_IxAxI := rec(
        forTransposition := false,
        switch := false,
        applicable := (t) ->
            t.hasTag(ABB)
			and _testIxAxI(t),

        children := (self, t) >> let(
            K := t.getTag(ABB).params[1],
            M := t.params[1].dims()[1],
            spl := t.params[1],
            n := t.params[4][1],
            p := t.params[4][2],
			m := t.dims()[1],

            Cond(
                # cond1
                m > K and K > M*p,
                let(
                    s := K / (M*p),
                    u := n/s,

                    [[GT(
                         spl, XChain([2,1,0,3]), XChain([2,1,0,3]), [s,u,p]
                    ).withTags(t.getTags())]]
                ),

                # cond2
				# p > K/2
                let(
                    s := K / 2,
                    u := p/s,

                    [[GT(
                        spl, XChain([1,0,3,2]), XChain([1,0,3,2]), [n,s,u]
                    ).withTags(t.getTags())]]
                )
            )
        ),

        apply := (self, t, C, nt) >> C[1]
    ),

    #
    GT_BB_AxI := rec(
        forTransposition := false,
        switch := false,
        applicable := (t) ->
            t.hasTag(ABB)
			and _testAxI(t),

        children := (self, t) >> let(
            M := t.params[1].dims()[1],
            K := t.getTag(ABB).params[1],
            n := t.params[4][1],
            spl := t.params[1],

            r := K / 2,
            s := n / r,

            [[GT(
                 spl, XChain([0,2,1]), XChain([0,2,1]), [r,s]
            ).withTags(t.getTags())]]
        ),

        apply := (self, t, C, nt) >> C[1]
    ),

    # when K <= M, we just drop the tag.

    GT_BB_DropTag := rec(
        forTransposition := false,
        switch := false,
        applicable := (self, t) >>
            t.hasTag(ABB)
            and ObjId(t) = GT
            and let(
                K := t.getTag(ABB).params[1],

                K = 2
            ),

        # build a new GT object without the ABB tag.
        children := (self, t) >>
            [[
                t.withoutTag(ABB)
            ]],

        apply := (self, t, C, nt) >> C[1],
    ),

    # just like nthloop except only applicable when
    # the Jam version below isn't.

    GT_BB_NthLoop := CopyFields(GT_NthLoop, rec(
        requiredFirstTag := [AExpRight,ANoTag,ALimitNthLoop,ABB],
        applicable := (t) ->
            Length(t.params[4]) > 0
			and (
				not t.hasTag(ABB)
				or (
					t.hasTag(ABB)
					and not _testIxA(t)
					and not _testIxAxI(t)
					and not _testAxI(t)
					and not _testJam(t)
				)
			)
    )),

    GT_BB_NthLoopJam := rec(
        requiredFirstTag := [ABB,ALimitNthLoop],
        applicable := (t) ->
            Length(t.params[4]) > 0
			and t.hasTag(ABB)
            and _testJam(t),

        # choose the rightmost index.
        freedoms := t -> let(
            xx1 := Last(t.params[2].params[1]),
            xx2 := Last(t.params[3].params[1]),

            When(xx1 <> 0, [[xx1]], [[xx2]])
        ),

        child := (t, fr) -> let(
            spl := t.params[1],
            M := spl.dims()[1],
            g := t.params[2],
            s := t.params[3],
            loopid := fr[1],
            newK := t.getTag(ABB).params[1]/t.params[4][loopid],

            When(t.hasTag(ALimitNthLoop),
                [
                    GT(spl,
                        g.without(loopid),
                        s.without(loopid),
                        ListWithout(t.params[4], loopid)).withTags(
                            When(newK = 2,
                                Filtered(t.getTags(), e -> ObjId(e) <> ABB),
                                Concat(
                                    [ABB(newK)],
                                    Filtered(t.getTags(), e -> ObjId(e) <> ABB)
                                )
                            )
                        ),
                    InfoNt(loopid)
                ],

                [
                    GT(spl, g.without(loopid),
                        s.without(loopid), ListWithout(t.params[4], loopid)),
                    InfoNt(loopid)
                ]
            )
        ),

        apply := (t, C, nt) -> let(
            loopid := nt[2].params[1],
            dft := nt[1].params[1],
            g := t.params[2],
            s := t.params[3],
            loop_dims := t.params[4],
            i := Ind(loop_dims[loopid]),

            JamISum(i,
                Scat(s.part(loopid, i, Rows(dft), loop_dims))
                * C[1]
                * Gath(g.part(loopid, i, Cols(dft), loop_dims))
            )
        )
    )
));


##  Copyright (c) 2018-2021, Carnegie Mellon University
##  See LICENSE for details

Import(simt);

Class(FFTXCUDAOpts, FFTXOpts, simt.TitanVDefaults, rec(
    tags := [],
    operations := rec(Print := s -> Print("<FFTX CUDA options record>")),
    max_threads := 2048,
    max_blocks := 1024,
    max_heap := 1024 * 1024 * 1024
));

cudaOpts := function(arg)
    local opts;
    opts := Copy(FFTXCUDAOpts);
    opts.breakdownRules.Circulant := [Circulant_PRDFT_FDataNT];
    opts.breakdownRules.PRDFT := List([PRDFT1_Base1, PRDFT1_Base2, PRDFT1_CT, PRDFT1_PF, PRDFT_PD, PRDFT_Rader], _noT);
    opts.breakdownRules.IPRDFT := List([ IPRDFT1_Base1, IPRDFT1_Base2, IPRDFT1_CT, IPRDFT_PD, IPRDFT_Rader ], _noT);
    opts.breakdownRules.PRDFT3 := List([ PRDFT3_Base1, PRDFT3_Base2, PRDFT3_CT ], _noT);
    return opts;

    return opts;
end;

Declare(ParseOptsCUDA);

Class(FFTXCUDADefaultConf, rec(
    getOpts := (self, t) >> ParseOptsCUDA(self, t),
    operations := rec(Print := s -> Print("<FFTX CUDA Default Configuration>")),
    useCUDA := true
));

Class(FFTXCUDADeviceDefaultConf, rec(
    getOpts := (self, t) >> ParseOptsCUDA(self, t),
    operations := rec(Print := s -> Print("<FFTX CUDA Device Default Configuration>")),
    useCUDADevice := true
));

cudaConf := rec(
    defaultName := "defaultCUDAConf",
    defaultOpts := (arg) >> FFTXCUDADefaultConf,
    devFunc := true,
    confHandler := cudaOpts
);

fftx.FFTXGlobals.registerConf(cudaConf);

getTargetOS := function()
    local tgt;

    if LocalConfig.osinfo.isWindows() then
        tgt := "win-x64-cuda";
    elif LocalConfig.osinfo.isLinux() then
        tgt := "linux-cuda";
    elif LocalConfig.osinfo.isDarwin() then
        tgt := "linux-cuda";    ## may work
    fi;
    return tgt;
end;

#--
Class(FFTXCUDADeviceOpts, FFTXCUDAOpts, simt.TitanVDefaults, rec(
    tags := [],
    devFunc := true,
    target := rec ( name := getTargetOS() ),
    operations := rec(Print := s -> Print("<FFTX CUDA Device options record>"))
));

cudaDeviceOpts := function(arg) # specific to WarpX size 100...
    local opts;
    opts := Copy(FFTXCUDADeviceOpts);
    opts.breakdownRules.Circulant := [Circulant_PRDFT_FDataNT];
    opts.breakdownRules.PRDFT := List([PRDFT1_Base1, PRDFT1_Base2, CopyFields(PRDFT1_CT,
            rec(allChildren := P ->Filtered(PRDFT1_CT.allChildren(P), i->When(P[1] = 100, Cols(i[1]) = 4, true)))),
        PRDFT_PD], _noT);
    opts.breakdownRules.IPRDFT := List([ IPRDFT1_Base1, IPRDFT1_Base2, IPRDFT1_CT, IPRDFT_PD ], _noT);
    opts.breakdownRules.PRDFT3 := List([ PRDFT3_Base1, PRDFT3_Base2, PRDFT3_CT ], _noT);
    opts.breakdownRules.DFT := [ DFT_Base,
        CopyFields(DFT_CT, rec(children := nt ->Filtered(DFT_CT.children(nt), i->When(nt.params[1] = 100, Cols(i[1]) = 4, true)))),
        DFT_PD ];
    opts.breakdownRules.TTensorInd := [dsA_base, L_dsA_L_base, dsA_L_base, L_dsA_base];
    return opts;
end;

cudaDeviceConf := rec(
    defaultName := "defaultCUDADeviceConf",
    defaultOpts := (arg) >> FFTXCUDADeviceDefaultConf,
    confHandler := cudaDeviceOpts
);

fftx.FFTXGlobals.registerConf(cudaDeviceConf);

# this is a first experimental opts-deriving logic. This needs to be done extensible and properly
ParseOptsCUDA := function(conf, t)
    local tt, _tt, _tt2, _conf, _opts, _HPCSupportedSizesCUDA, _thold,
    MAX_KERNEL, MAX_PRIME, MIN_SIZE, MAX_SIZE, size1, filter;

    # all dimensions need to be inthis array for the high perf MDDFT conf to kick in for now
    # size 320 is problematic at this point and needs attention. Need support for 3 stages to work first
    MAX_KERNEL := 21;
    MAX_PRIME := 13;
    MIN_SIZE := 32;
    MAX_SIZE := 320;

    _thold := MAX_KERNEL;
    filter := (e) -> When(e[1] * e[2] <= _thold ^ 2, e[1] <= _thold and e[2] <= _thold, e[1] <= _thold and e[2] >= _thold);
    size1 := Filtered([MIN_SIZE..MAX_SIZE], i -> ForAny(DivisorPairs(i), filter) and ForAll(Factors(i), j -> not IsPrime(j) or j <= MAX_PRIME));
    _HPCSupportedSizesCUDA := size1;

#    _HPCSupportedSizesCUDA := [80, 96, 100, 224, 320];
#    _thold := 16;

    if IsBound(conf.useCUDADevice) then
#        # detect real MD convolution
#        _tt := Collect(t, RCDiag)::Collect(t, MDPRDFT)::Collect(t, IMDPRDFT)::Collect(t, TTensorI);
#        if Length(_tt) = 4 then
#            _conf := FFTXGlobals.confWarpXCUDADevice();
#            _opts := FFTXGlobals.getOpts(_conf);
#            return _opts;
#        fi;

        # detect batch of DFT/PRDFT
        if ((Length(Collect(t, TTensorInd)) >= 1) or let(lst := Collect(t, TTensorI), (Length(lst) >= 1) and ForAll(lst, l->l.params[2] > 1))) and
            ((Length(Collect(t, DFT)) = 1) or (Length(Collect(t, PRDFT)) = 1) or (Length(Collect(t, IPRDFT)) = 1)) then
            _conf := FFTXGlobals.confBatchFFTCUDADevice();
            _opts := FFTXGlobals.getOpts(_conf);

            # opts for high performance CUDA cuFFT
            if ForAll(Flat(List(Collect(t, @(1, [DFT, PRDFT, IPRDFT])), j-> j.params[1])), i -> i in _HPCSupportedSizesCUDA)  then
                _opts.breakdownRules.TTwiddle := [ TTwiddle_Tw1 ];
                _opts.tags := [ASIMTKernelFlag(ASIMTGridDimX), ASIMTBlockDimY, ASIMTBlockDimX];

                _opts.globalUnrolling := 2*_thold + 1;

                _opts.breakdownRules.TTensorI := [CopyFields(IxA_L_split, rec(switch := true)),
                    fftx.platforms.cuda.L_IxA_SIMT, fftx.platforms.cuda.IxA_L_SIMT]::_opts.breakdownRules.TTensorI;
                _opts.breakdownRules.DFT := [CopyFields(DFT_tSPL_CT, rec(switch := true,
                    filter := e-> When(e[1]*e[2] <= _thold^2, e[1] <= _thold and e[2] <= _thold, e[1] <= _thold and e[2] >= _thold)))]::_opts.breakdownRules.DFT;

                _opts.unparser.simt_synccluster := _opts.unparser.simt_syncblock;
                _opts.postProcessSums := (s, opts) -> let(s1 := ApplyStrategy(s, [ MergedRuleSet(RulesFuncSimp, RulesSums, RulesSIMTFission) ], BUA, opts),
                    When(Collect(t, PRDFT)::Collect(t, IPRDFT) = [],
                        FixUpCUDASigmaSPL_3Stage(s1, opts),
                        FixUpCUDASigmaSPL_3Stage_Real(s1, opts)));
                _opts.postProcessCode := (c, opts) -> FixUpTeslaV_Code(c, opts);
#                _opts.postProcessCode := (c, opts) -> FixUpTeslaV_Code(PingPong_3Stages(c, opts), opts);
                _opts.fixUpTeslaV_Code := true;

                _opts.operations.Print := s -> Print("<FFTX CUDA HPC Batch DFT options record>");

            fi;
            return _opts;
        fi;

        # detect 3D DFT/Batch DFT
        _tt := Collect(t, MDDFT)::Collect(t, MDPRDFT)::Collect(t, IMDPRDFT);
        if Length(_tt) = 1 and Length(_tt[1].params[1]) = 3 then
            _conf := FFTXGlobals.confFFTCUDADevice();
            _opts := FFTXGlobals.getOpts(_conf);

            # opts for high performance CUDA cuFFT
            if ForAll(_tt[1].params[1], i-> i in _HPCSupportedSizesCUDA) then
                _opts.breakdownRules.MDDFT := [fftx.platforms.cuda.MDDFT_tSPL_Pease_SIMT];
                _opts.breakdownRules.MDPRDFT := [fftx.platforms.cuda.MDPRDFT_tSPL_Pease_SIMT];
                _opts.breakdownRules.IMDPRDFT := [fftx.platforms.cuda.IMDPRDFT_tSPL_Pease_SIMT];
                _opts.breakdownRules.TTwiddle := [ TTwiddle_Tw1 ];

                _opts.globalUnrolling := 2*_thold + 1;

                _opts.breakdownRules.TTensorI := [CopyFields(IxA_L_split, rec(switch := true)),
                    fftx.platforms.cuda.L_IxA_SIMT, fftx.platforms.cuda.IxA_L_SIMT]::_opts.breakdownRules.TTensorI;
                _opts.breakdownRules.DFT := [CopyFields(DFT_tSPL_CT, rec(switch := true,
                    filter := e-> When(e[1]*e[2] <= _thold^2, e[1] <= _thold and e[2] <= _thold, e[1] <= _thold and e[2] >= _thold)))]::_opts.breakdownRules.DFT;

                _opts.unparser.simt_synccluster := _opts.unparser.simt_syncblock;
#                _opts.postProcessSums := (s, opts) -> let(s1 := ApplyStrategy(s, [ MergedRuleSet(RulesFuncSimp, RulesSums, RulesSIMTFission) ], BUA, opts),
#                    FixUpCUDASigmaSPL_3Stage(s1, opts));
                _opts.postProcessSums := (s, opts) -> let(s1 := ApplyStrategy(s, [ MergedRuleSet(RulesFuncSimp, RulesSums, RulesSIMTFission) ], BUA, opts),
                    When(Collect(t, MDPRDFT)::Collect(t, IMDPRDFT) = [],
                        FixUpCUDASigmaSPL_3Stage(s1, opts),
                        FixUpCUDASigmaSPL_3Stage_Real(s1, opts)));

                _opts.postProcessCode := (c, opts) -> FixUpTeslaV_Code(c, opts);
#                _opts.postProcessCode := (c, opts) -> FixUpTeslaV_Code(PingPong_3Stages(c, opts), opts);
                _opts.fixUpTeslaV_Code := true;

                if ((Length(Collect(t, TTensorInd)) >= 1) or let(lst := Collect(t, TTensorI), (Length(lst) >= 1) and ForAll(lst, l->l.params[2] > 1))) then
                    _opts.operations.Print := s -> Print("<FFTX CUDA HPC Batch MDDFT/MDPRDFT/MDIPRDFT options record>");
                    _opts.tags := [ASIMTKernelFlag(ASIMTGridDimX), ASIMTGridDimY, ASIMTBlockDimY, ASIMTBlockDimX];
                else
                    _opts.operations.Print := s -> Print("<FFTX CUDA HPC MDDFT/MDPRDFT/MDIPRDFT options record>");
                    _opts.tags := [ASIMTKernelFlag(ASIMTGridDimX), ASIMTBlockDimY, ASIMTBlockDimX];
                fi;

                _opts.HPCSupportedSizesCUDA := _HPCSupportedSizesCUDA;

            fi;

            return _opts;
        fi;

        # detect 3D DFT/iDFT but non-convolution case
        _tt := Collect(t, MDDFT);
        if Length(_tt) = 2 and ForAll(_tt, i->Length(i.params[1]) = 3) and Sum(List(_tt, i->i.params[2])) = Product(_tt[1].params[1]) then
            _conf := FFTXGlobals.confFFTCUDADevice();
            _opts := FFTXGlobals.getOpts(_conf);

            # opts for high performance CUDA cuFFT
            if Length(Filtered(_tt, i -> ObjId(i) = MDDFT)) > 0 and ForAll(_tt[1].params[1], i-> i in _HPCSupportedSizesCUDA) then
                _opts.breakdownRules.MDDFT := [fftx.platforms.cuda.MDDFT_tSPL_Pease_SIMT];
                _opts.breakdownRules.MDPRDFT := [fftx.platforms.cuda.MDPRDFT_tSPL_Pease_SIMT];
                _opts.breakdownRules.IMDPRDFT := [fftx.platforms.cuda.IMDPRDFT_tSPL_Pease_SIMT];
                _opts.breakdownRules.TTwiddle := [ TTwiddle_Tw1 ];
                _opts.tags := [ASIMTKernelFlag(ASIMTGridDimX), ASIMTBlockDimY, ASIMTBlockDimX];

                _opts.globalUnrolling := 2*_thold + 1;

                _opts.breakdownRules.TTensorI := [CopyFields(IxA_L_split, rec(switch := true)),
                    fftx.platforms.cuda.L_IxA_SIMT, fftx.platforms.cuda.IxA_L_SIMT]::_opts.breakdownRules.TTensorI;
                _opts.breakdownRules.DFT := [CopyFields(DFT_tSPL_CT, rec(switch := true,
                    filter := e-> When(e[1]*e[2] <= _thold^2, e[1] <= _thold and e[2] <= _thold, e[1] <= _thold and e[2] >= _thold)))]::_opts.breakdownRules.DFT;

                _opts.unparser.simt_synccluster := _opts.unparser.simt_syncblock;
                _opts.postProcessSums := (s, opts) -> let(s1 := ApplyStrategy(s, [ MergedRuleSet(RulesFuncSimp, RulesSums, RulesSIMTFission) ], BUA, opts),
                    FixUpCUDASigmaSPL_3Stage(s1, opts));
                _opts.postProcessCode := (c, opts) -> FixUpTeslaV_Code(PingPong_3Stages(c, opts), opts);
                _opts.fixUpTeslaV_Code := true;

                _opts.operations.Print := s -> Print("<FFTX CUDA HPC MDDFT options record>");

            fi;

            return _opts;
        fi;

        # promote with default conf rules
        tt := _promote1(Copy(t));

        if ObjId(tt) = TFCall then
            _tt := tt.params[1];
            # check for convolution
            if (ObjId(_tt) in [MDRConv, MDRConvR, IOPrunedMDRConv]) or ((ObjId(_tt) in [TTensorI, TTensorInd]) and (ObjId(_tt.params[1]) in [MDRConv, MDRConvR])) then
                _conf := FFTXGlobals.confMDRConvCUDADevice();
                _opts := FFTXGlobals.getOpts(_conf);

                # opts for high performance CUDA cuFFT
                if (ObjId(_tt) in [MDRConv, MDRConvR, IOPrunedMDRConv] and ForAll(_tt.params[1], i-> i in _HPCSupportedSizesCUDA)) or
                    (ObjId(_tt) in [TTensorI, TTensorInd] and ForAll(_tt.params[1].params[1], i-> i in _HPCSupportedSizesCUDA)) then
                    _opts.breakdownRules.MDDFT := [fftx.platforms.cuda.MDDFT_tSPL_Pease_SIMT];
                    _opts.breakdownRules.MDPRDFT := [fftx.platforms.cuda.MDPRDFT_tSPL_Pease_SIMT];
                    _opts.breakdownRules.IMDPRDFT := [fftx.platforms.cuda.IMDPRDFT_tSPL_Pease_SIMT];
                    _opts.breakdownRules.TTwiddle := [ TTwiddle_Tw1 ];

                    _opts.globalUnrolling := 2*_thold + 1;

                    _opts.breakdownRules.TTensorI := [CopyFields(IxA_L_split, rec(switch := true)),
                        fftx.platforms.cuda.L_IxA_SIMT, fftx.platforms.cuda.IxA_L_SIMT]::_opts.breakdownRules.TTensorI;
                    _opts.breakdownRules.DFT := [CopyFields(DFT_tSPL_CT, rec(switch := true,
                        filter := e-> When(e[1]*e[2] <= _thold^2, e[1] <= _thold and e[2] <= _thold, e[1] <= _thold and e[2] >= _thold)))]::_opts.breakdownRules.DFT;

                    _opts.unparser.simt_synccluster := _opts.unparser.simt_syncblock;
    #                _opts.postProcessSums := (s, opts) -> let(s1 := ApplyStrategy(s, [ MergedRuleSet(RulesFuncSimp, RulesSums, RulesSIMTFission) ], BUA, opts),
    #                    FixUpCUDASigmaSPL_3Stage(s1, opts));
                    _opts.postProcessSums := (s, opts) -> let(s1 := ApplyStrategy(s, [ MergedRuleSet(RulesFuncSimp, RulesSums, RulesSIMTFission) ], BUA, opts),
                        When(Collect(t, MDPRDFT)::Collect(t, IMDPRDFT) = [],
                            FixUpCUDASigmaSPL_3Stage(s1, opts),
                            FixUpCUDASigmaSPL_3Stage_Real(s1, opts)));

                    _opts.postProcessCode := (c, opts) -> FixUpTeslaV_Code(c, opts);
    #                _opts.postProcessCode := (c, opts) -> FixUpTeslaV_Code(PingPong_3Stages(c, opts), opts);
                    _opts.fixUpTeslaV_Code := true;

                    if ((Length(Collect(t, TTensorInd)) >= 1) or let(lst := Collect(t, TTensorI), (Length(lst) >= 1) and ForAll(lst, l->l.params[2] > 1))) then
                        _opts.operations.Print := s -> Print("<FFTX CUDA HPC Batch MDRConv/MDRConvR/IOPrunedMDRConv options record>");
                        _opts.tags := [ASIMTKernelFlag(ASIMTGridDimX), ASIMTGridDimY, ASIMTBlockDimY, ASIMTBlockDimX];
                    else
                        _opts.operations.Print := s -> Print("<FFTX CUDA HPC MDRConv/MDRConvR/IOPrunedMDRConv options record>");
                        _opts.tags := [ASIMTKernelFlag(ASIMTGridDimX), ASIMTBlockDimY, ASIMTBlockDimX];
                    fi;

                    _opts.HPCSupportedSizesCUDA := _HPCSupportedSizesCUDA;

                fi;

                return _opts;
            fi;
            # check for Hockney. This is for N=130
            if ObjId(_tt) = IOPrunedMDRConv  and _tt.params[1] = [130,130,130] then
                _conf := FFTXGlobals.confHockneyMlcCUDADevice();
                _opts := FFTXGlobals.getOpts(_conf);
                return _opts;
            fi;
            # check for general Hockney.
            if ObjId(_tt) = IOPrunedMDRConv then
                _conf := FFTXGlobals.confMDRConvCUDADevice();
                _opts := FFTXGlobals.getOpts(_conf);
                _opts.tags := [ASIMTKernelFlag(ASIMTGridDimY), ASIMTGridDimX, ASIMTBlockDimZ];
                return _opts;
            fi;
        fi;

        # check for WarpX
        _conf := FFTXGlobals.confWarpXCUDADevice();
        _opts := FFTXGlobals.getOpts(_conf);
        tt := _opts.preProcess(Copy(t));
        if ObjId(tt) = TFCall and ObjId(tt.params[1]) = TCompose then
            _tt := tt.params[1].params[1];
            # detect promoted WarpX
            if IsList(_tt) and Length(_tt) = 3 and List(_tt, ObjId) = [ TNoDiagPullinRight, TRC, TNoDiagPullinLeft ] then
                return _opts;
            fi;
        fi;
        # we are doing nothing special
        return FFTXGlobals.getOpts(conf);
    fi;
    if IsBound(conf.useCUDA) then
        return FFTXGlobals.getOpts(conf);
    fi;

    # Here we have to handle GPU configs
    Error("Don't know how to derive opts!\n");
end;


#
# A6<E8 (char2) is below
#

#
# cls 31: E8(a7) MIZUNO
#
handle31char2:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,tmp11,tmp22,tmp33,tmp44,o,info,rep,cent,uu;
    o:=Classes(orbs)[31];
    info:=infos[31];
    rep:=Representative(o);

    uu:=Unipotent(alg,[[3,1],[16,1],[23,1],[8,1],[51,1],[58,1]],Ordering(rep));
    tmp1:=Conj(rep,uu);

    uu:=Unipotent(alg,[[3,1],[16,1],[8,1],[7,1],[10,1]],Ordering(rep));
    tmp2:=Conj(rep,uu);
    tmp2:=ApplyRootsReflections(tmp2,[16,3,8]);
    uu:=Unipotent(alg,[[7,1],[10,1],[3,1],[16,1],[8,1],[26,1],[41,1],[53,1],[80,1]],Ordering(tmp2));
    tmp2:=Conj(tmp2,uu);

    uu:=Unipotent(alg,[[16,1],[6,1],[1,1]],Ordering(rep));
    tmp3:=Conj(rep,uu);
    tmp3:=ConjNegUa(tmp3,7,1);
    tmp3:=ConjNegUa(tmp3,10,1);
    uu:=Unipotent(alg,[[45,1],[51,1]],Ordering(tmp3));
    tmp3:=Conj(tmp3,uu);

    uu:=Unipotent(alg,[[10,1],[4,1]],Ordering(rep));
    tmp4:=Conj(rep,uu);
    tmp4:=ConjNegUa(tmp4,6,1);
    tmp4:=ConjNegUa(tmp4,1,1);
    uu:=Unipotent(alg,[[37,1]],Ordering(tmp4));
    tmp4:=Conj(tmp4,uu);

#---
    cent:=FromPositiveBorel(o,info[2]);

    tmp11:=0;
    uu:=Unipotent(alg,[[3,1],[16,1],[23,1],[8,1],[51,1],[58,1]],Ordering(cent));
    tmp11:=Conj(cent,uu);

    tmp22:=0;
    uu:=Unipotent(alg,[[3,1],[16,1],[8,1],[7,1],[10,1]],Ordering(cent));
    tmp22:=Conj(cent,uu);
    tmp22:=ApplyRootsReflections(tmp22,[16,3,8]);
    uu:=Unipotent(alg,[[7,1],[10,1],[3,1],[16,1],[8,1],[26,1],[41,1],[53,1],[80,1]],Ordering(tmp22));
    tmp22:=Conj(tmp22,uu);

    tmp33:=0;
    uu:=Unipotent(alg,[[16,1],[6,1],[1,1]],Ordering(cent));
    tmp33:=Conj(cent,uu);
    tmp33:=ConjNegUa(tmp33,7,1);
    tmp33:=ConjNegUa(tmp33,10,1);
    uu:=Unipotent(alg,[[45,1],[51,1]],Ordering(tmp33));
    tmp33:=Conj(tmp33,uu);

    tmp44:=0;
    uu:=Unipotent(alg,[[10,1],[4,1]],Ordering(cent));
    tmp44:=Conj(cent,uu);
    tmp44:=ConjNegUa(tmp44,6,1);
    tmp44:=ConjNegUa(tmp44,1,1);
    uu:=Unipotent(alg,[[37,1]],Ordering(tmp44));
    tmp44:=Conj(tmp44,uu);

    return [tmp1,tmp2,tmp3,tmp4,tmp11,tmp22,tmp33,tmp44];
end;

#
# cls 30: E8(a7) MIZUNO
#
handle30char3:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,tmp11,tmp22,tmp33,tmp44,o,info,rep,
    cent,uu;
    o:=Classes(orbs)[30];
    info:=infos[30];
    rep:=Representative(o);

    tmp1:=ConjugateByTori(rep,[1,2,29],[-1,-1,-1]);
    uu:=Unipotent(alg,[[3,1],[16,-1],[23,-1],[8,1],[51,-1],[58,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);

    tmp2:=ConjugateByTori(rep,[50],[-1]);
    uu:=Unipotent(alg,[[3,1],[16,-1],[8,1],[7,-1],[10,-1]],Ordering(tmp2));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ApplyRootsReflections(tmp2,[16,3,8]);
    uu:=Unipotent(alg,[[7,1],[10,1],[3,1],[16,-1],[8,1],[15,-1],[17,1],[26,1],[41,-1],[53,1],[80,-1]],Ordering(tmp2));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[50,53],[-1,-1]);

#    tmp3:=ConjugateByTori(rep,[31],[1]);
    uu:=Unipotent(alg,[[16,-1],[6,-1],[1,-1]],Ordering(rep));
    tmp3:=Conj(rep,uu);
    tmp3:=ConjNegUa(tmp3,7,1);
    tmp3:=ConjNegUa(tmp3,10,1);
    uu:=Unipotent(alg,[[16,-1],[45,-1],[51,1]],Ordering(tmp3));
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjugateByTori(tmp3,[31],[-1]);

    tmp4:=ConjugateByTori(rep,[2,3,5],[-1,-1,-1]);
    uu:=Unipotent(alg,[[10,1],[4,1]],Ordering(rep));
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjNegUa(tmp4,6,1);
    tmp4:=ConjNegUa(tmp4,1,1);
    tmp4:=Conj(tmp4,uu);
    uu:=Unipotent(alg,[[37,1]],Ordering(tmp4));
    tmp4:=Conj(tmp4,uu);

#---
    cent:=FromPositiveBorel(o,info[2]);

    tmp11:=0;
    tmp11:=ConjugateByTori(cent,[1,2,29],[-1,-1,-1]);
    uu:=Unipotent(alg,[[3,1],[16,-1],[23,-1],[8,1],[51,-1],[58,1]],Ordering(cent));
    tmp11:=Conj(tmp11,uu);

    tmp22:=0;
    tmp22:=ConjugateByTori(cent,[50],[-1]);
    uu:=Unipotent(alg,[[3,1],[16,-1],[8,1],[7,-1],[10,-1]],Ordering(tmp22));
    tmp22:=Conj(tmp22,uu);
    tmp22:=ApplyRootsReflections(tmp22,[16,3,8]);
    uu:=Unipotent(alg,[[7,1],[10,1],[3,1],[16,-1],[8,1],[15,-1],[17,1],[26,1],[41,-1],[53,1],[80,-1]],Ordering(tmp22));
    tmp22:=Conj(tmp22,uu);
    tmp22:=ConjugateByTori(tmp22,[50,53],[-1,-1]);

    tmp33:=0;
#    tmp33:=ConjugateByTori(rep,[31],[1]);
    uu:=Unipotent(alg,[[16,-1],[6,-1],[1,-1]],Ordering(cent));
    tmp33:=Conj(cent,uu);
    tmp33:=ConjNegUa(tmp33,7,1);
    tmp33:=ConjNegUa(tmp33,10,1);
    uu:=Unipotent(alg,[[16,-1],[45,-1],[51,1]],Ordering(tmp33));
    tmp33:=Conj(tmp33,uu);
    tmp33:=ConjugateByTori(tmp33,[31],[-1]);

    tmp44:=0;
    tmp44:=ConjugateByTori(cent,[2,3,5],[-1,-1,-1]);
    uu:=Unipotent(alg,[[10,1],[4,1]],Ordering(cent));
    tmp44:=Conj(tmp44,uu);
    tmp44:=ConjNegUa(tmp44,6,1);
    tmp44:=ConjNegUa(tmp44,1,1);
    tmp44:=Conj(tmp44,uu);
    uu:=Unipotent(alg,[[37,1]],Ordering(tmp44));
    tmp44:=Conj(tmp44,uu);

    return [tmp1,tmp2,tmp3,tmp4,tmp11,tmp22,tmp33,tmp44];
end;

#
# cls 29: E8(a7) MIZUNO
#
handle29char5:=function(orbs,infos)
    local tmp1,tmp2,tmp3,tmp4,tmp11,tmp22,tmp33,tmp44,o,info,rep,
    cent,uu;
    o:=Classes(orbs)[29];
    info:=infos[29];
    rep:=Representative(o);

    tmp1:=ConjugateByTori(rep,[1,2,29],[-1,-1,-1]);
    uu:=Unipotent(alg,[[3,1],[16,-1],[23,-1],[8,1],[51,-1],[58,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);

    tmp2:=ConjugateByTori(rep,[50],[-1]);
    uu:=Unipotent(alg,[[3,1],[16,-1],[8,1],[7,-1],[10,-1]],Ordering(tmp2));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ApplyRootsReflections(tmp2,[16,3,8]);
    uu:=Unipotent(alg,[[7,1],[10,1],[3,1],[16,-1],[8,1],[15,2],[17,3],[26,1],[41,-1],[53,1],[80,-1]],Ordering(tmp2));
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[50,53],[-1,-1]);

#    tmp3:=ConjugateByTori(rep,[31],[1]);
    uu:=Unipotent(alg,[[16,-1],[6,-1],[1,-1]],Ordering(rep));
    tmp3:=Conj(rep,uu);
    tmp3:=ConjNegUa(tmp3,7,1);
    tmp3:=ConjNegUa(tmp3,10,1);
    uu:=Unipotent(alg,[[16,2],[45,-1],[51,1]],Ordering(tmp3));
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjugateByTori(tmp3,[31],[-1]);

    tmp4:=ConjugateByTori(rep,[2,3,5],[-1,-1,-1]);
    uu:=Unipotent(alg,[[10,1],[4,1]],Ordering(rep));
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjNegUa(tmp4,6,1);
    tmp4:=ConjNegUa(tmp4,1,1);
#    tmp4:=Conj(tmp4,uu);
    uu:=Unipotent(alg,[[10,3],[4,3],[37,1]],Ordering(tmp4));
    tmp4:=Conj(tmp4,uu);

#---
    cent:=FromPositiveBorel(o,info[2]);

    tmp11:=0;
    tmp11:=ConjugateByTori(cent,[1,2,29],[-1,-1,-1]);
    uu:=Unipotent(alg,[[3,1],[16,-1],[23,-1],[8,1],[51,-1],[58,1]],Ordering(cent));
    tmp11:=Conj(tmp11,uu);

    tmp22:=0;
    tmp22:=ConjugateByTori(cent,[50],[-1]);
    uu:=Unipotent(alg,[[3,1],[16,-1],[8,1],[7,-1],[10,-1]],Ordering(tmp22));
    tmp22:=Conj(tmp22,uu);
    tmp22:=ApplyRootsReflections(tmp22,[16,3,8]);
    uu:=Unipotent(alg,[[7,1],[10,1],[3,1],[16,-1],[8,1],[15,2],[17,3],[26,1],[41,-1],[53,1],[80,-1]],Ordering(tmp22));
    tmp22:=Conj(tmp22,uu);
    tmp22:=ConjugateByTori(tmp22,[50,53],[-1,-1]);

    tmp33:=0;
#    tmp33:=ConjugateByTori(rep,[31],[1]);
    uu:=Unipotent(alg,[[16,-1],[6,-1],[1,-1]],Ordering(cent));
    tmp33:=Conj(cent,uu);
    tmp33:=ConjNegUa(tmp33,7,1);
    tmp33:=ConjNegUa(tmp33,10,1);
    uu:=Unipotent(alg,[[16,2],[45,-1],[51,1]],Ordering(tmp33));
    tmp33:=Conj(tmp33,uu);
    tmp33:=ConjugateByTori(tmp33,[31],[-1]);

    tmp44:=0;
    tmp44:=ConjugateByTori(cent,[2,3,5],[-1,-1,-1]);
    uu:=Unipotent(alg,[[10,1],[4,1]],Ordering(cent));
    tmp44:=Conj(tmp44,uu);
    tmp44:=ConjNegUa(tmp44,6,1);
    tmp44:=ConjNegUa(tmp44,1,1);
#    tmp44:=Conj(tmp44,uu);
    uu:=Unipotent(alg,[[10,3],[4,3],[37,1]],Ordering(tmp44));
    tmp44:=Conj(tmp44,uu);

    return [tmp1,tmp2,tmp3,tmp4,tmp11,tmp22,tmp33,tmp44];
end;

#
# cls 30: A_6 (THE RIGHT WAY)
#
handle30char2bun:=function(orbs,infos)
    local tmp1,tmp2,o,info,rep,
    cent,uu;
    o:=Classes(orbs)[30];
    info:=infos[30];

    rep:=Representative(o);
    tmp1:=ApplyRootsReflections(rep,[7,61,97,4,25,4,2,3,5,12,27,39]);# [7,61,97,4,25,4,2,3,5] longest element in E7, [12,27,39] longest element in A6
    uu:=Unipotent(chevalleyAdj(rep),[[3,1],[5,1],[7,1],[11,1],[12,1],[27,1],[28,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[7,61,97,4,25,4,2,3,5,12,27,39]);# [7,61,97,4,25,4,2,3,5] longest element in E7, [12,27,39] longest element in A6
    uu:=Unipotent(chevalleyAdj(cent),[[3,1],[5,1],[7,1],[11,1],[12,1],[27,1],[28,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);

    return [tmp1,tmp2];
end;


# Copyright (c) 2018-2021, Carnegie Mellon University
# See LICENSE for details

# -------------------------------------------------------------------------
# SSE bit-level instructions
# 128x1i: 128-way bit register
# -------------------------------------------------------------------------

#Class(exp_128x1,  rec(v := 128, computeType := self >> TVect(T_UInt(1), 128)));
#Class(cmd_128x1,  rec(v := 128, computeType := self >> TVect(T_UInt(1), 128)));

Class(sseb_shl, VecExp_128.binary());
Class(sseb_shr, VecExp_128.binary());
Class(sseb_xor, VecExp_128.binary());
Class(sseb_and, VecExp_128.binary());
Class(sseb_or , VecExp_128.binary());
Class(sseb_not, VecExp_128.binary());

Class(sseb_bcast, VecExp_128.binary());

Class(sseb_uload,      VecExp_128.unary());
Class(sseb_load_sd_64, VecExp_128.unary());
Class(sseb_load_sd_32, VecExp_128.unary());
Class(sseb_load_sd_16, VecExp_128.unary());
Class(sseb_load_lo_64, VecExp_128.binary());
Class(sseb_load_hi_64, VecExp_128.binary());

Class(sseb_ustore,      VecExpCommand.binary());
Class(sseb_store_sd_64, VecExpCommand.binary());
Class(sseb_store_sd_32, VecExpCommand.binary());
Class(sseb_store_sd_16, VecExpCommand.binary());

Class(sseb_store_lo_64, VecExpCommand.ternary());
Class(sseb_store_hi_64, VecExpCommand.ternary());

#Class(vunpacklo_16x32f,  vbinop_new, exp_16x32f, rec(semantic := (in1, in2, p) -> unpacklo(in1, in2, 16, 1)));
#Class(vunpackhi_16x32f,  vbinop_new, exp_16x32f, rec(semantic := (in1, in2, p) -> unpackhi(in1, in2, 16, 1)));
#Class(vunpacklo2_16x32f, vbinop_new, exp_16x32f, rec(semantic := (in1, in2, p) -> unpacklo(in1, in2, 16, 2)));
#Class(vunpackhi2_16x32f, vbinop_new, exp_16x32f, rec(semantic := (in1, in2, p) -> unpackhi(in1, in2, 16, 2)));
#Class(vunpacklo4_16x32f, vbinop_new, exp_16x32f, rec(semantic := (in1, in2, p) -> unpacklo(in1, in2, 16, 4)));
#Class(vunpackhi4_16x32f, vbinop_new, exp_16x32f, rec(semantic := (in1, in2, p) -> unpackhi(in1, in2, 16, 4)));

# 128-bit mask split up in 8 bit chunks
 _vstore_16x8_mask := nmany -> List([0..15], i -> "0x" :: HexStringInt(Cond(
    nmany >= (i+1)*8, 255,
    nmany < i*8, 0,
    (nmany mod 8) <> 0, let(b := nmany mod 8,
	(2^b - 1) * 2^(8-b)),  # upper b bits (of 8) must be 1s
    0
)));

Class(SSEB_128, SIMD_Intel, rec(
    countrec := rec(
        ops := [
            [add, sub],
	    [mul],
            [sseb_bcast], # shuffles
            [sseb_load_sd_64, sseb_load_sd_32, sseb_load_sd_16],
            [deref],
            Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]", "[mults]", "[vperms]", "[svldst]", "[vldst]", "[vval]"],
        type := "TVect",
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

    includes := () -> ["<include/bitsse.h>"] :: _MM_MALLOC() :: _PMMINTRIN() :: _EMMINTRIN() :: _XMMINTRIN(),
    active   := true,
    info     := "SSE2 128 x 1-bit",
    ctype    := "__bit",
    instr    := [],
    v        := 128,
    t        := BitVector(128),
    bits     := 1,
    isFloat  := false,
    isFixedPoint := false,
    splopts := rec(),

    v_ones  := BitVector(128).one(),
    v_zeros := BitVector(128).zero(),
    val     := bits -> BitVector(128).value(bits),

    dupload  := (self, y, x) >> Checked(ObjId(x)=nth, # NOTE: is there a better way?
	let(base := x.loc,
	    ofs  := x.idx,
	    v    := self.v,
	    xvec := Cond(IsUnalignedPtrT(base.t), sseb_uload(base + idiv(ofs,v)*v),
		         vtref(self.t, base, idiv(ofs, v))),
	    assign(y, sseb_bcast(xvec, imod(ofs, v))))),

    svload := [ [ ], # load using subvecs of len 1
                [ ], # load using subvecs of len 2
                [ ], # load using subvecs of len 4
    ],

    svstore := [ [ ], # store using subvecs of len 1
                 [ ], # store using subvecs of len 2
                 [ ], # store using subvecs of len 4
    ],

    # keep the n lower scalars and zero the other ones
    mask := (self, c, n) >> Cond(
	n = 128, c,
        bin_and(c, self.val(Replicate(n, 1) :: Replicate(self.v - n, 0)))),

    loadCont := (self, n, y, yofs, x, xofs, xofs_align, opts) >> let(
#	_t := Checked(xofs=0, 0), # NOTE: remove this limitation, this is because xofs is in bits
	a  := _unwrap(xofs_align),
	nn := _unwrap(n),
	yy := vtref(self.t, y, yofs),
	m  := x -> self.mask(x, nn),
	# NOTE: BELOW CODE IS NOT VERIFIED, in particular shift directions might be invalid
	# NOTE: sseb_shl/shr need to be implemented in the backend,
        # NOTE : SSE requires shift values to be immediates (or does it not?)
	Cond(a = 0 and not IsUnalignedPtrT(x.t),
	         assign(yy, m(vtref(self.t, x, div(xofs, self.v)))),

	     # known alignment, sv is small, so that we only need 1 aligned load + 1 shift + mask
	     IsInt(a) and not IsUnalignedPtrT(x.t) and (nn <= self.v - a),
		 let(v1 := vtref(self.t, x, div(xofs - a, self.v)),
		     assign(yy, m(sseb_shl(v1, a)))),

	     # known alignment, sv covers 2 vectors, 2 aligned loads + 2 shifts + mask
	     # NB: no masking is needed because shifts will do the job
	     IsInt(a) and not IsUnalignedPtrT(x.t),
		 let(v1 := vtref(self.t, x, div(xofs - a, self.v)),
		     v2 := vtref(self.t, x, div(xofs - a, self.v) + 1),
		     assign(yy, m(sseb_or(sseb_shl(v1, a), sseb_shr(v2, self.v - a))))),

             # else, unknown alignment, use unaligned load
	     assign(yy, m(sseb_uload(x + xofs)))
        )
    ),

    # NOTE: The store mask has 8-bit granularity, how do we express this??
    storeCont := (self, n, y, yofs, yofs_align, x, xofs, opts) >> let(
#	_t := Checked(yofs=0, 0), # NOTE: remove this limitation, this is because yofs is in bits
	a  := _unwrap(yofs_align),
	nn := _unwrap(n),
	xx := vtref(self.t, x, xofs),
	Cond(nn = self.v and a = 0 and not IsUnalignedPtrT(y.t),
	        assign(vtref(self.t, y, div(yofs, self.v)), xx),
	     nn = self.v,
	        sseb_ustore(y + yofs, xx),
	     # else
	     #NOTE: yofs is in bits:
	        vstoremsk_16x8i(nth(y, yofs).toPtr(TReal), xx, _vstore_16x8_mask(nn)))),

));

_bitsBytes := (bits) -> let(bytelsts := List([0..Length(bits)/8-1], i -> bits{ [1..8] + 8*i }),
	                 List(bytelsts, l -> Sum([ 1 .. 8 ], i -> l[i] * 2 ^ (i - 1))));

Class(BitSSEUnparser, SSEUnparser, rec(
    infixbreak := 20,

    BitVector := (self, t, vars, i, is) >> Print("__m128i ", self.infix(vars, ", ", i + is)),

    Value := (self, o, i, is) >> Cond(ObjId(o.t) = BitVector,
	let(bytenums := _bitsBytes(o.v),
            Cond(self.cx.isInside(Value) and Length(self.cx.Value) >= 2, # nested in an array
		 Print(            "{", self.infix(bytenums, ", "), "}"),
		 Print("_mm_set_epi8(", self.infix(Reversed(bytenums), ", "), ")"))),

	Inherited(o, i, is)),

    mul := (self, o, i, is) >> Cond(ObjId(o.t) = BitVector and Length(o.args)=2,
	self.prefix("_mm_and_si128", o.args),
	Inherited(o, i, is)),

    add := (self, o, i, is) >> Cond(ObjId(o.t) = BitVector and Length(o.args)=2,
	self.prefix("_mm_xor_si128", o.args),
	Inherited(o, i, is)),

    sseb_uload := (self, o, i, is) >> self(vloadu_16x8i(o.args[1]), i, is),

    sseb_ustore := (self, o, i, is) >> self(vstoreu_16x8i(o.args[1], o.args[2]), i, is)
));