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

Class(RulesSIMTFission, RuleSet);

RewriteRules(RulesSIMTFission, rec(
    FissionSIMTISum := Rule([@(1, SIMTISum), @(2, Compose, e->ForAll(e.children(), c->ObjId(c) = SIMTISum))],
        e-> let(gath := Gath(fTensor(fBase(@(1).val.var), fId(Cols(@(2).val.child(1))))),
                scat := Scat(fTensor(fBase(@(1).val.var), fId(Cols(@(2).val.child(1))))),
                c1 := @(2).val.child(1) * gath,
                cn := scat * Last(@(2).val.children()),
                cms := List(DropLast(Drop(@(2).val.children(), 1), 1), c->scat * c *gath),
                Compose(List([c1]::cms::[cn], c -> SIMTISum(@(1).val.simt_dim, @(1).val.var, @(1).val.domain, c))))),
));

RewriteRules(RulesSums, rec(
    OO_Gath := Rule([@(1, Compose), OO, Gath],
        e -> ApplyFunc(OO, @(1).val.dims())),
    Gath_OO := Rule([@(1, Compose), Scat, OO],
        e -> ApplyFunc(OO, @(1).val.dims())),
    Scat_OO_Gath := Rule([@(1, Compose), Scat, OO, Gath],
        e -> ApplyFunc(OO, @(1).val.dims())),
));

SIMT_NextLoop := (s, simtidx) ->
    SubstBottomUp(s, [@(1, SIMTISum), @(2, Compose, e->ForAll(e.children(), c->ObjId(c) in [ISum, SUM]))],
        e->let(sx1c := @(2).val.children(),
            doms := List(sx1c, c->When(ObjId(c) = ISum, c.domain, Length(c.children()))),
            mdom := Maximum(doms),
            ranges := List(List(sx1c, c->[0, When(ObjId(c) = ISum, c.domain, Length(c.children()))-1])),
            newc := List([1..Length(sx1c)],
                i-> When(ObjId(sx1c[i]) = ISum,
                        SIMTISum(simtidx(mdom, ranges[i]), sx1c[i].var, sx1c[i].domain, sx1c[i].child(1)),
                        SIMTSUM(simtidx(mdom, ranges[i]), sx1c[i].children()))),
            SIMTISum(@(1).val.simt_dim, @(1).val.var, @(1).val.domain, Compose(newc))
        )
    );

_fBaseVar := function(s, i, v)
    local cands;

    cands := Collect(s, @(0, fBase, e->e.params[1]=i.range and IsInt(e.params[2]) and e.params[2] = v));
    if cands = [] then return s; fi;

    s := SubstTopDown(s, @(0, fBase, e->e.params[1]=i.range and IsInt(e.params[2]) and e.params[2] = v),
        e->fBase(i));
    return s;
end;

FixUpCUDASigmaSPL := function(s, opts)
    local srt, kernels, i, _s, newv;

    if IsBound(s.ruletree) then srt := s.ruletree; fi;

    s := SIMT_NextLoop(s, ASIMTBlockDimY);
    s := SIMT_NextLoop(s, ASIMTBlockDimX);

    s:= SubstTopDown(s, @(1, SIMTSUM),
        e->let(
            its := @(1).val.simt_dim.params[1],
            ii := Ind(its),
            ch := @(1).val.children(),
            nch := Length(ch),
            SIMTISum(@(1).val.simt_dim, ii, nch, SUM(List([0..nch-1],
               i->COND(eq(ii, V(i)), _fBaseVar(ch[i+1], ii, i), ApplyFunc(OO, ch[i+1].dims()))))))
    );

    if Length(Collect(s,  @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX))) > 0 then
        s := SubstTopDown(s, [@@(1, SIMTISum, (e,cx)->ObjId(e.simt_dim) = ASIMTBlockDimY), @(2, [BB, SUM])],
            (e,cx)->let(xdimcds := Collect(Filtered(cx.SIMTISum, s->ObjId(s.simt_dim) = ASIMTKernelFlag), @(0, SIMTISum, (e)->ObjId(e.simt_dim) = ASIMTBlockDimX)),
                xdim := When(xdimcds <> [], xdimcds[1].domain, @@(1).val.domain),
                SIMTISum(@@(1).val.simt_dim, @@(1).val.var, @@(1).val.domain,
                    SIMTISum(ASIMTBlockDimX(xdim,[0,0]), Ind(1), 1, @@(1).val.child(1))))
        );

        s := ApplyStrategy(s, [RulesSIMTFission, RulesSums], BUA, opts);
#        s := RulesSums(RulesSIMTFission(s));

        s := SubstTopDown(s, @@(1, SIMTSUM, (e,cx)->ObjId(e.simt_dim) = ASIMTBlockDimY and ForAll(e.children(), c->not ObjId(c) in [SIMTISum, SIMTSUM])),
            (e,cx)->let(xdimcds := Collect(Filtered(cx.SIMTISum, s->ObjId(s.simt_dim) = ASIMTBlockDimZ), @(0, SIMTISum, (e)->ObjId(e.simt_dim) = ASIMTBlockDimX)),
                xdim := When(xdimcds <> [], xdimcds[1].domain, When(IsBound(@@(1).val.domain), @@(1).val.domain, 1)),
                SIMTSUM(@@(1).val.simt_dim,
                    List(@@(1).val.children(), c->SIMTISum(ASIMTBlockDimX(xdim, [0,0]), Ind(1), 1, c))))
        );

        s := SubstTopDown(s, [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX and Set(Flat(e.simt_dim.simt_all_ranges())) = [0]), @(2, ISum)],
            e -> SIMTISum(ASIMTBlockDimX(@(2).val.domain), @(2).val.var, @(2).val.domain, @(2).val.child(1))
        );

        if ObjId(s) = Compose then
            kernels := s.children();

            for i in [1..Length(kernels)] do
                _s := kernels[i];
                if Length(Collect(_s,  @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX))) > 0 then
                    newv := Maximum(List(List(Collect(_s, @(1, [SIMTSUM, SIMTISum], e->ObjId(e.simt_dim) = ASIMTBlockDimX)), e-> e.simt_dim), i->i.params[1]));
                    _s := SubstTopDown(_s, @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX),
                        e->SIMTISum(ApplyFunc(ASIMTBlockDimX, [newv]::Drop(@(1).val.simt_dim.params, 1)), @(1).val.var, @(1).val.domain, @(1).val.child(1))
                    );

                    _s := SubstTopDown(_s, @(1, SIMTSUM, e->ObjId(e.simt_dim) = ASIMTBlockDimX),
                        e->SIMTSUM(ApplyFunc(ASIMTBlockDimX, [newv]::Drop(@(1).val.simt_dim.params, 1)), @(1).val.children())
                    );
                fi;
                kernels[i] := _s;
            od;
            s := Compose(kernels);
        fi;

        s := SubstTopDown(s, [@(1, SIMTSUM, e->ObjId(e.simt_dim) = ASIMTBlockDimX), @(2,BB), @(3, ISum)],
            e->SIMTSUM(ApplyFunc(ObjId(@(1).val.simt_dim),  [@(1).val.simt_dim.params[1], [0, @(3).val.domain-1], [@(3).val.domain,@(3).val.domain]]),
                SIMTISum(ApplyFunc(ObjId(@(1).val.simt_dim),  [@(1).val.simt_dim.params[1], [0, @(3).val.domain-1]]), @(3).val.var, @(3).val.domain, @(3).val.child(1)),
                @(2).val)
        );
    fi;
    # somehow SIMTSUM by itself does not create the necessary loop dimension (?)
    s := SubstBottomUp(s, [@(1,SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimZ), [BB, @(2,SUM)], ...],
        e->let(ch := @(2).val.children(), nch := Length(ch),
            ii := Ind(nch),
            SIMTISum(@(1).val.simt_dim, @(1).val.var, @(1).val.domain,
                SIMTISum(ASIMTBlockDimY(nch), ii, nch, SUM(List([0..nch-1], i->COND(eq(ii, V(i)), BB(_fBaseVar(ch[i+1], ii, i)), ApplyFunc(OO, ch[i+1].dims())))))))
    );

    s := SubstTopDown(s, @(1, Grp), e->e.child(1));

    s := SubstBottomUp(s, @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimZ),
        e->let(SIMTISum(ASIMTGridDimZ(@(1).val.simt_dim.params[1]), @(1).val.var, @(1).val.domain, @(1).val.child(1)))
    );

    if IsBound(opts.fixUpZ) and opts.fixUpZ then
        s := SubstBottomUp(s, @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTGridDimZ),
            e -> let(
                orig_zdim := @(1).val.domain,
                xdim := Maximum([1]::List(Collect(@(1).val.child(1), @(2, [SIMTSUM, SIMTISum], e->ObjId(e.simt_dim) = ASIMTBlockDimX)), i->i.simt_dim.params[1])),
                ydim := Maximum([1]::List(Collect(@(1).val.child(1), @(2, [SIMTSUM, SIMTISum], e->ObjId(e.simt_dim) = ASIMTBlockDimY)), i->i.simt_dim.params[1])),
                zdim_inner := Maximum([1]::Filtered(DivisorsInt(orig_zdim), i -> i * xdim * ydim <= (opts.max_threads))),
                zdim_outer := orig_zdim / zdim_inner,
                io := Ind(zdim_outer),
                sdo := ASIMTGridDimZ(zdim_outer),
                ii := Ind(zdim_inner),
                sdi := ASIMTBlockDimZ(zdim_inner),
                krn := SubstVars(Copy(@(1).val.child(1)), rec((@(1).val.var.id) := (io * zdim_inner + ii))),
                SIMTISum(sdo, io, zdim_outer,
                    SIMTISum(sdi, ii, zdim_inner, krn))
            )
        );
    fi;

    if IsBound(s.ruletree) then s.ruletree := srt; fi;

    return s;
end;

#FixUpCUDASigmaSPL_3Stage := (ss, opts) -> SubstTopDown(ss, @(1, Grp), e->e.child(1));
FixUpCUDASigmaSPL_3Stage := function(ss, opts)
    local kernels, _s, newv;

    # drop grp
    ss := SubstTopDown(ss, @(1, Grp), e->e.child(1));

    # parallelize and flatten loop
    ss:= let(simtidx := ASIMTBlockDimX,
        SubstBottomUp(ss, [@(1, SIMTISum), @(2, Compose, e->ForAll(e.children(), c->ObjId(c) = ISum))],
            e->let( #Error(),
                    simtloop := @(1).val,
                    sx1c := @(2).val.children(),
                    doms := List(sx1c, c->c.domain),
                    i := @(1).val.var,
                    mdom := Maximum(doms),
                    ranges := List(List(sx1c, c->[0, c.domain-1])),
                    nch := [sx1c[1] * Gath(fTensor(fBase(i), fId(Cols(sx1c[1]))))] ::
                        List(sx1c{[2..Length(sx1c)-1]}, c -> Scat(fTensor(fBase(i), fId(Rows(c)))) * c * Gath(fTensor(fBase(i), fId(Cols(c))))) ::
                        [ Scat(fTensor(fBase(i), fId(Rows(Last(sx1c))))) * Last(sx1c)],
                    newc := List([1..Length(nch)],
                        j-> SIMTISum(simtloop.simt_dim, simtloop.var, simtloop.domain,
                                SIMTISum(simtidx(mdom, ranges[j]), sx1c[j].var, sx1c[j].domain, ApplyStrategy(nch[j], opts.formulaStrategies.sigmaSpl, BUA, opts).child(1)))),
                ApplyFunc(Compose, newc)
            ))
    );
    ss := ApplyStrategy(ss, opts.formulaStrategies.sigmaSpl, BUA, opts);

#    ss:= let(simtidx := ASIMTBlockDimX,
#        SubstBottomUp(ss, [@(1, SIMTISum), @(2, Compose, e->ForAll(e.children(), c->ObjId(c) = ISum))],
#            e->let(sx1c := @(2).val.children(),
#                    doms := List(sx1c, c->c.domain),
#                    i := @(1).val.var,
#                    mdom := Maximum(doms),
#                    ranges := List(List(sx1c, c->[0, c.domain-1])),
#                    newc := List([1..Length(sx1c)],
#                        j-> SIMTISum(@(1).val.simt_dim, @(1).val.var, @(1).val.domain, SIMTISum(simtidx(mdom, ranges[j]), sx1c[j].var, sx1c[j].domain, sx1c[j].child(1)))),
#                ApplyFunc(Compose, newc)
#            ))
#    );

    # loop distribution Y(X*X)
    ss := SubstBottomUp(ss, [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimY), Compose],
        e -> let(ch := @(1).val.child(1).children(), i := @(1).val.var,
            nch := [ch[1] * Gath(fTensor(fBase(i), fId(Cols(ch[1]))))] ::
                List(ch{[2..Length(ch)-1]}, c -> Scat(fTensor(fBase(i), fId(Rows(c)))) * c * Gath(fTensor(fBase(i), fId(Cols(c))))) ::
                [ Scat(fTensor(fBase(i), fId(Rows(Last(ch))))) * Last(ch)],
            Compose(List(nch, c -> SIMTISum(@(1).val.simt_dim, @(1).val.var, @(1).val.var.range, c)))
            ));
    ss := ApplyStrategy(ss, opts.formulaStrategies.sigmaSpl, BUA, opts);

    # privatize temporaries -- that should be a no-op now
    ss := SubstTopDown(ss, [@(1, SIMTISum), @(2, Compose, e->ForAll(e.children(), c->ObjId(c) = ISum))],
        e-> let(ch := @(1).val.child(1).children(),
            i := @(1).val.var,
            nch := [ch[1] * Gath(fTensor(fBase(i), fId(Cols(ch[1]))))] ::
                List(ch{[2..Length(ch)-1]}, c -> Scat(fTensor(fBase(i), fId(Rows(c)))) * c * Gath(fTensor(fBase(i), fId(Cols(c))))) ::
                [ Scat(fTensor(fBase(i), fId(Rows(Last(ch))))) * Last(ch)],
            SIMTISum(@(1).val.simt_dim, @(1).val.var, @(1).val.domain, Compose(List(nch, c->ApplyStrategy(c, opts.formulaStrategies.sigmaSpl, BUA, opts))))
        )
    );

    # flatten X/X -> X loops
    ss := SubstTopDown(ss,
        [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), [@(2, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), BB]],
        e->let(s1 := @(1).val,
            i1 := s1.var,
            i2 := s1.child(1).var,
            rng := i1.range * i2.range,
            ii := Ind(rng),
            sr := rec(
                (i1.id) := idiv(ii, i2.range),
                (i2.id) := imod(ii, i2.range)
            ),
            sdim := ASIMTBlockDimX(rng),
            SIMTISum(sdim, ii, ii.range, SubstVars(s1.child(1).child(1), sr))
        )
    );

    # flatten Y/X -> X loops
    ss := SubstTopDown(ss,
        [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimY), [@(2, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), BB]],
        e->let(s1 := @(1).val,
            i1 := s1.var,
            i2 := s1.child(1).var,
            rng := i1.range * i2.range,
            ii := Ind(rng),
            sr := rec(
                (i1.id) := idiv(ii, i2.range),
                (i2.id) := imod(ii, i2.range)
            ),
            sdim := ASIMTBlockDimX(rng),
            SIMTISum(sdim, ii, ii.range, SubstVars(s1.child(1).child(1), sr))
        )
    );

    # fix loop iterations
    if ObjId(ss) = Compose then
        kernels := ss.children();
    else
        kernels := [ss];
    fi;

    for i in [1..Length(kernels)] do
        _s := kernels[i];
        if Length(Collect(_s,  @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX))) > 0 then
            newv := Maximum(List(List(Collect(_s, @(1, [SIMTSUM, SIMTISum], e->ObjId(e.simt_dim) = ASIMTBlockDimX)), e-> e.simt_dim), i->i.params[1]));
            _s := SubstTopDown(_s, @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX),
                e->SIMTISum(ApplyFunc(ASIMTBlockDimX, [newv]::Drop(@(1).val.simt_dim.params, 1)), @(1).val.var, @(1).val.domain, @(1).val.child(1))
            );

            _s := SubstTopDown(_s, @(1, SIMTSUM, e->ObjId(e.simt_dim) = ASIMTBlockDimX),
                e->SIMTSUM(ApplyFunc(ASIMTBlockDimX, [newv]::Drop(@(1).val.simt_dim.params, 1)), @(1).val.children())
            );
        fi;
        kernels[i] := _s;
    od;

    if ObjId(ss) = Compose then
        ss := Compose(kernels);
    else
        ss := kernels[1];
    fi;

#    # prepare for PingPong: privatize first ping so it can be mapped to Y without conflict
#    ss := SubstBottomUp(ss, [@(1, SIMTISum), [@(2, Compose), @(3, SIMTISum), @(4, SIMTISum), @(5, SIMTISum)]],
#        e->let(
#            g := Gath(fTensor(fBase(@(1).val.var), fId(Cols(@(4).val)))),
#            s := Scat(fTensor(fBase(@(1).val.var), fId(Rows(@(5).val)))),
#            krn := @(3).val * Grp(@(4).val * g) * Grp(s * @(5).val),
#            CopyFields(@(1).val, rec(_children := [krn])))
#    );
#    ss := ApplyStrategy(ss, opts.formulaStrategies.sigmaSpl, BUA, opts);
#    ss := SubstTopDown(ss, @(1, Grp), e->e.child(1));

    return ss;
end;

PingPong_3Stages := function(c, opts)
    local cands, candvars, outvars, invars, in_loops, substvars, link_loops, linkvars, link_func, subst_list, substrec, shift, shiftvar, v;

    # need to guard that better...
    cands := Collect(c, @(1, specifiers_func, e -> (Collect(e, @(2, decl, f -> Length(f.vars) <= 2 and ForAll(f.vars, k -> ObjId(k.t) = TArray))) <> [])) );

    candvars := chain(cands).free()::[X, Y];

    if Length(cands) > 0 then
#    Error();
        outvars := Filtered([Y]::Flat(List(Collect(c, @@(1, decl,
            (e, cx) -> (not IsBound(cx.specifiers_func) or cx.specifiers_func = []) and
                       (not IsBound(cx.func) or cx.func = []))), x->x.vars)), p -> p in candvars);
#        outvars := List(Collect(c, @(1, cu_call)), e->e.args[2]);

        invars := Filtered([X]::Flat(List(Collect(c, @@(1, decl,
            (e, cx) -> (not IsBound(cx.specifiers_func) or cx.specifiers_func = []) and
                       (not IsBound(cx.func) or cx.func = []))), x->x.vars)), p -> p in candvars);
#        invars := List(Collect(c, @(1, cu_call)), e->e.args[1]);

        in_loops := Collect(cands, @(1, simt_loop, e -> Collect(e.cmd, @(2,simt_loop)) = [] and
            Collect(e.cmd, [assign, @(3), @(4, nth, f->f.loc in invars), ...]) <> []));

        substvars := Set(List(Collect(in_loops, [assign, @(1, nth, e -> ObjId(e.loc.t) = TArray), ...]), f -> f.loc.loc));

        link_loops := Collect(cands, @(1, simt_loop, e -> Collect(e.cmd, @(2,simt_loop)) = [] and
            Collect(e.cmd, [assign, @(3), @(4, nth, f->f.loc in substvars), ...]) <> []));

        linkvars := Set(List(Collect(link_loops, [assign, @(1, nth, e -> ObjId(e.loc.t) = TArray), ...]), f -> f.loc.loc));

        link_func := v -> Set(Collect(Filtered(Collect(c, @(1, specifiers_func)),
            e -> Collect(e.cmd, [assign, @(3), @(4, nth, f->f.loc = v), ...]) <> []), @(1, var, e-> e in outvars)))[1];

        for v in substvars do
            shiftvar := Collect(Collect(c, @(1, decl, e-> v in e.vars)), @(2, simt_loop, f->ObjId(f.simt_idx) = simtBlockIdxX))[1].var;
            shift := v.t.size;
            SubstBottomUp(c, @(1, nth, e->e.loc = v),
                e-> nth(@(1).val.loc, shiftvar * shift + @(1).val.idx));
        od;

        subst_list := List(substvars, vv -> [vv, link_func(vv)]);

        substrec := FoldR(subst_list, (a,b) -> CopyFields(a, rec((b[1].id) := b[2])), rec());

        c := SubstBottomUp(c, @(1, decl), e-> decl(Filtered(@(1).val.vars, v -> not v in substvars), @(1).val.cmd));
        c := SubstVars(c, substrec);
    fi;

    return c;
end;

#FixUpCUDASigmaSPL_3Stage := (ss, opts) -> _FixUpCUDASigmaSPL_3Stage(_FixUpCUDASigmaSPL_3Stage(ss, opts), opts);

FixUpTeslaV_Code := function (c, opts)
    local kernels, kernel_inits, globals, var_decls, var_dels, cx, v, heap_init, dptr;

    if IsBound(opts.fixUpTeslaV_Code) and opts.fixUpTeslaV_Code then
        kernels := List(Collect(c, specifiers_func), k->k.id);

        dptr := var.fresh_t("hp", TPtr(TReal));

#        kernel_inits := List(kernels, k-> call(rec(id := "INIT_KERNEL"), k));
#        #define INIT_KERNEL(k) cudaFuncSetCacheConfig(k, cudaFuncCachePreferEqual)
        kernel_inits := List(kernels, k-> call(rec(id := "cudaFuncSetCacheConfig"), k, "cudaFuncCachePreferL1"));

#       cudaDeviceSetLimit(cudaLimitMallocHeapSize, (DEV_MIN_HEAP_SIZE));
        heap_init := call(rec(id := "cudaDeviceSetLimit"), "cudaLimitMallocHeapSize", opts.max_heap);

        globals := Flat(List(Collect(c, @@(1, decl, (e, cx) -> (not IsBound(cx.specifiers_func) or cx.specifiers_func = []) and
                               (not IsBound(cx.func) or cx.func = []))), x->x.vars));

#        var_decls := List(globals, v -> call(rec(id := "DECLARE_DEVICE_ARRAY"), v.id, sizeof(v.t.t) * v.t.size));
#        double *hp;\
#        cudaMalloc((void**)&hp,(sz));\
#        cudaMemcpyToSymbol(p,&hp,sizeof(double*));\

        var_decls := chain(Flat(List(globals, v -> [
                call(rec(id := "cudaMalloc"), tcast(TPtr(TPtr(TVoid)), addrof(dptr)), sizeof(v.t.t) * v.t.size),
                call(rec(id := "cudaMemcpyToSymbol"), v, addrof(dptr), sizeof(dptr.t))
            ])));

#        var_dels := List(globals, v -> call(rec(id := "DELETE_DEVICE_ARRAY"), v.id));
#        double *hp;\
#        cudaMemcpyFromSymbol(&hp,p,sizeof(double*));\
#        cudaFree((void**)&hp);\

        var_dels := decl(dptr, chain(Flat(List(globals, v -> [
                call(rec(id := "cudaMemcpyFromSymbol"), addrof(dptr), v, sizeof(dptr.t)),
                call(rec(id := "cudaFree"), dptr)
            ]))));

        cx := chain([heap_init] :: kernel_inits :: [var_decls]);
        for v in globals do
            v.t := TPtr(v.t.t, ["__device__"]);
        od;

        c := SubstBottomUp(c, @(1, func, f -> f.id = "init"),
            e -> CopyFields(@(1).val, rec(cmd := decl(dptr, chain(cx, @(1).val.cmd))))
        );
        c := SubstBottomUp(c, @(1, func, f -> f.id = "destroy"),
            e -> CopyFields(@(1).val, rec(cmd := chain(var_dels, @(1).val.cmd)))
        );
        c := SubstBottomUp(c, @(1, chain, e -> ForAny(e.cmds, e -> ObjId(e) = func and e.id = "init")),
            e -> chain(Filtered(@(1).val.cmds, e-> ObjId(e) <> func or e.id <> "init") :: Filtered(@(1).val.cmds, e -> ObjId(e) = func and e.id = "init"))
        );

    fi;

    return c;
end;

FixUpCUDASigmaSPL_3Stage_Real := function(ss, opts)
    local kernels, _s, newv, srt;

    if IsBound(ss.ruletree) then srt := ss.ruletree; fi;

    # drop grp
    ss := SubstTopDown(ss, @(1, Grp), e->e.child(1));

#    ss := SIMT_NextLoop(ss, ASIMTBlockDimY);
    ss := SIMT_NextLoop(ss, ASIMTBlockDimX);

    ss:= SubstTopDown(ss, @(1, SIMTSUM),
        e->let(
            its := @(1).val.simt_dim.params[1],
            ii := Ind(its),
            ch := @(1).val.children(),
            nch := Length(ch),
            SIMTISum(@(1).val.simt_dim, ii, nch, SUM(List([0..nch-1],
               i->COND(eq(ii, V(i)), _fBaseVar(ch[i+1], ii, i), ApplyFunc(OO, ch[i+1].dims()))))))
    );

    # loop distribution X(X*X)
    ss := SubstBottomUp(ss, [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), Compose],
        e -> let(ch := @(1).val.child(1).children(), i := @(1).val.var,
            nch := [ch[1] * Gath(fTensor(fBase(i), fId(Cols(ch[1]))))] ::
                List(ch{[2..Length(ch)-1]}, c -> Scat(fTensor(fBase(i), fId(Rows(c)))) * c * Gath(fTensor(fBase(i), fId(Cols(c))))) ::
                [ Scat(fTensor(fBase(i), fId(Rows(Last(ch))))) * Last(ch)],
            Compose(List(nch, c -> SIMTISum(@(1).val.simt_dim, @(1).val.var, @(1).val.var.range, c)))
            ));
    ss := ApplyStrategy(ss, opts.formulaStrategies.sigmaSpl, BUA, opts);

    # flatten X/X -> X loops
    ss := SubstTopDown(ss,
        [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), [@(2, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), @(3, [BB, SUM]) ]],
        e->let(s1 := @(1).val,
            i1 := s1.var,
            i2 := s1.child(1).var,
            rng := i1.range * i2.range,
            ii := Ind(rng),
            sr := rec(
                (i1.id) := idiv(ii, i2.range),
                (i2.id) := imod(ii, i2.range)
            ),
            sdim := ASIMTBlockDimX(rng),
            SIMTISum(sdim, ii, ii.range, SubstVars(s1.child(1).child(1), sr))
        )
    );

    # loop distribution Y(X*X)
    ss := SubstBottomUp(ss, [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimY), Compose],
        e -> let(ch := @(1).val.child(1).children(), i := @(1).val.var,
            nch := [ch[1] * Gath(fTensor(fBase(i), fId(Cols(ch[1]))))] ::
                List(ch{[2..Length(ch)-1]}, c -> Scat(fTensor(fBase(i), fId(Rows(c)))) * c * Gath(fTensor(fBase(i), fId(Cols(c))))) ::
                [ Scat(fTensor(fBase(i), fId(Rows(Last(ch))))) * Last(ch)],
            Compose(List(nch, c -> SIMTISum(@(1).val.simt_dim, @(1).val.var, @(1).val.var.range, c)))
            ));
    ss := ApplyStrategy(ss, opts.formulaStrategies.sigmaSpl, BUA, opts);

    # flatten Y/X -> X loops
    ss := SubstTopDown(ss,
        [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimY), [@(2, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), @(3, [BB, SUM, Data, ISum])]],
        e->let(s1 := @(1).val,
            i1 := s1.var,
            i2 := s1.child(1).var,
            rng := i1.range * i2.range,
            ii := Ind(rng),
            sr := rec(
                (i1.id) := idiv(ii, i2.range),
                (i2.id) := imod(ii, i2.range)
            ),
            sdim := ASIMTBlockDimX(rng),
            SIMTISum(sdim, ii, ii.range, SubstVars(s1.child(1).child(1), sr))
        )
    );

    # flatten GridDim X/X -> X loops
    ss := SubstTopDown(ss,
        [@(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), [@(2, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX), @(3, [BB, SUM]) ]],
        e->let(s1 := @(1).val,
            i1 := s1.var,
            i2 := s1.child(1).var,
            rng := i1.range * i2.range,
            ii := Ind(rng),
            sr := rec(
                (i1.id) := idiv(ii, i2.range),
                (i2.id) := imod(ii, i2.range)
            ),
            sdim := ASIMTBlockDimX(rng),
            SIMTISum(sdim, ii, ii.range, SubstVars(s1.child(1).child(1), sr))
        )
    );

    # fix loop iterations
    if ObjId(ss) = Compose then
        kernels := ss.children();
    else
        kernels := [ss];
    fi;

    for i in [1..Length(kernels)] do
        _s := kernels[i];
        if Length(Collect(_s,  @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX))) > 0 then
            newv := Maximum(List(List(Collect(_s, @(1, [SIMTSUM, SIMTISum], e->ObjId(e.simt_dim) = ASIMTBlockDimX)), e-> e.simt_dim), i->i.params[1]));
            _s := SubstTopDown(_s, @(1, SIMTISum, e->ObjId(e.simt_dim) = ASIMTBlockDimX),
                e->SIMTISum(ApplyFunc(ASIMTBlockDimX, [newv]::Drop(@(1).val.simt_dim.params, 1)), @(1).val.var, @(1).val.domain, @(1).val.child(1))
            );

            _s := SubstTopDown(_s, @(1, SIMTSUM, e->ObjId(e.simt_dim) = ASIMTBlockDimX),
                e->SIMTSUM(ApplyFunc(ASIMTBlockDimX, [newv]::Drop(@(1).val.simt_dim.params, 1)), @(1).val.children())
            );
        fi;
        kernels[i] := _s;
    od;

    if ObjId(ss) = Compose then
        ss := Compose(kernels);
    else
        ss := kernels[1];
    fi;

    return ss;
end;


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

Class(APar, ASingletonTag);
Class(AVec, ASingletonTag);

IsParPar := P -> P[3] = APar and P[4] = APar;
IsVecVec := P -> P[3] = AVec and P[4] = AVec;
IsVecPar := P -> P[3] = AVec and P[4] = APar;
IsParVec := P -> P[3] = APar and P[4] = AVec;
IsParXXX := P -> P[3] = APar;
IsVecXXX := P -> P[3] = AVec;
IsXXXPar := P -> P[4] = APar;
IsXXXVec := P -> P[4] = AVec;

BaseOperation.doNotMeasure := true;
PermClass.doNotMeasure := true;

###################################################################
#F tSPL - base class for all tSPL constructs
#F *OBSOLETE*. Use Tagged_tSPL
#F

#D Class(tSPL, NonTerminal, rec(
#D    isTSPL := true
#D ));

###################################################################
#F Tagged_tSPL - base class for all tSPL constructs with tag support
#F
#F *!!* Should be used for all new constructs instead of tSPL
#F
Class(Tagged_tSPL, TaggedNonTerminal, rec(
    isTSPL := true
));

###################################################################
#F Tagged_tSPL_Container - base class for a generic tSPL container
#F
Class(Tagged_tSPL_Container, Tagged_tSPL, rec(
    abbrevs :=  [ nt -> Checked(IsSPL(nt), [nt]) ],
    dims := self >> self.params[1].dims(),
    advdims := self >> self.params[1].advdims(),
    terminate := self >> self.params[1].terminate(),
    transpose := self >> ObjId(self)(self.params[1].transpose())
                             .withTags(self.getTags()),
    isReal := self >> self.params[1].isReal(),
    isInplace := self >> self.params[1].isInplace(),
    normalizedArithCost := self >> self.params[1].normalizedArithCost(),
    doNotMeasure := true,
));

IsTSPL := o -> IsRec(o) and IsBound(o.isTSPL) and o.isTSPL;

Declare(TGrp, TTensor, TTensorI, TTensorInd, TDirectSum, TInplace);

#F TGrp(<s>)
#F  tSPL grouping container (i.e. parenthesis)
#F
Class(TGrp, Tagged_tSPL_Container, rec(
    abbrevs :=  [ s -> Checked(IsSPL(s), [s]) ],
    HashId := self >> let(
	p := self.params[1],
	h := When(IsBound(p.HashId), p.HashId(), p),
        [ TGrp, h ] :: When(IsBound(self.tags), self.tags, [])
    )
));

#F TTensor(<A>, <B>)
#F    Nonterminal for Tensor(A, B)
#F
Class(TTensor, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (A, B) -> Checked(IsNonTerminal(A), IsNonTerminal(B), [A,B]) ],

    dims := self >> let(
	a := self.params[1].dims(), b := self.params[2].dims(),
	[ a[1]*b[1], a[2]*b[2] ] ),

    terminate := self >> Tensor(self.params[1], self.params[2]),

    transpose := self >>
        TTensor(self.params[1].transpose(), self.params[2].transpose())
           .withTags(self.getTags()),

    isReal := self >> self.params[1].isReal() and self.params[2].isReal(),

    normalizedArithCost := self >>
        self.params[1].normalizedArithCost() * Rows(self.params[2]) +
	self.params[2].normalizedArithCost() * Cols(self.params[1]),

    HashId := self >> let(
	h := List(self.params, i -> When(IsBound(i.HashId), i.HashId(), i)),
        When(IsBound(self.tags), Concatenation(h, self.tags), h))
));

#F TDirectSum(<A>, <B>)
#F    Nonterminal for DirectSum(A, B)
#F
Class(TDirectSum, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (A, B) -> Checked(IsSPL(A), IsSPL(B), [ A, B ]) ],
    dims := self >> let(
	a:=self.params[1].dims(), b:=self.params[2].dims(),
	[ a[1]+b[1], a[2]+b[2] ]),

    terminate := self >> DirectSum(self.params[1], self.params[2]),

    transpose := self >>
        TDirectSum(self.params[1].transpose(), self.params[2].transpose())
	    .withTags(self.getTags()),

    isReal := self >> self.params[1].isReal() and self.params[2].isReal(),

    doNotMeasure := true,

    HashId := self >> let(
	h := List(self.params, i->When(IsBound(i.HashId), i.HashId(), i)),
        When(IsBound(self.tags), Concatenation(h, self.tags), h))
));

#F TTensorI(<nt>, <s>, <l>, <r>)
#F
#F tSPL nonterminal for A x I, I x A, (A x I)L, (I x A)L
#F
#F Example: TTensorI(F(2), 32, APar, APar)
#F
Class(TTensorI, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (nt, s, l, r) -> Checked(
        IsSPL(nt), IsPosInt(s), l in [APar, AVec], r in [APar, AVec],
	[nt, s, l, r])
    ],

    dims := self >> self.params[1].dims()*self.params[2],

    SPLtSPL := (self, nt, P) >> let(
	A := nt[1], n := P[2], l := P[3], r := P[4],
        Cond(IsParPar(P), Tensor(I(n), A),
             IsVecVec(P), Tensor(A, I(n)),
             IsParVec(P), Tensor(I(n), A) * L(A.dims()[2]*n, n),
             IsVecPar(P), let(m:=A.dims()[2], Tensor(A, I(n)) * L(m*n, m))
    )),

    terminate := self >> let(
	P := self.params, A:=P[1].terminate(), n:= P[2], l:=P[3],  r:=P[4],
        Cond(IsParPar(P), Tensor(I(n), A),
             IsVecVec(P), Tensor(A, I(n)),
             IsParVec(P), Tensor(I(n), A) * L(A.dims()[2]*n, n),
             IsVecPar(P), let(m:=A.dims()[2], Tensor(A, I(n)) * L(m*n, m)))),

    transpose := self >> let(p := self.params,
        TTensorI(p[1].transpose(), p[2], p[4], p[3]).withTags(self.getTags())),

    isReal := self >> self.params[1].isReal(),

    normalizedArithCost := self >>
        self.params[1].normalizedArithCost() * self.params[2],

    doNotMeasure := true,

    HashId := self >> let(
	p := self.params,
	h := When(IsBound(p[1].HashId), p[1].HashId(), p[1]),
        [h, p[2], p[3], p[4]] :: When(IsBound(self.tags), self.tags, [])
    ),
));

Class(TTensorInd, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (nt, idx, l, r) -> Checked(
        IsSPL(nt), IsVar(idx), l in [APar, AVec], r in [APar, AVec],
	[nt, idx, l, r])
    ],

    dims := self >> self.params[1].dims() * self.params[2].range,

    SPLtSPL := (self, nt, P) >> let(
	A := nt[1], idx := P[2], m := A.dims()[2], n := idx.range,
        dsA := IDirSum(idx, A),
        Cond(IsParPar(P), dsA,
             IsVecVec(P), L(m*n, m) * dsA * L(m*n, n),
             IsParVec(P), dsA * L(m*n, n),
             IsVecPar(P), L(m*n, m) * dsA)
    ),

    terminate := self >> let(
	P := self.params, A := P[1], idx:= P[2],
        lstA := List([0..idx.range-1],
	    i -> SubstVars(Copy(A), rec((idx.id) := V(i))).terminate()),
        d := A.dims(),
	n := idx.range,
        dsA := DirectSum(lstA),
        Cond(IsParPar(P), dsA,
             IsVecVec(P), L(d[1]*n, d[1]) * dsA * L(d[2]*n, n),
             IsParVec(P), dsA * L(m*n, n),
             IsVecPar(P), L(m*n, m) * dsA)
    ),

    transpose := self >> TTensorInd(
       self.params[1].transpose(), self.params[2], self.params[4], self.params[3])
          .withTags(self.getTags()),

    isReal := self >> self.params[1].isReal(),

    normalizedArithCost := self >>
        self.params[1].normalizedArithCost() * self.params[2].range,

#    doNotMeasure := true,

    HashId := self >> let(
	p := self.params,
	h := When(IsBound(p[1].HashId), p[1].HashId(), p[1]),
        [h, p[2].range, p[3], p[4]] :: When(IsBound(self.tags), self.tags, [])
    )
));

Declare(TL);

#F  tSPL I x L x I
#F  TL(m, n, k, j)
#F  I(k), L(m, n), I(j)
Class(TL, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (size, stride) -> Checked(ForAll([size, stride], IsPosIntSym), [size, stride, 1, 1]),
                  (size, stride, left, right) -> Checked(ForAll([size, stride, left, right], IsPosIntSym), [size, stride, left, right]) ],
    __call__ := arg >> let(self := arg[1], args := Drop(arg, 1),
        Cond(args=[1,1,1,1], I(1),
             ApplyFunc(Inherited,args))),

    dims := self >> Replicate(2, self.params[1]*self.params[3]*self.params[4]),
    terminate := self >> Tensor(I(self.params[3]), L(self.params[1], self.params[2]), I(self.params[4])),
    transpose := self >> TL(self.params[1], self.params[1]/self.params[2], self.params[3], self.params[4]).withTags(self.getTags()),
    isReal := self >> true,
    noCodelet := true,
    # need that for DPBench!
    doNotMeasure := true,
    transposeSymmetric := False,
    isSymmetric := self >> self.params[2]^2 = self.params[1],
    hashAs := self >> ObjId(self)(self.params[1], self.params[2], self.params[3], self.params[4]).withTags(self.getTags()),
));

#F  tSPL Bit Reversal
Class(TBR, Tagged_tSPL_Container, rec(
    abbrevs :=  [ n -> Checked(IsInt(n), [n])],
    dims := self >> [self.params[1], self.params[1]],
    terminate := self >> BR(self.params[1]),
    transpose := self >> self,
    isReal := True,
    doNotMeasure := true,
));

Declare(TRDiag);

#F TRDiag(<nrows>, <ncols>, <diagfunc>)
#F
#F TRDiag is a rectangular tall matrix, which is a diagonal
#F zero-padded on the bottom.
#F
#F To obtain wide matrix (diagonal zero-padded on the right),
#F use TRDiag(...).transpose()
#F
#F Example:
#F  spiral> PrintMat(MatSPL(  TRDiag(6, 3, FList(TInt, [1,2,3])) ));
#F  [ [ 1,  ,   ],
#F    [  , 2,   ],
#F    [  ,  , 3 ],
#F    [  ,  ,   ],
#F    [  ,  ,   ],
#F    [  ,  ,   ] ]
#F
Class(TRDiag, Tagged_tSPL, rec(
    abbrevs :=  [ (m,n,diag) -> Checked(IsPosIntSym(m), IsPosIntSym(n),
	    AnySyms(m, n) or (m>=n),
	    IsFunction(diag) or IsFuncExp(diag),
	    [m, n, diag])],

    dims :=  self >> let( d := [ self.params[1], self.params[2] ], Cond(self.transposed, Reversed(d), d)),

    # tells autolib's ConstrainPSums that params[2] must be = params[3].domain()
    # NOTE: get rid of params[2]
    paramConstraints := self >> [[self.params[2], self.params[3].domain()]],

    terminate := self >> let(
        res := RI(self.params[1], self.params[2]) * Diag(self.params[3]),
	When(self.transposed, res.transpose(), res)),

    conjTranspose := self >> CopyFields(self,
	rec(params := [self.params[1], self.params[2], FConj(self.params[3])]))
            .transpose(),

    isReal := self >> not IsComplexT(self.params[3].range()),

    doNotMeasure := true,

    hashAs := self >> let(
	t := ObjId(self)(self.params[1], self.params[2],
	                 fUnk(self.params[3].range(), self.params[3].domain())),
	tt := t.withTags(self.getTags()),
	When(self.transposed, tt.transpose(), tt))
));

#F  tSPL Digit Reversal
Class(TDR, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (n, r) -> Checked(IsInt(n) and  IsInt(r) and n=r^LogInt(n,r), [n, r])],
    dims := self >> [self.params[1], self.params[1]],
    terminate := self >> DR(self.params[1], self.params[2]),
    transpose := self >> self,
    isReal := True,
    doNotMeasure := true,
));

Declare(TReflL);
#F  tSPL Refl(L(mn,m))
Class(TReflL, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (mn, m) -> Checked(IsInt(mn) and  IsInt(m), [mn, m]) ],
    dims := self >> self.terminate().dims(),
    terminate := self >> let(mn := self.params[1], m:=self.params[2], N := mn/2,
                             Refl(N, 2*N-1, N, L(mn, m))),
    transpose := self >> TReflL(self.params[1], self.params[1]/self.params[2]).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
));

Declare(TRC, TCompose, TDiag, TICompose);

#F   tSPL RC(.)
Class(TRC, Tagged_tSPL_Container, rec(
    _short_print := true,
    abbrevs :=  [ (A) -> Checked(IsNonTerminal(A) or IsSPL(A), [A]) ],
    dims := self >> 2*self.params[1].dims(),
    terminate := self >> Mat(MatSPL(RC(self.params[1]))),

    transpose := self >> ObjId(self)(
	self.params[1].conjTranspose()).withTags(self.getTags()),

    conjTranspose := self >> self.transpose(),

    isReal := self >> true,

    # Do not use doNotMeasure, this will prevent TRC_By_Def from ever being found!
    doNotMeasure := false,
    normalizedArithCost := self >> self.params[1].normalizedArithCost(),

    HashId := self >> let(
	h := [ When(IsBound(self.params[1].HashId), self.params[1].HashId(),
		    self.params[1]) ],
        When(IsBound(self.tags), Concatenation(h, self.tags), h))
));

Class(TMat, Tagged_tSPL, rec(
    abbrevs :=  [ m -> Checked(IsMat(m), [m]) ],
    dims := self >> DimensionsMat(self.params[1]),
    terminate := self >> Mat(self.params[1]),
    transpose := self >> ObjId(self)(TransposedMat(self.params[1])).withTags(self.getTags()),
    isReal := self >> IsRealMat(self.params[1]),
    isInplace := self >> false,
    normalizedArithCost := self >> 2 * Rows(self.params[1]) * Cols(self.params[1]),
    doNotMeasure := false,
));

#F   tSPL Diag(d)
Class(TDiag, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (D) -> [D] ],
    dims := self >> let(n:=self.params[1].domain(), [n,n]),
    terminate := self >> Diag(self.params[1]),
    transpose := self >> Copy(self),
    conjTranspose := self >> ObjId(self)(FConj(self.params[1])).withTags(self.getTags()),

    isReal := self >> Diag(self.params[1]).isReal(),

    doNotMeasure := true,
    noCodelet := true,
    hashAs := self >> ObjId(self)(FUnk(self.params[1].domain())).withTags(self.getTags()),
    #NOTE: depends on wether the whole transform is complex...
    normalizedArithCost := self >> When(self.params[1].isReal(), 2*Rows(self), 6*Rows(self))
));

#F   tSPL RCDiag(d)
Class(TRCDiag, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (D) -> [D] ],
    dims := self >> let(n:=self.params[1].domain(), [n,n]),
    terminate := self >> RCDiag(self.params[1]),
    # NOTE: transpose on real == conjugate; transpose on complex == id. What to use?
#    transpose := self >> let(d := self.params[1].var.value, t := d.t.t, size := d.t.size,
#        newd := List(d.v, i-> t.value(Complex(ReComplex(i.v), -ImComplex(i.v)))),
#        FData(newd)),
    transpose := self >> CopyFields(self, rec(transposed := not self.transposed)),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    hashAs := self >> ObjId(self)(FUnk(self.params[1].domain())).withTags(self.getTags()),
    normalizedArithCost := self >> 3*Rows(self)
));

#F   tSPL Id(f)
Class(TId, TDiag);

Declare(TCond);
#F   tSPL Cond
Class(TCond, Tagged_tSPL_Container, rec(
    abbrevs   := [ (f, a, b) -> Checked(IsBound(f.ev), [f,a,b]) ],
    dims      := self >> self.params[2].dims(),
    terminate := self >> COND(self.params[1], self.params[2].terminate(), self.params[3].terminate()),
    transpose := self >> TCond(self.params[1], self.params[2].transpose(), self.params[3].transpose()), # maybe
));

Declare(TRaderMid);
#F   tSPL RaderMid(N, k, root, transp)
Class(TRaderMid, Tagged_tSPL_Container, rec(
    # -- Rader infrastructure --
    raderDiag := (N, k, root) -> SubList(ComplexFFT(List([0..N-2], x->ComplexW(N, k*root^x mod N))), 2) / (N-1),
    raderMid := (self, N, k, root) >>
                 DirectSum(Mat([[1, 1], [1, -1/(N-1)]]),
                   Diag(FData(self.raderDiag(N, k, root)))),
    # -- tSPL infrastructure --
    abbrevs :=  [ (N, k, root) -> [N, k, root, false],
                  (N, k, root, transp) -> [N, k, root, transp] ],
    dims := self >> let(N:=self.params[1], [N,N]),
    terminate := self >> let(spl := self.raderMid(self.params[1], self.params[2], self.params[3]),
        When(self.params[4], spl.transpose(), spl)),
    transpose := self >> TRaderMid(self.params[1], self.params[2], self.params[3], not self.params[4]).withTags(self.getTags()),
    isReal := False,
    doNotMeasure := true,
    noCodelet := true,
));

#F   tSPL Compose([A,B,...])
Class(TCompose, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (l) -> Checked(IsList(l), [l]) ],
    dims := self >> [self.params[1][1].dims()[1], self.params[1][Length(self.params[1])].dims()[2]],
    terminate := self >> Compose(List(self.params[1], i->i.terminate())),
    transpose := self >> TCompose(Reversed(List(self.params[1], i->i.transpose()))).withTags(self.getTags()),
    isReal := self >> ForAll(self.params[1], i->i.isReal()),
    doNotMeasure := true,
    normalizedArithCost := self >> Sum(List(self.params[1], i->i.normalizedArithCost())),

    HashId := self >> let(h := List(self.params[1], i->When(IsBound(i.HashId), i.HashId(), i)),
        When(IsBound(self.tags), Concatenation(h, self.tags), h))
));

#F   tSPL ICompose(<var>, <domain>, <nonterm>, <attrib>)
Class(TICompose, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (var,domain,l) -> [var, domain, l] ],
    dims := self >> self.params[3].dims(),
    terminate := self >> ICompose(self.params[1], self.params[2], self.params[3]).unroll(),

    unroll := self >> let(
        var := self.params[1],
        domain := self.params[2],
        expr := self.params[3],
        tag := self.getTags(),
        TCompose(
            List([ 0 .. domain - 1 ],
                index_value -> SubstBottomUp(
                    Copy(expr), var,
                    e -> V(index_value))))),

    isReal := self >> ICompose(self.params[1], self.params[2], self.params[3]).unroll().isReal(),

    transpose := self >> let(
        var := self.params[1],
        dom := self.params[2],
        ch := self.params[3],
        newvar := var.clone(),
        tags := self.getTags(),
        TICompose(newvar, dom,
            SubstVars(Copy(ch.transpose()), tab((var.id) := dom-1-newvar))).withTags(tags)
    )
));

Declare(TGath, TScat, TPrm, TConj, TS);
#F   tSPL Gath(d)
Class(TGath, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl) -> [tspl] ],
    dims := self >> [self.params[1].domain(), self.params[1].range()],
    terminate := self >> Gath(self.params[1]),
    transpose := self >> TScat(self.params[1]).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> 0
));

Class(TScat, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl) -> [tspl] ],
    dims := self >> [self.params[1].range(),self.params[1].domain()],
    terminate := self >> Scat(self.params[1]),
    transpose := self >> TGath(self.params[1]).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> 0
));

Class(TPrm, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl) -> [tspl] ],
    dims := self >> Cond(IsBound(self.params[1].domain), [self.params[1].domain(), self.params[1].range()], self.params[1].dims()),
    terminate := self >> Prm(self.params[1]),
    transpose := self >> TPrm(self.params[1].transpose()).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> 0
));

Class(TConj, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl, prm) -> [tspl,prm.transpose(),prm],
                  (tspl, prml, prmr) -> [tspl,prml,prmr] ],
    dims := self >> [self.params[2].domain(), self.params[3].range()],
    terminate := self >> ConjLR(self.params[1].terminate(), self.params[2], self.params[3]),
    transpose := self >> TConj(self.params[1].transpose(), self.params[3].transpose(), self.params[2].transpose()).withTags(self.getTags()),
    isReal := self >> self.params[1].isReal(),
    doNotMeasure := false,
    noCodelet := true,
    normalizedArithCost := self >> self.params[1].normalizedArithCost()
));

Class(TS, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (N) -> [N, false],
                  (N, t) -> [N, t] ],
    dims := self >> [self.params[1], self.params[1]],
    terminate := self >> When(self.params[2], S(self.params[1]).transpose(), S(self.params[1])),
    transpose := self >> TS(self.params[1], not self.params[2]).withTags(self.getTags()),
    isReal := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> self.params[1] - 1
));

Class(TInplace, Tagged_tSPL_Container, rec(
    abbrevs :=  [ (tspl) -> [tspl] ],
    dims := self >> self.params[1].dims(),
    terminate := self >> self.params[1].terminate(),
    transpose := self >> TInplace(self.params[1].transpose()).withTags(self.getTags()),
    isReal := self >> self.params[1].isReal(),
    isInplace := True,
    doNotMeasure := true,
    noCodelet := true,
    normalizedArithCost := self >> self.params[1].normalizedArithCost()
));

InterleavedComplexT := t -> TRC(t);
SplitComplexT := t -> let(r:= Rows(t), c:=Cols(t), TConj(TRC(t), L(2*r, 2), L(2*c, c)));
ComplexT := (t,opts) -> When((not IsBound(opts.interleavedComplex)) or (IsBound(opts.interleavedComplex) and opts.interleavedComplex), InterleavedComplexT(t), SplitComplexT(t));

InterleavedSplitComplexT := t -> let(r:= Rows(t), c:=Cols(t), TConj(TRC(t), fId(2*r), L(2*c, c)));


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

NewRulesFor(TFCall, rec(
    TFCall_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> c[1]
    )
));

NewRulesFor(TDeviceCall, rec(
    TFCall_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> c[1]
    )
));

NewRulesFor(TSparseMat, rec(
    TSparseMat_base := rec(
        forTransposition := false,
        children := nt -> [[ ]],
        applicable := (self, nt) >> When(IsBound(self.max_rows), nt.params[1][1] <= self.max_rows, true),
        apply := function(nt, c, cnt)
            local P, mat, i, j, rdata;
            P := nt.params;
            mat := MatSPL(ApplyFunc(O, P[1]));

            for i in [0..P[1][1]-1] do
                rdata := Drop(Filtered(P[2], ii->ii[1] = i)[1], 1);
                for j in rdata do
                    mat[i+1][j[1]+1] := j[2];
                od;
            od;

            return Mat(mat);
        end
    ),
    TSparseMat_VStack := rec(
        forTransposition := false,
        applicable := nt -> nt.params[1][1] > 1,
        children := nt -> [ List([0..nt.params[1][1]-1], i->TSparseMat([1, nt.params[1][2]], [let(row := Filtered(nt.params[2], j->j[1] = i)[1], [0] :: Drop(row, 1))])) ],
        apply := (nt, c, cnt) -> BB(VStack(c))
    ),
));

NewRulesFor(TIterHStack, rec(
    TIterHStack_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()), InfoNt(nt.params[2]) ]],
        apply := (nt, c, cnt) -> IterHStack(cnt[2].params[1], cnt[2].params[1].range, c[1])
    )
));

NewRulesFor(TIterVStack, rec(
    TIterVStack_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()), InfoNt(nt.params[2]) ]],
        apply := (nt, c, cnt) -> IterVStack(cnt[2].params[1], cnt[2].params[1].range, c[1])
    )
));

NewRulesFor(TNoDiagPullinLeft, rec(
    TNoDiagPullinLeft_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> NoDiagPullinLeft(c[1])
    )
));

NewRulesFor(TNoDiagPullinRight, rec(
    TNoDiagPullinRight_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> NoDiagPullinRight(c[1])
    )
));

NewRulesFor(TNoPullLeft, rec(
    TNoPullLeft_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> NoPullLeft(c[1])
    )
));

NewRulesFor(TNoPullRight, rec(
    TNoPullRight_tag := rec(
        forTransposition := false,
        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> NoPullRight(c[1])
    )
));

NewRulesFor(TColMajor, rec(
    TColMajor_MDDFT_2D := rec(
        forTransposition := false,
        switch := false,
        applicable := (self, nt) >> ObjId(nt.params[1]) = MDDFT and Length(nt.params[1].params[1]) = 2,

        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> let(n1 := nt.params[1].params[1][1], n2 := nt.params[2][1].params[1][2],
            prm := L(n1*n2, n2),
            iprm := prm.transpose(),
            iprm * c[1] * prm)
    ),
    TColMajor_MDDFT_3D := rec(
        forTransposition := false,
        switch := false,
        applicable := (self, nt) >> ObjId(nt.params[1]) = MDDFT and Length(nt.params[1].params[1]) = 3,

        children := nt -> [[ nt.params[1].withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> let(n1 := nt.params[1].params[1][1], n2 := nt.params[1].params[1][2], n3 := nt.params[1].params[1][3],
            # prm := Tensor(I(n1), L(n2*n3, n3)) * L(n1*n2*n3, n2*n3),
            prm := Prm(fCompose(L(n1*n2*n3, n2*n3), fTensor(fId(n1), L(n2*n3, n3)))),
            # iprm := L(n1*n2*n3, n1) * Tensor(I(n1), L(n2*n3, n2)),
            iprm := prm.transpose(),
            iprm * c[1] * prm)
    ),
    TColMajor_MDDFT_tensorFlip := rec(
        forTransposition := false,
        applicable := (self, nt) >> ObjId(nt.params[1]) = MDDFT,

        children := nt -> [[ ApplyFunc(MDDFT, [Reversed(nt.params[1].params[1])]::Drop(nt.params[1].params, 1)).withTags(nt.getTags()) ]],
        apply := (nt, c, cnt) -> c[1]
    ),
));


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

NewRulesFor(GT, rec(
    GT_Par := rec(
        maxSize       := false,
        minSize       := false,
        parEntireLoop := true,    # parallelize entire loop
        splitLoop := true,        # split off <nthreads> iterations

        requiredFirstTag := AParSMP,

    applicable := (self, t) >> let(
        rank := Length(t.params[4]), nthreads := t.firstTag().params[1],
        rank = 1 and let(its := t.params[4][1],
        PatternMatch(t, [GT, @(1), @(2,XChain), @(3,XChain), ...], empty_cx()) and
        IsPosInt(its/nthreads))),

        children := (self, t) >> let(
            spl := t.params[1], g := t.params[2], s := t.params[3], its := t.params[4][1],
            nthreads := t.firstTag().params[1], tags := Drop(t.getTags(), 1),
            Concatenation(
                  When(self.splitLoop,
                      [[ GT(spl, g, s, [its / nthreads]).withTags(tags), InfoNt(nthreads) ]], []),
                  When(self.parEntireLoop,
                      [[ GT(spl, XChain([0]), XChain([0]), []).withTags(tags), InfoNt(its) ]], [])
            )
        ),

        apply := (self, t, C, Nonterms) >> let(
            spl := t.params[1], N := Minimum(spl.dimensions),
            g := t.params[2], s := t.params[3], its := t.params[4][1],
            gg := When(g.params[1]=[0,1], XChain([0,1,2]), XChain([1,2,0])),
            ss := When(s.params[1]=[0,1], XChain([0,1,2]), XChain([1,2,0])),
            nthreads := t.firstTag().params[1],
            tid      := t.firstTag().params[2],
            par_its := When(IsBound(Nonterms[2]), Nonterms[2].params[1], nthreads), i := Ind(par_its),

            SMPBarrier(nthreads, tid,
                SMPSum(nthreads, tid, i, par_its,
                    Scat(ss.part(1, i, Rows(spl), [par_its, its/par_its])) *
                    C[1] *
                    Gath(gg.part(1, i, Cols(spl), [par_its, its/par_its]))))
        )
    ),

    # Push parallel tag down to preserve locality: smp(I x A) -> (I x smp(A))
    # may be good for MD DFTs
    GT_Par_IxA_Push := rec(
        forTransposition := false,
        requiredFirstTag := AParSMP,
        applicable := t -> t.rank()=1 and t.params[2]=GTPar and t.params[3]=GTPar,
        children := t -> [[ t.params[1].withTags(t.getTags()) ]],
        apply := (t, C, Nonterms) -> Tensor(I(t.params[4][1]), C[1])
    ),

    GT_Par_odd := rec(
        maxSize       := false,
        minSize       := false,
        requiredFirstTag := AParSMP,

    applicable := (self, t) >> let(
        rank := Length(t.params[4]), nthreads := t.firstTag().params[1],
        rank = 1 and let(its := t.params[4][1], not IsInt(its/nthreads) and
        PatternMatch(t, [GT, @(1), @(2,XChain), @(3,XChain), ...], empty_cx()))),

    children := (self, t) >> let(
            spl := t.params[1], g := t.params[2], s := t.params[3], its := t.params[4][1],
            nthreads := t.firstTag().params[1], tags := Drop(t.getTags(), 1),
                      [[ spl.withTags(tags) ]]),

    apply := (t, C, Nonterms) -> let(
            spl := Nonterms[1],
            its := t.params[4][1],
            i := Ind(its),
            g := fTensor(fBase(i), fId(Cols(spl))),
            s := fTensor(fBase(i), fId(Rows(spl))),
            nthreads := t.firstTag().params[1],
            tid      := t.firstTag().params[2],

            SMPSum(its, i, i, its,
                Scat(s) * C[1] * Gath(g)))
    )
));

NewRulesFor(TTensorInd, rec(
#   base cases
#   I x A
    dsA_base_smp := rec(
        forTransposition := false,
        applicable := nt -> IsParPar(nt.params) and nt.isTag(1, AParSMP) and nt.params[2].range = nt.getTags()[1].params[1],
        children := nt -> let(jp := IndPar(nt.params[2].range),
            [[ SubstVars(Copy(nt.params[1]), rec((nt.params[2].id) := jp)).withTags(Drop(nt.getTags(), 1)), InfoNt(jp) ]]),
        apply := (self, t, C, Nonterms) >> let(
            spl := C[1],
            nthreads := t.firstTag().params[1],
            tid      := t.firstTag().params[2],
            jp := Nonterms[2].params[1],
            g := fTensor(fBase(jp), fId(Cols(spl))),
            s := fTensor(fBase(jp), fId(Rows(spl))),

            SMPBarrier(nthreads, tid,
                SMPSum(nthreads, tid, jp, nthreads,
                    Scat(s) *
                    C[1] *
                    Gath(g))
        ))
    ),

    dsA_smp := rec(
        forTransposition := false,
        applicable := nt -> IsParPar(nt.params) and nt.isTag(1, AParSMP) and
            let(r := nt.params[2].range/nt.getTags()[1].params[1], IsInt(r) and r > 1),
        children := nt -> let(jp := IndPar(nt.getTags()[1].params[1]), j := Ind(nt.params[2].range/nt.getTags()[1].params[1]),
            [[ TTensorInd(
                SubstVars(Copy(nt.params[1]), rec((nt.params[2].id) := jp * V(j.range) + j)),
                j, APar, APar).withTags(Drop(nt.getTags(), 1)), InfoNt(jp) ]]),
        apply := (self, t, C, Nonterms) >> let(
            spl := C[1],
            nthreads := t.firstTag().params[1],
            tid      := t.firstTag().params[2],
            jp :=  Nonterms[2].params[1],
            g := fTensor(fBase(jp), fId(Cols(spl))),
            s := fTensor(fBase(jp), fId(Rows(spl))),

            SMPBarrier(nthreads, tid,
                SMPSum(nthreads, tid, jp, nthreads,
                    Scat(s) *
                    C[1] *
                    Gath(g))
            )
        )
    ),
    L_dsA_L_base_smp := rec(
        forTransposition := false,
        applicable := nt -> IsVecVec(nt.params) and nt.isTag(1, AParSMP) and nt.params[2].range = nt.getTags()[1].params[1],
        children := nt -> let(jp := IndPar(nt.getTags()[1].params[1]),
            [[ SubstVars(Copy(nt.params[1]), rec((nt.params[2].id) := jp)).withTags(Drop(nt.getTags(), 1)),
             InfoNt(jp) ]]),
        apply := (self, t, C, Nonterms) >> let(
            spl := C[1],
            nthreads := t.firstTag().params[1],
            tid      := t.firstTag().params[2],
            jp := Nonterms[2].params[1],
            g := fTensor(fId(Cols(spl)), fBase(jp)),
            s := fTensor(fId(Rows(spl)), fBase(jp)),

            SMPBarrier(nthreads, tid,
                SMPSum(nthreads, tid, jp, nthreads,
                    Scat(s) *
                    C[1] *
                    Gath(g))
        ))
    ),
    L_dsA_L_smp := rec(
        forTransposition := false,
        applicable := nt -> IsVecVec(nt.params) and nt.isTag(1, AParSMP) and
            let(r := nt.params[2].range/nt.getTags()[1].params[1], IsInt(r) and r > 1),
        children := nt -> let(jp := IndPar(nt.getTags()[1].params[1]), j := Ind(nt.params[2].range/nt.getTags()[1].params[1]),
            [[ TTensorInd(
                SubstVars(Copy(nt.params[1]), rec((nt.params[2].id) := jp * V(j.range) + j)),
                j, AVec, AVec).withTags(Drop(nt.getTags(), 1)), InfoNt(jp), InfoNt(j) ]]),

        apply := (self, t, C, Nonterms) >> let(
            spl := C[1],
            kernel := Nonterms[1].params[1],
            nthreads := t.firstTag().params[1],
            tid      := t.firstTag().params[2],
            jp := Nonterms[2].params[1],
            j := Nonterms[3].params[1],
            g := fCompose(fTensor(L(nthreads*Cols(kernel), nthreads), fId(j.range)), fTensor(fBase(jp), fId(Cols(spl)))),
            s := fCompose(fTensor(L(nthreads*Rows(kernel), nthreads), fId(j.range)), fTensor(fBase(jp), fId(Rows(spl)))),

            SMPBarrier(nthreads, tid,
                SMPSum(nthreads, tid, jp, nthreads,
                    Scat(s) *
                    C[1] *
                    Gath(g))
            )
        )
    )
));


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

#Declare (CellCGenSIMD, CellCompileStrategyVector, CellVHackUnparser);

#   IBM AltiVec specific (av is for "AltiVec")

vconstpr_av := (self, v) >> Cond(v.t=TInt, v.idx_cprint(),
                                  v.t = TDouble, Print("(", self.vconst1, "((float)", v.cprint(), "))"),
                                  Print("(", self.vconstv, "{", PrintCS(v.v), "})")); #changed Reversed(v.v) to v.v

prep_perm_string := function(p)
    local base_string, final_string, i_counter;
    base_string := [0,1,2,3];
    final_string := [];
    for i_counter in (p-1) do # changed Reversed(p-1) to (p-1)
        Append(final_string, (base_string+i_counter*4));
    od;
    return final_string;
end;
avshuffle := (p) -> Print("((vector unsigned char){", PrintCS(prep_perm_string(p)), "})");
avprintop := (self) >> Chain(Print(self.altivec, "("), spiral.compiler._cprintcs(self.args), Print(")"));

spushuffle := (p) -> Print("((vector unsigned char){", PrintCS(prep_perm_string(p)), "})");
spuprintop := (self) >> Chain(Print(self.spu, "("), spiral.compiler._cprintcs(self.args), Print(")"));

vpermop := (in1, in2, p, v)-> List(p, i->When(i<=v, in1[i], in2[i-v]));

#aperm := meth(self)
#    local l1, l2, l3, res, el, v;
#    res := [];
#    v := self.v();
#    l1 := Cartesian(self.params());
#    for el in l1 do
#        l2 := Filtered(el, i->i<=v);
#        l3 := Filtered(el, i->i>v);
#        if Length(l2) = Length(l3) and Try([PermList(l2-Minimum(l2)+1)])[1] and Try(PermList(l3-Minimum(l3)+1))[1] then
#            Add(res, el);
#        fi;
#    od;
#    return res;
#end;

aperm := meth(self)
    local l1, l2, l3, res, el, v;
    res := [];
    v := self.v();
    res := Cartesian(self.params());
    return Filtered(res, i->Length(Set(Copy(i)))=v);
end;

Class(vparam_av, Value,
    rec(
        __call__ := arg >> WithBases(
            arg[1],
            rec(
                p := arg[2],
                args := [],
                operations := PrintOps
            )
        ),
        t := TUnknown,
        print := self >> Print(self.p),
        cprint := self >> avshuffle(self.p),
        isVparam := true
    )
); #Class added for AltiVec

Class(vparam_spu, Value,
    rec(
        __call__ := arg >> WithBases(
            arg[1],
            rec(
                p := arg[2],
                args := [],
                operations := PrintOps
            )
        ),
        t := TUnknown,
        print := self >> Print(self.p),
        cprint := self >> spushuffle(self.p),
        #from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), [rch[1], rch[2]]),
        isVparam := true
    )
); #Class added for SPU

Class(vop_av, Exp, rec(
        __call__ := arg >> _computeExpType(WithBases(arg[1], rec(
                    p := When(Length(arg) >= arg[1].numargs+2, arg[arg[1].numargs+2], []),
                    args  := Concat(List(Sublist(arg, [2..arg[1].numargs+1]), toExpArg),
                                When(Length(arg) >= arg[1].numargs+2 and arg[arg[1].numargs+2] <> [],
                                    [vparam_av(arg[arg[1].numargs+2])], [])),
                    operations := PrintOps
                )
            )),
        params := self >> [],
        permparams := self >> Cartesian(self.params()),
        cprint := avprintop,
        vcprint := self>>self.cprint(),
        isBinop := self >> self.numargs = 2,
        isUnop := self >> self.numargs = 1,
        isLoadop := False,
        isStoreop := False,
    )
); #Class added for AltiVec

Class(vop_spu, Exp, rec(
        __call__ := arg >> code._computeExpType(
            WithBases(arg[1], rec(
                    p := When(Length(arg) >= arg[1].numargs+2, arg[arg[1].numargs+2], []),
                    args  := Concat(List(Sublist(arg, [2..arg[1].numargs+1]), toExpArg),
                                When(Length(arg) >= arg[1].numargs+2 and arg[arg[1].numargs+2] <> [],
                                    [vparam_spu(arg[arg[1].numargs+2])], [])),
                    operations := PrintOps
                )
                )),
        params := self >> [],
        permparams := self >> Cartesian(self.params()),
        cprint := spuprintop,
        vcprint := self>>self.cprint(),
        isBinop := self >> self.numargs = 2,
        isUnop := self >> self.numargs = 1,
        isLoadop := False,
        isStoreop := False,
    )
); #Class added for SPU

Class(vbinop_av, vop_av, rec(numargs:=2, vcost := 1)); #Class added for AltiVec
Class(vbinop_spu, vop_spu, rec(numargs:=2, vcost := 1)); #Class added for SPU

Class(vunop_av, vop_av, rec(numargs:=1, vcost := 1)); #Class added for AltiVec
Class(vunop_spu, vop_spu, rec(numargs:=1, vcost := 1)); #Class added for SPU

Class(vunbinop_av, vop_av,
    rec(
        numargs := 1,
        params := self >> self.binop.params(),
        v := self >> self.binop.v(),
        ctype := self >> self.binop.ctype(),
        cprint := self >> self.binop(self.args[1], self.args[1], self.p).cprint(),
        semantic := (self, in1, p) >> self.binop.semantic(in1, in1, p),
        vcost := 1
    )
); #Class added for AltiVec

Class(vunbinop_spu, vop_spu,
    rec(
        numargs := 1,
        params := self >> self.binop.params(),
        v := self >> self.binop.v(),
        ctype := self >> self.binop.ctype(),
        cprint := self >> self.binop(self.args[1], self.args[1], self.p).cprint(),
        semantic := (self, in1, p) >> self.binop.semantic(in1, in1, p),
        vcost := 1
    )
); #Class added for SPU


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

spu_4x32f.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := Tensor(
                  L(4, 2),
                  I(2)
                ),
              l := 1,
              N := 4,
              n := 2,
              r := 2 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 2, 5, 6 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 3, 4, 7, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 2, 4 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 5, 7, 6, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                I(2),
                L(4, 2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 2, 4 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 5, 7, 6, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 2),
              l := 1,
              N := 8,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 5, 7 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 2, 4, 6, 8 ] ) ],
          v := 4,
          vperm := VPerm(L(8, 2), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 5, 7 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 2, 4, 6, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 4),
              l := 1,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 5, 2, 6 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 3, 7, 4, 8 ] ) ],
          v := 4,
          vperm := VPerm(L(8, 4), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 5, 2, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 7, 4, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 2),
              l := 1,
              N := 8,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 2, 4 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 5, 7, 6, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ) *
              L(8, 2), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 2, 4 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 5, 7, 6, 8 ]))), 4, 4.2000000000000002) *
            VTensor(I(2), 4) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 5, 7 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 2, 4, 6, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ) *
              Tensor(
                I(2),
                L(4, 2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 5, 7 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 2, 4, 6, 8 ]))), 4, 4.2000000000000002) *
            VTensor(I(2), 4) ), rec(
          perm := rec(
              spl := L(8, 4),
              l := 1,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 5, 3, 7 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 2, 6, 4, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ) *
              L(8, 4), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 5, 3, 7 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 2, 6, 4, 8 ]))), 4, 4.2000000000000002) *
            VTensor(I(2), 4) ), rec(
          perm := rec(
              spl := L(8, 4),
              l := 1,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 2, 4 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 5, 7, 6, 8 ] ) ],
          v := 4,
          vperm := VTensor(I(2), 4) *
            VPerm(L(8, 4) *
              Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 2, 4 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 5, 7, 6, 8 ]))), 4, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 5, 2, 6 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 3, 7, 4, 8 ] ) ],
          v := 4,
          vperm := VTensor(I(2), 4) *
            VPerm(Tensor(
                I(2),
                L(4, 2)
              ) *
              Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 5, 2, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 7, 4, 8 ]))), 4, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 2),
              l := 1,
              N := 8,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 5, 3, 7 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 2, 6, 4, 8 ] ) ],
          v := 4,
          vperm := VTensor(I(2), 4) *
            VPerm(L(8, 2) *
              Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 5, 3, 7 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 2, 6, 4, 8 ]))), 4, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) ) ],
  unrules := [ rec(
          perm := rec(
              spl := L(4, 2),
              l := 1,
              N := 4,
              n := 2,
              r := 1 ),
          instr := rec(
              instr := vuperm_4x32f_spu,
              p := [ 1, 3, 2, 4 ] ),
          v := 4,
          vperm := VPerm(L(4, 2), (y, x) -> assign(vref(y, 0, 4), vuperm_4x32f_spu(vref(x, 0, 4), [ 1, 3, 2, 4 ])), 4, 0.90000000000000002) ) ],
  x_I_vby2 := [ [ rec(
              instr := vperm_4x32f_spu,
              p := [ 1, 2, 5, 6 ] ), rec(
              instr := vperm_4x32f_spu,
              p := [ 1, 2, 7, 8 ] ) ], [ rec(
              instr := vperm_4x32f_spu,
              p := [ 3, 4, 5, 6 ] ), rec(
              instr := vperm_4x32f_spu,
              p := [ 3, 4, 7, 8 ] ) ] ] ));
spu_2x64f.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := L(4, 2),
              l := 1,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_2x64f_spu,
                  p := [ 1, 3 ] ), rec(
                  instr := vperm_2x64f_spu,
                  p := [ 2, 4 ] ) ],
          v := 2,
          vperm := VPerm(L(4, 2), (y, x) -> chain(assign(vref(y, 0, 2), vperm_2x64f_spu(vref(x, 0, 2), vref(x, 2, 2), [ 1, 3 ])), assign(vref(y, 2, 2), vperm_2x64f_spu(vref(x, 0, 2), vref(x, 2, 2), [ 2, 4 ]))), 2, 2) ) ],
  unrules := [  ],
  x_I_vby2 := [ [ rec(
              instr := vperm_2x64f_spu,
              p := [ 1, 3 ] ), rec(
              instr := vperm_2x64f_spu,
              p := [ 1, 4 ] ) ], [ rec(
              instr := vperm_2x64f_spu,
              p := [ 2, 3 ] ), rec(
              instr := vperm_2x64f_spu,
              p := [ 2, 4 ] ) ] ] ));
ppu_4x32f.setRules(rec(
  binrules := [ rec(
          perm := rec(
              spl := Tensor(
                  L(4, 2),
                  I(2)
                ),
              l := 1,
              N := 4,
              n := 2,
              r := 2 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 2, 5, 6 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 3, 4, 7, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 2, 4 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 5, 7, 6, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                I(2),
                L(4, 2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 2, 4 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 5, 7, 6, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 2),
              l := 1,
              N := 8,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 5, 7 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 2, 4, 6, 8 ] ) ],
          v := 4,
          vperm := VPerm(L(8, 2), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 5, 7 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 2, 4, 6, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 4),
              l := 1,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 5, 2, 6 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 3, 7, 4, 8 ] ) ],
          v := 4,
          vperm := VPerm(L(8, 4), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 5, 2, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 7, 4, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 2),
              l := 1,
              N := 8,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 2, 4 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 5, 7, 6, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ) *
              L(8, 2), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 2, 4 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 5, 7, 6, 8 ]))), 4, 4.2000000000000002) *
            VTensor(I(2), 4) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 5, 7 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 2, 4, 6, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ) *
              Tensor(
                I(2),
                L(4, 2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 5, 7 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 2, 4, 6, 8 ]))), 4, 4.2000000000000002) *
            VTensor(I(2), 4) ), rec(
          perm := rec(
              spl := L(8, 4),
              l := 1,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 5, 3, 7 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 2, 6, 4, 8 ] ) ],
          v := 4,
          vperm := VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ) *
              L(8, 4), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 5, 3, 7 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 2, 6, 4, 8 ]))), 4, 4.2000000000000002) *
            VTensor(I(2), 4) ), rec(
          perm := rec(
              spl := L(8, 4),
              l := 1,
              N := 8,
              n := 4,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 3, 2, 4 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 5, 7, 6, 8 ] ) ],
          v := 4,
          vperm := VTensor(I(2), 4) *
            VPerm(L(8, 4) *
              Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 3, 2, 4 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 5, 7, 6, 8 ]))), 4, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := Tensor(
                  I(2),
                  L(4, 2)
                ),
              l := 2,
              N := 4,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 5, 2, 6 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 3, 7, 4, 8 ] ) ],
          v := 4,
          vperm := VTensor(I(2), 4) *
            VPerm(Tensor(
                I(2),
                L(4, 2)
              ) *
              Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 5, 2, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 7, 4, 8 ]))), 4, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) ), rec(
          perm := rec(
              spl := L(8, 2),
              l := 1,
              N := 8,
              n := 2,
              r := 1 ),
          instr := [ rec(
                  instr := vperm_4x32f_spu,
                  p := [ 1, 5, 3, 7 ] ), rec(
                  instr := vperm_4x32f_spu,
                  p := [ 2, 6, 4, 8 ] ) ],
          v := 4,
          vperm := VTensor(I(2), 4) *
            VPerm(L(8, 2) *
              Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 5, 3, 7 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 2, 6, 4, 8 ]))), 4, 4.2000000000000002) *
            VPerm(Tensor(
                L(4, 2),
                I(2)
              ), (y, x) -> chain(assign(vref(y, 0, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 1, 2, 5, 6 ])), assign(vref(y, 4, 4), vperm_4x32f_spu(vref(x, 0, 4), vref(x, 4, 4), [ 3, 4, 7, 8 ]))), 4, 2) ) ],
  unrules := [ rec(
          perm := rec(
              spl := L(4, 2),
              l := 1,
              N := 4,
              n := 2,
              r := 1 ),
          instr := rec(
              instr := vuperm_4x32f_spu,
              p := [ 1, 3, 2, 4 ] ),
          v := 4,
          vperm := VPerm(L(4, 2), (y, x) -> assign(vref(y, 0, 4), vuperm_4x32f_spu(vref(x, 0, 4), [ 1, 3, 2, 4 ])), 4, 0.90000000000000002) ) ],
  x_I_vby2 := [ [ rec(
              instr := vperm_4x32f_spu,
              p := [ 1, 2, 5, 6 ] ), rec(
              instr := vperm_4x32f_spu,
              p := [ 1, 2, 7, 8 ] ) ], [ rec(
              instr := vperm_4x32f_spu,
              p := [ 3, 4, 5, 6 ] ), rec(
              instr := vperm_4x32f_spu,
              p := [ 3, 4, 7, 8 ] ) ] ] ));