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

Class(ScriptGenSSE, ScriptGenBase, rec(

	_arch := () -> "SSE",

	_init := meth(self)
		self._setTransform(SGKEY_FFT);
		self._setType(SGKEY_SPCX);
		self._setSize(16);
		self._setFilename("");
	end,

	_validTransforms := meth(arg)
		return [SGKEY_FFT, SGKEY_IFFT, SGKEY_FFT_2D, SGKEY_IFFT_2D];
	end,

	_validSizes := meth(arg)
		local self, szs, xform, type;
		self  := arg[1];
		szs   := [];
		xform := self.getSettingsValue(SGKEY_TRANSFORM);
		type  := self.getSettingsValue(SGKEY_DATATYPE);

		if xform in [SGKEY_FFT, SGKEY_IFFT] then
			szs := Filtered([1..512], i->ForAll(Factors(i), j->j<=16) and IsInt(i/4));
			Append(szs, Filtered([513..1024], i->ForAll(Factors(i), j->j<=16) and IsInt(i/16)));
			Append(szs, List([11..16], i->2^i));
		elif xform in [SGKEY_FFT_2D, SGKEY_IFFT_2D] then
			szs := List(Filtered([1..360], i->ForAll(Factors(i), j->j<=16) and IsInt(i/4)), n -> [n, n]);
		elif xform = SGKEY_WHT then
			if type = SGKEY_DPCX then
				szs := List([2..10], i->2^i);
			else
				szs := List([4..10], i->2^i);
			fi;
		fi;

		return szs;
	end,

	_validTypes := meth(arg)
		return [SGKEY_SPCX, SGKEY_DPCX];
	end,

	_genScript := meth(self, runType)
		local scrstr, xform, type, szs, tempvar, tempopts, optrec, optstr, isa, funcstr,
			use_cx;

		tempvar  := "tdp";
		xform	 := self.getSettingsValue(SGKEY_TRANSFORM);
		type	 := self.getSettingsValue(SGKEY_DATATYPE);
		szs      := self.getSettingsValue(SGKEY_SIZE);
		scrstr	 := "";

		tempopts := tempvar::"Opts";

		if (type = SGKEY_SPCX) then
			isa    := "SSE_4x32f";
			use_cx := ForAny(szs, s -> not IsInt(s / 16));
		else
			isa    := "SSE_2x64f";
			use_cx := false;
		fi;

		if xform in [SGKEY_FFT, SGKEY_IFFT] then
			optrec := rec (
				tsplRader:=false,
				tsplBluestein:=false,
				tsplPFA:=false,
				oddSizes:=false,
				interleavedComplex := true,
			);
			if use_cx then
				if (type = SGKEY_SPCX) then
					optrec.cplxVect := true;
					optrec.realVect := false;
					optrec.PRDFT := false;
					optrec.URDFT := true;
				fi;
			fi;
			if xform = SGKEY_IFFT then
				optrec.transInverse := true;
			fi;
			funcstr := "doSimdDft("::String(szs)::", "::isa::", "::tempopts::")";
		elif xform in [SGKEY_FFT_2D, SGKEY_IFFT_2D] then
			optrec := rec (
				interleavedComplex := true,
                oddSizes := false,
				svct := true,
				splitL := false,
				pushTag := true,
				flipIxA := false,
				stdTTensor := true,
				tsplPFA := false
			);
			if xform = SGKEY_IFFT_2D then
				optrec.transInverse := true;
			fi;
			funcstr := "doSimdMddft("::String(szs)::", "::isa::", "::tempopts::")";
		elif xform = SGKEY_WHT then
			optrec := rec (
				svct := true,
				stdTTensor := true,
				tsplPFA := false
			);
			if (type = SGKEY_SPCX) then
				optrec.oddSizes := false;
			else
				optrec.verify := true;
				optrec.oddSizes := true;
			fi;
			funcstr := "doSimdWht("::String(szs)::", "::isa::", "::tempopts::")";
		else
			return "";
		fi;

		optrec.faultTolerant := true;

		if IsBound(self._settings.(SGKEY_FUNCNAME)) then
			optrec.functionNameRoot := self._settings.(SGKEY_FUNCNAME);
		fi;

		optstr := StringPrint(optrec);
		scrstr := tempopts::" := "::optstr::";;\n";
		Append(scrstr, tempvar::" := "::funcstr::";;\n");
		Append(scrstr, tempvar::"."::runType::"();\n");

		return scrstr;
	end,

)); # Class ScriptGenSSE

SetScriptGenConstructor(ScriptGenSSE);


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

Declare(simtBlockIdxX, simtBlockIdxY, simtBlockIdxZ);
Declare(simtThreadIdxX, simtThreadIdxY, simtThreadIdxZ);
Declare(simt_syncgrid, simt_syncblock, simt_synccluster);

Declare(ASIMTListDim);

# The basic assumption is that threads are organized in a 6-dimensional structure (a 3D grid of 3D blocks).

# A dimension tag is a list of (thread) ranges over a one or more Grid/Block dimensions.
# e.g., ASIMTGridDimX(32) refers to 32 blocks (indexed from 0 to 31) over the X grid dimension.

# A range can also be a subset of the whole set of allocated elements:
# e.g., ASIMTGridDimX(32, [4,8]) refers to 5 blocks (indexed from 4 to 8) over the X grid dimension.

# More than one ranges can also be expressed over the same dimension:
# e.g., ASIMTGridDimX(32, [0,3], [4,8])
# As well as over different ones:
# e.g., ASIMTListDim( ASIMTGridDimX(32, [4,8]), ASIMTGridDimY(96) )

# SIMT Grid/BLock Dimension tags
Class(ASIMTDim, ASingletonTag, rec(
    isSIMTTag := true,
    # When lowering to icode a dimension tag instantiates a dimension index
    _idx      := Ignore,
    _build_gen_idx := (self, Idx, plist) >> ApplyFunc(Idx, plist),
    simt_idx    := (arg) >> let(self := arg[1], idx := Copy(self._idx), Cond(idx=Ignore, idx, Length(arg)>1, ApplyFunc(idx.set_rng, self.simt_range(arg[2])), ApplyFunc(idx.set_rng, self.simt_range(1)) ) ),
    need_cond := (self, i) >> let(L := Length(self.params), i < L and L > 1 and (self.params[i+1] <> [0,self.params[1]-1]) ),
    simt_cond   := (self, i) >> let( rng := self.simt_range(i), When(
                            self.need_cond(i), Cond(
                                rng[1] = 0, leq(self._idx, V(Last(rng))),
                                Last(rng) = self.params[1]-1, geq(self._idx, V(rng[1])),
                                rng[1] = Last(rng), eq(self._idx, V(rng[1])),
                                logic_and(geq(self._idx, V(rng[1])), leq(self._idx, V(Last(rng))))
                                ),
                            V(true)
                            )),
    update_simt_range := meth(self, i, new_rng)
        local pre, post, dim;

        dim := self;
        if ObjId(new_rng) = ObjId(dim) then
            if Length(dim.params) < i then
            dim.params := dim.params :: List([Length(dim.params)..i], p -> dim.simt_range(p));
            fi;
            pre  := Take(dim.params, i);
            post := When(Length(dim.params)>i+1, Drop(dim.params, i+1), []);
            dim.params := pre::new_rng.simt_all_ranges()::post;
        elif Length(dim.params) <= 2 and i = 1 then
            dim := new_rng;
        else
            if Length(dim.params) < i then
            dim.params := dim.params :: List([Length(dim.params)..i], p -> dim.simt_range(p));
            fi;
            pre  := [ApplyFunc( ObjId(dim), Take(dim.params, i) ) ];
            post := When(Length(dim.params)>i+1, [ApplyFunc( ObjId(dim), Drop(dim.params, i+1) )], []);
            dim := ApplyFunc(ASIMTListDim, pre::[new_rng]::post);
        fi;
        return dim;
    end,
    simt_range  := (self, i) >> When(Length(self.params) > i, self.params[i+1], [0, self.params[1]-1]),
    simt_all_ranges := (self) >> When(Length(self.params) > 1, Drop(self.params,1), [ [0, self.params[1]-1] ]),
    simt_sync   := (self) >> skip(),
    num_ranges := (self) >> Maximum(1, Length(self.params)-1)
    ));

Class(ASIMTGridDim,  ASIMTDim, rec(
    simt_sync := (self) >> simt_syncgrid()
    ));

Class(ASIMTGridDimX,  ASIMTGridDim, rec(
    updateParams := meth(self)
        self._idx := self._build_gen_idx(simtBlockIdxX, self.params{[1]});
        return self;
    end,
    ));
Class(ASIMTGridDimY,  ASIMTGridDim, rec(
    updateParams := meth(self)
        self._idx := self._build_gen_idx(simtBlockIdxY, self.params{[1]});
        return self;
    end,
    ));
Class(ASIMTGridDimZ,  ASIMTGridDim, rec(
    updateParams := meth(self)
        self._idx := self._build_gen_idx(simtBlockIdxZ, self.params{[1]});
        return self;
    end,
    ));

Class(ASIMTBlockDim, ASIMTDim, rec(
    simt_sync := (self) >> simt_syncblock()
    ));

Class(ASIMTBlockDimX,  ASIMTBlockDim, rec(
    simt_sync := (self) >> simt_synccluster(),
    updateParams := meth(self)
        self._idx := self._build_gen_idx(simtThreadIdxX, self.params{[1]});
        return self;
    end,
    ));
Class(ASIMTBlockDimY,  ASIMTBlockDim, rec(
    updateParams := meth(self)
        self._idx := self._build_gen_idx(simtThreadIdxY, self.params{[1]});
        return self;
    end,
    ));
Class(ASIMTBlockDimZ,  ASIMTBlockDim, rec(
    updateParams := meth(self)
        self._idx := self._build_gen_idx(simtThreadIdxZ, self.params{[1]});
        return self;
    end,
    ));

Class(ASIMTListDim, ASIMTDim, rec(

    _point_to_rng := meth(self, i)
        local p, curr, curr_nr;

        curr := self.params[1];
        curr_nr := curr.num_ranges();
        p := 2;
        while i > curr_nr and p <= Length(self.params) do
            i := i - curr_nr;
            curr := self.params[p];
            curr_nr := curr.num_ranges();
            p := p + 1;
        od;
        #[<inner-dim containing requested rng>, <pos of rng within inner-dim>, <pos of inner-dim in params>]
        return [curr, i, p-1];
    end,
    simt_idx    := (arg) >> let(self := arg[1], nump := Length(self.params),
                                When(Length(arg)>1, let( d_i := self._point_to_rng(arg[2]), d_i[1].simt_idx(d_i[2]) ),
                                                    List(self.params, p -> p.simt_idx())
                                ) ),
    need_cond := (self, i) >> let( d_i := self._point_to_rng(i), d_i[1].need_cond(d_i[2]) ),
    simt_cond   := (self, i) >> let( d_i := self._point_to_rng(i), d_i[1].simt_cond(d_i[2]) ),
    update_simt_range := meth(self, i, new_rng)
        local d_i;

        d_i := self._point_to_rng(i);
        self.params[d_i[3]] := d_i[1].update_simt_range(d_i[2], new_rng);

        return self;
    end,
    num_ranges := (self) >> Sum(List(self.params, i -> i.num_ranges())),
    simt_range  := (self, i) >> let( d_i := self._point_to_rng(i), d_i[1].simt_range(d_i[2]) ),
    simt_all_ranges := (self) >> let( numi := self.num_ranges(), List([1..numi], i -> self.simt_range(i)) ),
    simt_sync   := (self) >> Maximum( List(self.params, p -> p.simt_sync()) ),
    simt_dim    := (self, i) >> self.params[i],
    simt_dim_at := (self, i) >> let( d_i := self._point_to_rng(i), d_i[1])
    ));

Class(ASIMTLoopDim,  ASIMTDim, rec(
    simt_range  := (self, i) >> [],
    simt_all_ranges := (self) >> [],
    num_ranges := (self) >> 0,
    update_simt_range := meth(self, i, new_rng)
        local dim;

        dim := self;
        if ObjId(new_rng) <> ObjId(dim) then
            dim := When(i=1, new_rng, ApplyFunc(ASIMTListDim, Replicate(i-1, dim)::[new_rng]) );
        fi;
        return dim;
    end,
    ));

Class(ASIMTFlag, ASIMTDim, rec(
    updateParams := meth(self)
        self._simt_dim := self.params[1];
    end,
    simt_dim    := (self) >> self._simt_dim,
    simt_idx    := (arg) >> let(self := arg[1], ApplyFunc(self._simt_dim.simt_idx, arg{[2..Length(arg)]}) ),
    need_cond := (self, i) >> self._simt_dim.need_cond(i),
    simt_cond   := (self, i) >> self._simt_dim.simt_cond(i),
    update_simt_range := meth(self, i, new_rng)
        self._simt_dim := self._simt_dim.update_simt_range(i, new_rng);
        return self;
    end,
    simt_range  := (self, i) >> self._simt_dim.simt_range(i),
    simt_all_ranges := (self) >> self._simt_dim.simt_all_ranges(),
    simt_sync   := (self) >> self._simt_dim.simt_sync(),
    num_ranges := (self) >> self._simt_dim.num_ranges()
    ));

Class(ASIMTKernelFlag, ASIMTFlag);

Class(ASIMTFissionFlag, ASIMTFlag);

Class(ASIMTTileFlag, ASIMTFlag, rec(
    updateParams := meth(self)
        Inherited();
        self._tsize  := self.params[2];
    end,

    tsize := (self) >> self._tsize
    ));

IsASIMTDim := (tag) -> ObjId(tag) in ASIMTDim._all_descendants();

##### Wrapping ######

Class(PlaceHolderMat, BaseMat, rec(

    abbrevs := [ (id, ttype, dims) -> [ rec(
                            id := id,
                            TType := Copy(ttype),
                            dimensions := Copy(dims) ) ] ],

    new := (self, spl) >> let( is_spl := IsSPL(spl), SPL(WithBases(self, rec(
                            id := When(is_spl, var._id(""), spl.id),
                            TType := When(is_spl, Copy(spl.TType), spl.TType),
                            dimensions := When(is_spl, Copy(spl.dimensions), spl.dimensions) )))
                        ),

    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), rch).appendAobj(self),

    rChildren := (self) >> [ self.id, self.TType, self.dimensions ],
    rSetChild := rSetChildFields("id", "TType", "dimensions"),

    rng := meth(self) local d;
        d := [self.dimensions[1]];
        if IsBound(self.a.t_out) then
            return List(TransposedMat([self.a.t_out, d]), e -> TArray(e[1], e[2]));
        else
            return List(d, e -> TArray(self.TType, e));
        fi;
    end,

    dmn := meth(self) local d;
        d := [self.dimensions[2]];
        if IsBound(self.a.t_in) then
            return List(TransposedMat([self.a.t_in, d]), e -> TArray(e[1], e[2]));
        else
            return List(d, e -> TArray(self.TType, e));
        fi;
    end,

    ));

wrap_rule_apply := function(rules, wrapper)
    local rule;
    rules := Flat([rules]);
    for rule in rules do
        if IsNewRule(rule) then
            rule._apply := rule.apply;
            rule.apply := wrapper(rule);
        else
            rule._rule := rule.rule;
            rule.rule  := wrapper(rule);
        fi;
    od;

end;

unwrap_rule_apply := function(rules)
    local rule;
    rules := Flat([rules]);
    for rule in rules do
        if IsNewRule(rule) then
            rule.apply := rule._apply;
        else rule.rule := rule._rule;
        fi;
    od;

end;

_original_apply := (R, nt, children, nonterms) -> Checked(IsRule(R), IsSPL(nt),
    When(IsNewRule(R),
     R._apply(nt, children, nonterms),
     When(NumGenArgs(R._rule)=2,
          R._rule(nt.params, children),
          R._rule(nt.params, children, nonterms)))
);

dummy_wrap := (R) -> ( (nt, c, cnt) -> let(PrintLine("Using wrapped rule."), _apply(R, nt, c, cnt)) );

_strl := (n, c) -> StringJoin("", Replicate(n, c));

TaggableOps := [Tensor, SUM, ISum, DirectSum, IDirSum, VStack, IterVStack, HStack1, IterHStack1];

wrap_apply := (R) -> function(arg)
                        local nt, c, cnt, simt_exp, simt_dims, phs, ss;

                        nt := arg[1]; c := arg[2]; cnt := arg[3];

                        # Use placeholders for children to prevent Collect from going beyond rule.apply's boundaries
                        phs := List(c, spl -> PlaceHolderMat(spl));
                        simt_exp := _original_apply(R, nt, phs, cnt);  # This dispatches to right call: _rule (for Rules) or _apply (for NewRules)
                        ss := Collect(simt_exp, @(1, TaggableOps) );

                        simt_dims := let( cd := nt.getA("simt_dim", [ASIMTLoopDim()]),
                                        Cond(cd=Ignore, [],
                                             IsList(cd) and Length(cd) = 1, Replicate(Length(ss), cd[1]),
                                             cd)
                                    );
                        if Length(simt_dims) <> Length(ss) then
                            Error("Number of tags (", Length(simt_dims), ") <> number of taggable operators (", Length(ss), ")");
                        fi;

                        # PrintLine("@@@@@@@@@@@@@ Beg ", R, "@@@@@@@@@@@@@@@");
                        # PrintLine("NT      >> ", nt);
                        # PrintLine("simt_dims >> ", simt_dims);
                        # PrintLine("simt_expr >>", simt_exp);
                        # PrintLine("phs     >>", phs);
                        # PrintLine("ch      >>", c);
                        # PrintLine("@@@@@@@@@@@@@ End ", R, "@@@@@@@@@@@@@@@");

                        for i in [1..Length(ss)] do
                            simt_exp := SubstTopDownNR(simt_exp, @(1, ObjId(ss[i])).cond(e->e=ss[i]),
                                        e -> ApplyFunc(AllClasses.("SIMT"::ObjId(ss[i]).name), [simt_dims[i], ss[i]]));
                        od;

                        for i in [1..Length(c)] do
                            simt_exp := SubstTopDownNR(simt_exp, @(1).cond(e->e=phs[i]), e -> c[i] );
                        od;

                        return simt_exp;
                    end;

# Node of a TagTree
tag_rec := (tag, tag_list) -> rec(simt_dim := tag, ch_tags := tag_list);

# Merge RuleTree and TagTree
tmp_tag_rt := function(rt, dims, opts)
    if dims = Ignore then
        rt.node := rt.node.setA(["simt_dim", [ASIMTLoopDim()] ]);
        DoForAll([1..Length(rt.children)], i -> tmp_tag_rt(rt.children[i], dims, opts) );
    elif IsASIMTDim(dims) then
        rt.node := rt.node.setA(["simt_dim", [dims] ]);
        DoForAll([1..Length(rt.children)], i -> tmp_tag_rt(rt.children[i], Ignore, opts) );
    elif IsList(dims) then
        rt.node := rt.node.setA(["simt_dim", [ASIMTLoopDim()] ]);
        DoForAll([1..Length(rt.children)], i -> tmp_tag_rt(rt.children[i], dims[i], opts) );
    else
        rt.node := rt.node.setA(["simt_dim", dims.simt_dim]);
        DoForAll([1..Length(rt.children)], i -> tmp_tag_rt(rt.children[i], dims.ch_tags[i], opts) );
    fi;
end;


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

Class(RulesCxRC_Op, RuleSet);
RewriteRules(RulesCxRC_Op, rec(

    vRC_Compose := Rule([vRC, @(1, Compose)], e -> Compose(List(@(1).val.children(), vRC))),
    vRC_SUM := Rule([vRC, @(1, SUM)], e -> SUM(List(@(1).val.children(), vRC))),
    vRC_SUMAcc := Rule([vRC, @(1, SUMAcc)], e -> SUMAcc(List(@(1).val.children(), vRC))),

    vRC_Container := Rule([vRC, @(1, [BB,Buf,Inplace,Grp,NoPull,NoPullLeft,NoPullRight, NoDiagPullin, NoDiagPullinLeft, NoDiagPullinRight ])],
        e -> ObjId(@(1).val)(vRC(@(1).val.child(1)))),

    vRC_Data := Rule([vRC, @(1, Data)], e -> Data(@(1).val.var, @(1).val.value, vRC(@(1).val.child(1)))),

    vRC_RStep := Rule([vRC, @(1, RecursStep)], e -> RecursStep(2*@(1).val.yofs, 2*@(1).val.xofs,
                                                      vRC(@(1).val.child(1)))),

    vRC_ISum := Rule([vRC, @(1, ISum)], e -> ISum(@(1).val.var, @(1).val.domain, vRC(@(1).val.child(1)))),
    vRC_ICompose := Rule([vRC, @(1, ICompose)], e -> ICompose(@(1).val.var, @(1).val.domain, vRC(@(1).val.child(1)))),

    vRC_Grp := Rule([vRC, @(1, Grp)], e -> Grp(vRC(@(1).val.child(1)))),

    vRC_Scale := Rule([vRC, @(1, Scale)], e ->
         vRC(Diag(fConst(Rows(@(1).val), @(1).val.scalar))) * vRC(@(1).val.child(1))),

    vRC_CR := Rule([vRC, @(1, CR)], e -> @(1).val.child(1)),

    VTensor_VScale := Rule([@(1, VTensor), [VScale, @(2), @(3), @(4)]], e -> VScale(VTensor(@(2).val, @(1).val.vlen), @(3).val, @(4).val*@(1).val.vlen)),
    VTensor_VTensor := Rule([@(1, VTensor), @(2, VTensor)], e -> VTensor(@(2).val.child(1), @(1).val.vlen*@(2).val.vlen)),

    vRC_SMP := Rule([@(1, vRC), @(2, [SMPSum, SMPBarrier])],
        e -> let(s := @(2).val, CopyFields(s, rec(_children := List(s.children(), c->ObjId(@(1).val)(c)), dimensions := @(1).val.dimensions)))),

    # should RC and VRC be in some other place?
    vRC_TCvt := Rule( [@(0, [vRC, RC, VRC]), @(1, TCvt)],
        e -> let( t := @(1).val, TCvt( 2*t.n(), t.isa_to(), t.isa_from(), t.props()).withTags(t.getTags()).takeAobj(t) )),
));

Class(RulesCxRC_Term, RuleSet);
RewriteRules(RulesCxRC_Term, rec(
    vRC_VContainer := Rule([vRC, @(1, VContainer)], e -> VContainer(vRC(@(1).val.child(1)), @(1).val.isa.uncplx())),

#    RC_VTensor := Rule([ RC, @(1, VTensor) ], e -> RCVTensor(@(1).val.child(1), @(1).val.vlen)),
#
#    RCVTensor_VTensor := Rule(@(1, RCVTensor, e -> e.isReal()), e -> VTensor(@(1).val.child(1), 2*@(1).val.vlen)),

#
#    RC_VGathScat_sv := Rule([@(1, RC), @(2, [VGath_sv, VScat_sv])], e->
#        @(2).val.rcVariant(@(2).val.func, 2*@(2).val.v, @(2).val.sv)),
#
#    RC_VGathScat := Rule([@(1, RC), @(2, [VGath, VScat])], e->
#        ObjId(@(2).val)(@(2).val.func, 2*@(2).val.v)),
#
#    RC_VPrm_x_I := Rule([@(1, RC), @(2, VPrm_x_I)], e ->  VPrm_x_I(@(2).val.func, @(2).val.inverse, 2*@(2).val.v)),
#
#    RC_VDiag_x_I := Rule([@(1, RC), @(2, VDiag_x_I)], e ->  VDiag_x_I(@(2).val.element, 2*@(2).val.v)),
#
#    VContainer_RCDiag := Rule(@@(1, RCDiag, (e, cx) -> IsBound(cx.VContainer) and Length(cx.VContainer)=1),
#        (e, cx) -> RCVDiag(@@(1).val.element, Last(cx.VContainer).isa.getV())),
#
#    RCVTensor_Blk := Rule([@(1, RCVtensor), @(2, Blk)], e -> RCVDiag(RCData(@(2).val.element), 2*@(2).val.v)),

##--- RC rules moved to vRC

## Shared Franz/Yevgen Diag rules
    vRC_Diag      := Rule([@(1, vRC), @(2, Diag) ], e -> vRC(VDiag(@(2).val.element, 1))),

#############################
## Original Franz's rules:

    vRC_VDiag     := Rule([@(1, vRC), @(2, VDiag)], e -> let(v:=2*@(2).val.v,
        RCVDiag(VData(RCData(@(2).val.element), v), v))),

    vRC_VDiag_x_I := Rule([@(1, vRC), @(2, VDiag_x_I)], e -> let(
        v   := @(2).val.v,
        elt := @(2).val.element,
        dup := Cond(IsVecT(elt.range()), elt, VDup(elt, v)),
        RCVDiag(RCVData(dup), 2*v)
    )),

############################################
## Yevgen's searchable rules for autolib
    vRC_VDiag     := Rule([@(1, vRC), @(2, VDiag)], (e,cx) -> let(
	v   := 2*@(2).val.v,
	alt := Global.autolib.RCOND("diag", Global.autolib.rcondModeFreedom, maybe(),
		     RCVDiag(VData(RCData(@(2).val.element), v), v),
		     RCVDiagSplit(RCVDataSplit(@(2).val.element, v), v)),
	Cond( not IsBound(cx.opts.hack_vRC_VDiag), alt.child(2),
	      cx.opts.hack_vRC_VDiag = "search",   alt,
	      cx.opts.hack_vRC_VDiag = "compact",  alt.child(1),
	      cx.opts.hack_vRC_VDiag = "split",    alt.child(2) ))), # strip alternator outside of autolib

    vRC_VDiag_x_I := Rule([@(1, vRC), @(2, VDiag_x_I)], (e,cx) -> let(
        v   := @(2).val.v,
        elt := @(2).val.element,
	t   := elt.range(),
        # Cond(IsVecT(t), VDup(elt, 2), VDup(elt, 2*v)), v),  # does not work, dimension error
	Cond(IsRealT(t),
	         VDiag_x_I(elt, 2*v),

	     IsVecT(t) and IsRealT(t.t),
	         VDiag_x_I(VDup(elt, 2), 2*v), # NOTE: make sure sub-vector vdup is properly
		                               #        handled by the compiler rewrite rules
	     # else, complex
	     let(
		 dup := Cond(IsVecT(t), elt, VDup(elt, v)),
		 alt := Global.autolib.RCOND("diag", Global.autolib.rcondModeFreedom, maybe(),
                     RCVDiag(RCVData(dup), 2*v),
		     RCVDiagSplit(RCVDataSplitVec(dup), 2*v)),
		 Cond( not IsBound(cx.opts.hack_vRC_VDiag_x_I),  alt.child(2),  # strip alternator outside of autolib
		     cx.opts.hack_vRC_VDiag_x_I = "search",    alt,
		     cx.opts.hack_vRC_VDiag_x_I = "compact",   alt.child(1),
		     cx.opts.hack_vRC_VDiag_x_I = "split",     alt.child(2) )
	      )
	))),

    # we need to check the divisibility of dimensions, because all VGathXXX constructs output full vectors
    # This means that gather of 3 points won't be handled here.
    vRC_Gath := Rule([vRC, @@(1, Gath, (e,cx)->IsBound(cx.VContainer) and cx.VContainer<>[] and imod(2*e.func.domain(), Last(cx.VContainer).isa.getV())=0)],
        (e,cx) -> VGath_sv(@@(1).val.func, Last(cx.VContainer).isa.getV(), 2)),

    vRC_Scat := Rule([vRC, @@(1, [Scat,ScatAcc], (e,cx)->IsBound(cx.VContainer) and cx.VContainer<>[] and imod(2*e.func.domain(), Last(cx.VContainer).isa.getV())=0)],
        (e,cx) -> Cond(@@(1).val _is Scat,
	    VScat_sv(@@(1).val.func, Last(cx.VContainer).isa.getV(), 2),
	    VScat_svAcc(@@(1).val.func, Last(cx.VContainer).isa.getV(), 2))),

#    NOTE: why is this commented out?
#    VA: btw - in fixed size code this happens anyway with help of vRC_REALMAT and RulesVec through VTensor
#    vRC_Prm := Rule([vRC, @(1, Prm)], e -> VPrm_x_I(@(1).val.func, 2)),

    # NOTE: This rule looks evil, as explained below.
    #        Rules for VTensor( VRCDiag | VPRM | ... ) do not exist
    #        and if it fires prematurely (maybe due to some composite operator)
    #        the rewrite system will get stuck later.
    #
    #        This is exactly what happens in autolib ASP.DFT(N) using _CT1Tr.
    #
    #        Thus I blocked it. I think it should be possible to get rid of
    #        it in a graceful way.
    #
    vRC_REALMAT := Rule([vRC, @@(1).cond(
        (e,cx) -> not IsBound(cx.opts.autolib) and e.isReal() and not ObjId(e) in
            [VRCDiag, VPrm_x_I, VIxJ2, VJxI, VBlk,
             VGath_zero, VScat_zero, VGath_pc, VScat_pc, VScat_pcAcc, IxVGath_pc, IxVScat_pc,
             VGath_sv, VScat_sv, VScat_svAcc, VGath, VScat, VScatAcc, VGath_u, VScat_u, VScatAcc_u, VStretchGath, VStretchScat,
             Gath, Scat, ScatAcc])],
        e -> VTensor(@@(1).val, 2)),

    vRC_Blk := Rule([vRC, @(1, Blk, e -> not e.isReal())], e -> RCVBlk(MapMat(@(1).val.element, i->[i]), 2)),
    vRC_Blk1 := Rule([vRC, @(1, Blk1, e -> not e.isReal())], e -> vRC(Blk([[@(1).val.element]]))),

    VTensor_XXX := Rule([@(1,VTensor,e->e.vlen=2), @(2,[VGath, VScat, VScatAcc, VGath_sv, VScat_sv, VScat_svAcc, VGath_u, VScat_u, VScatAcc_u, VPrm_x_I])], e->vRC(e.child(1))),

    VTensor_I := Rule([@(1, VTensor), [I, @(2)]], e -> VPrm_x_I(fId(@(2).val), e.vlen)),

    vRC_VGath := Rule([@(1, vRC), @(2, VGath)], e -> VGath(@(2).val.func, 2*@(2).val.v)),
    vRC_VScat := Rule([@(1, vRC), @(2, [VScat, VScatAcc])], e -> ObjId(@(2).val)(@(2).val.func, 2*@(2).val.v)),

    vRC_VGath_u := Rule([@(1, vRC), @(2, VGath_u)], e -> VGath_u(fTensor(@(2).val.func, fBase(2, 0)), 2*@(2).val.v)),
    vRC_VScat_u := Rule([@(1, vRC), @(2, [VScat_u, VScatAcc_u])], e -> ObjId(@(2).val)(fTensor(@(2).val.func, fBase(2, 0)), 2*@(2).val.v)),

    vRC_VGath_sv := Rule([@(1, vRC), @(2, VGath_sv)], e -> VGath_sv(@(2).val.func, 2*@(2).val.v, 2*@(2).val.sv, Cond(@(2).val.rem _is Unk, Unk(TInt), 2*@(2).val.rem))),
    vRC_VScat_sv := Rule([@(1, vRC), @(2, [VScat_sv, VScat_svAcc])], e -> ObjId(@(2).val)(@(2).val.func, 2*@(2).val.v, 2*@(2).val.sv, Cond(@(2).val.rem _is Unk, Unk(TInt), 2*@(2).val.rem))),

    vRC_VStretchGath := Rule([@(1, vRC), @(2, VStretchGath)], e -> vRCStretchGath(@(2).val.func, @(2).val.part, 2*@(2).val.v)),
    vRC_VStretchScat := Rule([@(1, vRC), @(2, VStretchScat)], e -> vRCStretchScat(@(2).val.func, @(2).val.part, 2*@(2).val.v)),

    fix_VGathScat := Rule([@(1, [VGath, VScat, VScatAcc]), [@(2,fTensor), ...,
            [fId, @@(3).cond((e, cx)->IsBound(cx.VContainer) and cx.VContainer <> [] and @(1).val.v < Last(cx.VContainer).isa.getV() and IsInt(@(1).val.v*e / Last(cx.VContainer).isa.getV())
            and (not IsBound(cx.VTensor) or (IsBound(cx.VTensor) and cx.VTensor = []))
            and (not IsBound(cx.vRC) or (IsBound(cx.vRC) and cx.vRC = []))
	    and not (IsBound(cx.opts.assumeOuter_vRC) and cx.opts.assumeOuter_vRC)
             ) ]]],
        (e, cx) -> ObjId(@(1).val)(fTensor(DropLast(@(2).val.children(), 1), fId(@(1).val.v*@@(3).val / Last(cx.VContainer).isa.getV())), Last(cx.VContainer).isa.getV())),

    diagTensor_fPrecompute := Rule([diagTensor, [fPrecompute, @(1)], @(2)], x->fPrecompute(diagTensor(@(1).val, @(2).val))),
    # shouldn't this be in some other place:
    fStretch_fPrecompute := Rule([fStretch, @(1, fPrecompute), @(2), @(3)], e -> fPrecompute(fStretch(@(1).val.child(1), @(2).val, @(3).val))),

    RCVData_fPrecompute := Rule([@(0,[RCVData,RCVDataSplitVec,VDataRDup]), [fPrecompute, @(1)]],
	x->fPrecompute(ObjId(@(0).val)(@(1).val))),
    RCVData2_fPrecompute := Rule([@(0,[RCVDataSplit]), [fPrecompute, @(1)], @(2)],
	x->fPrecompute(ObjId(@(0).val)(@(1).val, @(2).val))),

    RCVData_fCompose    := Rule([RCVData, @(1, fCompose, x -> not IsType(Last(x.children()).range()))],
        e -> let(ch := @(1).val.children(),
	         chsplit := SplitBy(ch, x->IsType(x.range())),
		 fCompose(RCVData(fCompose(chsplit[1])), fCompose(chsplit[2])))),

    RCVDataSplitVec_fCompose    := Rule([RCVDataSplitVec, @(1, fCompose, x -> not IsType(Last(x.children()).range()))],
        e -> let(ch := @(1).val.children(),
	         chsplit := SplitBy(ch, x->IsType(x.range())),
		 fCompose(RCVDataSplitVec(fCompose(chsplit[1])), fTensor(fCompose(chsplit[2]), fId(2))))),

    # NOTE: potential problem -- RCVDataSplit_fCompose rule is missing

    RCVData_diagDirsum  := Rule([@(0,[RCVData,RCVDataSplitVec]), @(1,diagDirsum)],
        e -> ApplyFunc(diagDirsum, List(@(1).val.children(), ObjId(@(0).val)))),

    RCVDataSplit_diagDirsum  := Rule([ @(0, RCVDataSplit, x -> x.v mod 2 = 0),
                                   @(1, diagDirsum, x -> x.subdomainsDivisibleBy(@(0).val.v/2)),
                                   @(2)],
        e -> ApplyFunc(diagDirsum, List(@(1).val.children(), x->ObjId(@(0).val)(x, @(2).val)))),
    # pull out fPrecompute because it's stuck in there, RCVDataSplit cannot go through diagDirsum
    RCVDataSplit_diagDirsum_fPrecompute  := Rule( [@(1, diagDirsum), ..., @@(2, fPrecompute, (x, cx) -> cx.isInside(RCVDataSplit) and not @(1).val.subdomainsDivisibleBy(Last(cx.RCVDataSplit).v/2) ), ...],
        e -> fPrecompute(SubstTopDown(e, fPrecompute, ee -> ee.child(1)))),

    # When vector length is 2 we can go through compose
    RCVData2_fCompose_diagDirsum := Rule([RCVDataSplit, [@(1, fCompose), @(2, diagDirsum), ...], @(3).cond( x -> x = 2)],
        e -> fCompose(RCVDataSplit(@(2).val, 2), fTensor(fCompose(Drop(@(1).val.children(),1)), fId(2)))),

    VDup_fCompose := Rule([VDup, [@(1,fCompose), @(2), ...], @(3)],
        e -> fCompose(VDup(@(2).val, @(3).val), fCompose(Drop(@(1).val.children(),1)))),

    VDup_diagDirsum := Rule([VDup, @(1,diagDirsum), @(2)],
        e -> ApplyFunc(diagDirsum, List(@(1).val.children(), x->VDup(x, @(2).val)))),

    # YSV: Previously RCVBlk(...) was created with complex vector length,
    #      now we use real vlen for consistency
    vRC_VTensor_Blk := Rule([@(0, vRC), [@(1, VTensor), @(2, Blk)]],
        e -> let(v := @(1).val.vlen,
	         RCVBlk(MapMat(@(2).val.element, e -> Replicate(v, e)), 2*v))),

    vRC_VGath_pc := Rule([@(1, vRC), @(2, VGath_pc)],
        e -> let(g := @(2).val, VGath_pc(g.N*2, g.n*2, g.ofs*2, g.v*2))),

    vRC_VScat_pc := Rule([@(1, vRC), @(2, [VScat_pc, VScat_pcAcc])],
        e -> let(s := @(2).val, ObjId(@(2).val)(s.N*2, s.n*2, s.ofs*2, s.v*2))),

    vRC_IxVGath_pc := Rule([@(1, vRC), @(2, IxVGath_pc)],
        e -> let(g := @(2).val, IxVGath_pc(g.k, g.N*2, g.n*2, g.ofs*2, g.v*2))),

    vRC_IxVScat_pc := Rule([@(1, vRC), @(2, IxVScat_pc)],
        e -> let(s := @(2).val, IxVScat_pc(s.k, s.N*2, s.n*2, s.ofs*2, s.v*2))),

    vRC_VIxJ2 := Rule([vRC, @(1, VIxJ2)], e -> RCVIxJ2(@(1).val.v*2)),
    vRC_VJxI  := Rule([vRC, @(1, VJxI)], e -> VJxI(@(1).val.m, @(1).val.v*2)),

    vRC_VBlk_real := Rule([vRC, @(1, VBlk, e -> e.isReal())],
	e -> VBlk(MapMat(@(1).val.element, i ->
		TVect(i.t.t.realType(), 2*@(1).val.v).value(
		    ConcatList(i.v, j -> [j, j]))), 2*@(1).val.v)),

    vRC_VBlk_complex := Rule([vRC, @(1, VBlk, e -> not e.isReal())],
	e -> RCVBlk(MapMat(@(1).val.element, _unwrap), 2*@(1).val.v)),

    vRC_VGath_zero := Rule([vRC, @(1, VGath_zero)], e -> VGath_zero(@(1).val.N, @(1).val.n, @(1).val.v*2)),

    vRC_VScat_zero := Rule([vRC, @(1, VScat_zero)], e -> VScat_zero(@(1).val.N, @(1).val.n, @(1).val.v*2)),

    vRC_VPrm_x_I := Rule([vRC, @(1, VPrm_x_I)], e -> VPrm_x_I(@(1).val.func, @(1).val.v*2)),

    sv2__Scat_sv_fTensor := Rule([@(1, [VScat_sv, VScat_svAcc]), [@(2,fTensor), ..., [fId, @(3).cond(e -> IsInt(@(1).val.sv*EvalScalar(e)/@(1).val.v))]]],
        e -> let(v := @(1).val.v, sv := @(1).val.sv, n := EvalScalar(@(3).val), newn := sv*n/v,
	         scat := Cond(@(1).val _is VScat_sv, VScat, VScatAcc),
             scat(fTensor(DropLast(@(2).val.children(), 1), fId(newn)), v))),

    sv2__Gath_sv_fTensor := Rule([@(1, VGath_sv), [@(2,fTensor), ..., [fId, @(3).cond(e -> IsInt(@(1).val.sv*EvalScalar(e)/@(1).val.v))]]],
        e -> let(v := @(1).val.v, sv := @(1).val.sv, n := EvalScalar(@(3).val), newn := sv*n/v,
             VGath(fTensor(DropLast(@(2).val.children(), 1), fId(newn)), v))),

#    vRC_VRCDiag := Rule([vRC, [@(1, VRCDiag), @(2, VDup)]],
#        e -> let(v := @(1).val.v*@(2).val.v, VRCDiag(VDup(@(2).val.func, v), v))),
#
#    vRC_VRCDiag_fPrecompute := Rule([vRC, [@(1, VRCDiag), [fPrecompute, @(2, VDup)]]],
#        e -> let(v := @(1).val.v*@(2).val.v, VRCDiag(fPrecompute(VDup(@(2).val.func, v)), v)))
#

    # NOTE: this is a recipe for disaster
#    vRC_VRCDiag_VDup := Rule([vRC, @(1, VRCDiag, e->Length(Collect(e.element, VDup))=1)],
#        e -> let(vdup := Collect(@(1).val.element, VDup)[1], v := 2*vdup.v, VRCDiag(SubstTopDown(@(1).val.element, @(2, VDup), i->VDup(@(2).val.func, v)), v))),

    vRC_VRCDiag := Rule([vRC, @(1, VRCDiag)], e -> VRCDiag(VDataRDup(@(1).val.element), 2*@(1).val.v)),

    vRC_I := Rule( [@(0, vRC), @(1, I)],
        e -> I(2*@(1).val.params[1])),

    vRC_VOLMultiplication := Rule([vRC, @(1, VOLMultiplication)],
        e -> let(p := @(1).val.rChildren(), RCVOLMultiplication(p[1], p[2], 2*p[3]))),
    vRC_VOLConjMultiplication := Rule([vRC, @(1, VOLConjMultiplication)],
        e -> let(p := @(1).val.rChildren(), RCVOLConjMultiplication(p[1], p[2], 2*p[3]))),

));

RulesCxRC := MergedRuleSet(RulesCxRC_Op, RulesCxRC_Term);


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

# This file implements a register allocator for straight line code
# Based on Belady's farthest-first eviction strategy.  For normal
# three-operand code without memory ops, this should yield the optimal
# (w.r.t. number of spill-related load-stores) code.
#
# For two-operand code without memory ops, this can still be optimal,
# but I'm not sure.
#
# For two-operand code *with* memory ops (=x86) it is not clear at
# all, whether this simple allocator is best. Main problem is that we
# have a choice of spilling versus using memop in an instruction.
#
# The output of this pass should be fed into C-compiler, and spill
# space here is represented as a regular array. The hope is that
# C-compiler won't touch the spill array (S), and therefore will not
# run out of registers.
#
# Potential problems: on 2-operand architectures (x86), the compiler
# can internally operate on 3-operand code, and then convert to
# 2-operand at some point. This may render this allocator useless,
# because 3->2 conversion is likely to destroy lot's of information.
# NOTE: Investigate this further.
#

# we are assuming SSA code
MarkLiveness := function(code)
    local live, add, remove, v, c, already_dead, n;
    MarkDefUse(code);

    # Starting from the statement, invariant : variable is dead afters its last use
    already_dead := Set([]);
    for c in Reversed(code.cmds) do
        c.dead := Difference(PredCmd(c), already_dead);
	UniteSet(already_dead, c.dead);
    od;

    live := Set([]);
    n := 1;
    for c in code.cmds do
        Constraint(IsAssign(c));
	if SuccCmd(c) <> [] then AddSet(live, c.loc); fi;
	SubtractSet(live, c.dead);
	c.live := Copy(live);
	c.n := n; # position in a chain
	n := n+1;
    od;
    return code;
end;

# RequiredVars(<chain>) - minimum number of required variables
# MarkLiveness should be run beforehand
#
RequiredVars := (code) -> Maximum0(List(code.cmds, c->Length(c.live)));

# ReuseVars(<chain>) - reusing variables in the chain
# MarkLiveness should be run beforehand,
# output is no longer in SSA form
#

ReuseVars := function(code)
    local free, current, map, reduced, new, out, v, c, f, alive;
    reduced := [];
    free    := [];
    current := [];

    map     := tab();

    for c in code.cmds do

        alive := Set(c.live :: Filtered(c.dead, e -> IsVar(e) and not IsArrayT(e.t) and not IsPtrT(e.t)));
        new   := Difference(alive, current);
        out   := Difference(current, alive);

        for v in out do
            if IsBound(map.(v.id)) then
                free := [map.(v.id)] :: free;
                Unbind(map.(v.id));
            else
                free := free :: [v];
            fi;
        od;
        if Length(new)=1 then
            f := PositionProperty(free, e -> e.t=new[1].t);
            if f<>false then
                map.(new[1].id) := free[f];
                free := ListWithout(free, f);
            fi;
        fi;

        Add(reduced, SubstParamsCustom(c, map, [var]));
        current := alive;
    od;

    return chain(reduced);
end;

# RequiredRegs(<chain>) - minimum number of required registers
# MarkLiveness should be run beforehand
#
RequiredRegs := (code, t) -> Maximum(List(code.cmds, c->Length(
	    Filtered(c.live, x->IsBound(x.t) and x.t=t))));

# spill using Belady's MIN heuristic - spill the value used farthest away
_spill := function(n, regmap, regvals, S, free_slots, newcmds)
    local dists, maxdist, spill, evict, r;

    dists := List(regvals, v -> let(
	    dd := Filtered(List(SuccLoc(v), succ -> succ.def.n), succn -> succn >= n),
            # if successor list (beyond current n) is empty, the variable is dead and should
	    # be evicted, assign it a really large dist value 2^20
	    When(dd=[], 2^20, Minimum(dd))));

    maxdist := Maximum(dists);
    evict := regvals[Position(dists, maxdist)];
    r := regmap.(evict.id); # freed up register

    if not IsBound(evict.value) then # do not save constants into spill slot
	Constraint(Length(free_slots) > 0);
	spill := nth(S, free_slots[1]);
	RemoveSet(free_slots, free_slots[1]);
	Add(newcmds, assign(spill, r));
	regmap.(evict.id) := spill;
    else
	Unbind(regmap.(evict.id));
    fi;

    RemoveSet(regvals, evict);

    return r;
end;

_getreg := function(n, newvar, free, regmap, regvals, S, free_slots, newcmds)
    local r;
    if Length(free) > 0 then
        # we have free registers, grab <r> from free list
	r := free[1];
	RemoveSet(free, r);
    else
 	# no free registers spill something to free up <r>
	r := _spill(n, regmap, regvals, S, free_slots, newcmds);
    fi;
    AddSet(regvals, newvar);
    regmap.(newvar.id) := r;
    return r;
end;

_reload := function(n, vars, free, regmap, regvals, S, free_slots, newcmds)
    local v, r, reload_from;
    for v in vars do
        reload_from := When(IsBound(regmap.(v.id)), regmap.(v.id), v);
        r := _getreg(n, v, free, regmap, regvals, S, free_slots, newcmds);
	Add(newcmds, assign(r, reload_from));
        # if <reload_from> is a spill location (ie not a variable),
        # then return the  spill slot to the free list
	if not IsVar(reload_from) then
	AddSet(free_slots, reload_from.idx); fi;
    od;
end;

# MarkLiveness should be run beforehand
#
RegAlloc := function(code, numregs, t)
    local regs, stack, regmap, regvals, c, cc, free, dead, r, v,
         dists, maxdist, evict, newcmds,
	 S, free_slots, reqregs, spill, deadregs,
	 reloads;

    reqregs := RequiredRegs(code, t);
    S := var("S", TArray(t, When(reqregs-numregs+1 > 0, reqregs-numregs+21, 0)));
    # free spill slots
    free_slots := Set([0..S.t.size-1]);

    regmap := rec();
    free := Set(List([0..numregs-1], x -> var(Concat("r", StringInt(x)), t)));
    for r in free do r.isReg := true; od;

    regvals := Set([]); # set of all variables that reside in registers (=not spilled)

    newcmds := [];
    for c in code.cmds do
        Constraint(IsAssign(c));
	# plug in allocated registers instead of SSA vars
	cc := Copy(c);

	reloads := Union(
	    # do not reload constants (in register for which regmap.(..) == var, and external)
	    Filtered(cc.op_inout(), x->IsVar(x) and (not IsBound(regmap.(x.id)) or # external
		                                     not IsVar(regmap.(x.id))) and not (TPtr = ObjId(x.t) or TInt = x.t)), # or spilled

	    # do not reload constants or other inputs used only once
	    # ideally we should use Belady heuristic again
	    Filtered(cc.op_in(), x->IsVar(x) and (not IsBound(regmap.(x.id)) or
		                                  not IsVar(regmap.(x.id))
	                                     and (not Length(SuccCmd(x.def))=1)) and not (TPtr = ObjId(x.t) or  TInt = x.t)));

	_reload(cc.n, reloads, free, regmap, regvals, S, free_slots, newcmds);
	if (cc.loc in cc.op_inout()) or (cc.loc in cc.op_in()) then
	    cc := SubstVars(cc, regmap);
	else
	    cc.exp := SubstVars(cc.exp, regmap);
	fi;
 	# allocate a register if c.loc only is a variable of type t
	if IsVar(cc.loc) and not IsBound(cc.loc.isReg) and cc.loc.t = t then
	    deadregs := Filtered(cc.dead, v -> IsVar(v) and v in regvals);
	    if deadregs <> [] then
		# grab reg from the dead list (this automatically implements coalescing)
		v := deadregs[1];

		RemoveSet(regvals, v);
		AddSet(regvals, cc.loc);
		r := regmap.(v.id);
		Unbind(regmap.(v.id));
		regmap.(cc.loc.id) := r;
		cc.loc := r;
	    else
		r := _getreg(cc.n, cc.loc, free, regmap, regvals, S, free_slots, newcmds);
		cc.loc := r;
	    fi;
	fi;
	# do not output dummy moves (ie assign(r1, r1)).
	if not (ObjId(cc)=assign and cc.loc = cc.exp) then
	    Add(newcmds, cc);
	fi;

	# put registers associated with dead variables on the free list
	for v in cc.dead do
	    if IsVar(v) and (v in regvals) then
		AddSet(free, regmap.(v.id));
		RemoveSet(regvals, v);
		Unbind(regmap.(v.id));
	    fi;
	od;

    od;
    return chain(newcmds);
end;


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

Class(FFTXIAOpts, FFTXOpts, rec(
    tags := [],
    operations := rec(Print := s -> Print("<FFTX IA options record>")),
    max_threads := 1024
));

iaOpts := function(arg)
    local opts, optrec, sseopts, smpopts;

    optrec := rec(dataType := T_Real(64), globalUnrolling := 32);
    smpopts := rec(numproc := 4, api := "OpenMP");
    sseopts := rec(svct := true, splitL := false, oddSizes := false, stdTTensor := true, tsplPFA := false);

    if Length(arg) >= 1 then optrec := CopyFields(optrec, arg[1]); fi;
    if Length(arg) >= 2 then smpopts := CopyFields(smpopts, arg[2]); fi;
    if Length(arg) >= 3 then sseopts := CopyFields(sseopts, arg[3]); fi;

    opts := CopyFields(FFTXOpts, IAGlobals.getOpts(optrec, smpopts, sseopts));
    opts.breakdownRules.TFCall := FFTXOpts.breakdownRules.TFCall;

    # FFTX specific breakdown rules
    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;
end;

Declare(ParseOptsIA);

Class(FFTXIADefaultConf, rec(
    __call__ := self >> self,
    getOpts := (self, t) >> ParseOptsIA(self, t),
    operations := rec(Print := s -> Print("<FFTX IA Default Configuration>")),
    useOMP := true,
    useSIMD := true
));

iaConf := rec(
    defaultName := "defaultIAConf",
    defaultOpts := (arg) >> FFTXIADefaultConf,
    useOMP := true,
    useSIMD := true,
    confHandler := iaOpts
);

fftx.FFTXGlobals.registerConf(iaConf);

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;

#--

# this is a first experimental opts-deriving logic. This needs to be done extensible and properly
ParseOptsIA := function(conf, t)
    local tt, _tt, _conf, _opts;

    _opts := iaOpts();
    return _opts;

#    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/MDDFT/MDPRDFT
#        if ((Length(Collect(t, TTensorInd)) >= 1) or (Length(Collect(t, TTensorI)) >= 1)) and
#            ((Length(Collect(t, DFT)) = 1) or (Length(Collect(t, PRDFT)) = 1) or (Length(Collect(t, IPRDFT)) = 1) or
#              (Length(Collect(t, MDDFT)) >= 1) or (Length(Collect(t, MDPRDFT)) >= 1) or (Length(Collect(t, IMDPRDFT)) >= 1)) then
#            _conf := FFTXGlobals.confBatchFFTCUDADevice();
#            _opts := FFTXGlobals.getOpts(_conf);
#            return _opts;
#        fi;
#
#        # detect 3D 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);
#            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) = MDRConv) or ((ObjId(_tt) = TTensorI) and (ObjId(_tt.params[1]) = MDRConv)) then
#                _conf := FFTXGlobals.confMDRConvCUDADevice();
#                _opts := FFTXGlobals.getOpts(_conf);
#                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;
#        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;


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

Class(TerminateRaderMid, RuleSet);
RewriteRules(TerminateRaderMid, rec(
    terminateRaderMid := Rule(@(1, VecRaderMid), e-> e.term())
));

Class(StretchRaderMid, RuleSet);
RewriteRules(StretchRaderMid, rec(

    stretchCT_A := ARule(Compose,
        [ [ DelayedDirectSum, @(2, VScat_sv, e->Cols(e)=getV(e)), [ @(3, Compose), ..., @(4, IxVGath_pc) ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, @(7, VGath_sv, e->Rows(e)=getV(e)), [ @(8, Compose),
                @(9, IxVScat_pc, s-> let(g:=@(4).val, s.k=g.k and s.N=g.N and s.ofs=g.ofs and s.v=g.v )), ... ] ]
        ],
        e -> [ DelayedDirectSum(@(2).val, Compose(DropLast(@(3).val.children(), 1))),
               let(g:=@(4).val , @(5).val.stretchCT(g.N)),
               DelayedDirectSum(@(7).val, Compose(Drop(@(8).val.children(), 1))) ]),

    stretchCT := ARule(Compose,
        [ [ DelayedDirectSum, [ @(1, Compose), ..., @(2, VScat_sv, e->Cols(e)=getV(e)) ], [ @(3, Compose), ..., @(4, IxVGath_pc) ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, [ @(6, Compose), @(7, VGath_sv, e->Rows(e)=getV(e)), ... ], [ @(8, Compose),
                @(9, IxVScat_pc, s-> let(g:=@(4).val, s.k=g.k and s.N=g.N and s.ofs=g.ofs and s.v=g.v )), ... ] ]
        ],
        e -> [ DelayedDirectSum(@(1).val, Compose(DropLast(@(3).val.children(), 1))),
               let(g:=@(4).val , @(5).val.stretchCT(g.N)),
               DelayedDirectSum(@(6).val, Compose(Drop(@(8).val.children(), 1))) ]),

#--
    stretchCT2_A := ARule(Compose,
        [ [ DelayedDirectSum, @(2, VScat_sv, e->Cols(e)=getV(e)), [ @(3, Compose), ..., @(4, IxVGath_pc), [VScat_sv, fId] ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, @(7, VGath_sv, e->Rows(e)=getV(e)), [ @(8, Compose),
                [VGath_sv, fId], @(9, IxVScat_pc, s-> let(g:=@(4).val, s.k=g.k and s.N=g.N and s.ofs=g.ofs and s.v=g.v )), ... ] ]
        ],
        e -> [ DelayedDirectSum(@(2).val, Compose(DropLast(@(3).val.children(), 2))),
               let(g:=@(4).val , @(5).val.stretchCT(g.N)),
               DelayedDirectSum(@(7).val, Compose(Drop(@(8).val.children(), 2))) ]),

    stretchCT2 := ARule(Compose,
        [ [ DelayedDirectSum, [ @(1, Compose), ..., @(2, VScat_sv, e->Cols(e)=getV(e)) ], [ @(3, Compose), ..., @(4, IxVGath_pc), [VScat_sv, fId] ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, [ @(6, Compose), @(7, VGath_sv, e->Rows(e)=getV(e)), ... ], [ @(8, Compose),
                [VGath_sv, fId], @(9, IxVScat_pc, s-> let(g:=@(4).val, s.k=g.k and s.N=g.N and s.ofs=g.ofs and s.v=g.v )), ... ] ]
        ],
        e -> [ DelayedDirectSum(@(1).val, Compose(DropLast(@(3).val.children(), 2))),
               let(g:=@(4).val , @(5).val.stretchCT(g.N)),
               DelayedDirectSum(@(6).val, Compose(Drop(@(8).val.children(), 2))) ]),

#--
    stretchPFA_A := ARule(Compose,
        [ [ DelayedDirectSum, @(2, VScat_sv, e->Cols(e)=getV(e)), [ @(3, Compose), ..., @(4, VStretchGath), [VScat_sv, fId] ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, @(7, VGath_sv, e->Rows(e)=getV(e)), [ @(8, Compose),
                [VGath_sv, fId], @(9, VStretchScat, s-> let(g:=@(4).val, s.part=g.part and s.func.range()=g.func.range() and s.v=g.v )),  ... ] ]
        ],
        e -> [ DelayedDirectSum(@(2).val, Compose(DropLast(@(3).val.children(), 2))),
               let(g:=@(4).val , @(5).val.stretchPFA(g.part, g.func.range(), @(4).val.func)),
               DelayedDirectSum(@(7).val, Compose(Drop(@(8).val.children(), 2))) ]),

    stretchPFA := ARule(Compose,
        [ [ DelayedDirectSum, [ @(1, Compose), ..., @(2, VScat_sv, e->Cols(e)=getV(e)) ], [ @(3, Compose), ..., @(4, VStretchGath), [VScat_sv, fId] ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, [ @(6, Compose), @(7, VGath_sv, e->Rows(e)=getV(e)), ... ], [ @(8, Compose),
                [VGath_sv, fId], @(9, VStretchScat, s-> let(g:=@(4).val, s.part=g.part and s.func.range()=g.func.range() and s.v=g.v )),  ... ] ]
        ],
        e -> [ DelayedDirectSum(@(1).val, Compose(DropLast(@(3).val.children(), 2))),
               let(g:=@(4).val , @(5).val.stretchPFA(g.part, g.func.range(), @(4).val.func)),
               DelayedDirectSum(@(6).val, Compose(Drop(@(8).val.children(), 2))) ]),
#--
    stretchPFA2_A := ARule(Compose,
        [ [ DelayedDirectSum, [ @(1, Compose), ..., @(2, VScat_sv, e->Cols(e)=getV(e)) ], [ @(3, Compose), ..., @(4, VStretchGath), ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, [ @(6, Compose), @(7, VGath_sv, e->Rows(e)=getV(e)), ... ], [ @(8, Compose),
                @(9, VStretchScat, s-> let(g:=@(4).val, s.part=g.part and s.func.range()=g.func.range() and s.v=g.v )),  ... ] ]
        ],
        e -> [ DelayedDirectSum(@(1).val, Compose(DropLast(@(3).val.children(), 1))),
               let(g:=@(4).val , @(5).val.stretchPFA(g.part, g.func.range(), @(4).val.func)),
               DelayedDirectSum(@(6).val, Compose(Drop(@(8).val.children(), 1))) ]),

    stretchPFA2 := ARule(Compose,
        [ [ DelayedDirectSum, @(2, VScat_sv, e->Cols(e)=getV(e)), [ @(3, Compose), ..., @(4, VStretchGath), ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, @(7, VGath_sv, e->Rows(e)=getV(e)), [ @(8, Compose),
                @(9, VStretchScat, s-> let(g:=@(4).val, s.part=g.part and s.func.range()=g.func.range() and s.v=g.v )),  ... ] ]
        ],
        e -> [ DelayedDirectSum(@(2).val, Compose(DropLast(@(3).val.children(), 1))),
               let(g:=@(4).val , @(5).val.stretchPFA(g.part, g.func.range(), @(4).val.func)),
               DelayedDirectSum(@(7).val, Compose(Drop(@(8).val.children(), 1))) ]),
#--
    stretchPFA3_A := ARule(Compose,
        [ [ DelayedDirectSum, [ @(1, Compose), ..., @(2, VScat_sv, e->Cols(e)=getV(e)) ], [ @(3, Compose), ..., @(4, DelayedPrm) ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, [ @(6, Compose), @(7, VGath_sv, e->Rows(e)=getV(e)), ... ], [ @(8, Compose),
                @(9, DelayedPrm, s-> let(g:=@(4).val, 1=1)),  ... ] ]
        ],
        e -> [ DelayedDirectSum(@(1).val, Compose(DropLast(@(3).val.children(), 1))),
               let(g:=@(4).val , @(5).val.stretchPFA(1, Cols(g), @(4).val.func)),
               DelayedDirectSum(@(6).val, Compose(Drop(@(8).val.children(), 1))) ]),

    stretchPFA3 := ARule(Compose,
        [ [ DelayedDirectSum, @(2, VScat_sv, e->Cols(e)=getV(e)), [ @(3, Compose), ..., @(4, DelayedPrm) ] ],
          @(5, VecRaderMid),
          [ DelayedDirectSum, @(7, VGath_sv, e->Rows(e)=getV(e)), [ @(8, Compose),
                @(9, DelayedPrm, s-> let(g:=@(4).val, 1=1)),  ... ] ]
        ],
        e -> [ DelayedDirectSum(@(2).val, Compose(DropLast(@(3).val.children(), 1))),
               let(g:=@(4).val , @(5).val.stretchPFA(1, Cols(g), @(4).val.func)),
               DelayedDirectSum(@(7).val, Compose(Drop(@(8).val.children(), 1))) ])
));


#
# cls 17: A_2
#
handle17char2:=function()
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=AllClasses(orbs)[17];
    info:=infos[17];

    rep:=Representative(o);
    #pr[15]=[0,0,1,1,1,0]
    #pr[13]=[0,1,1,1,0,0]
    #pr[26]=[1,1,1,2,1,0]
    tmp1:=ApplyRootsReflections(rep,[15,13,26]);
    uu:=Unipotent(chevalleyAdj(rep),[[3,1]]);
    tmp1:=Conj(tmp1,uu);

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[15,13,26]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,1]]);
    tmp2:=Conj(tmp2,uu);

    return [tmp1,tmp2];
end;

#
# cls 10: D_4(a_1)
#
handle10char2:=function()
    local tmp1,tmp2,tmp3,tmp4,o,info,rep,cent,uu;
    o:=AllClasses(orbs)[10];
    info:=infos[10];

    rep:=Representative(o);
    #pr[4] =[0,0,0,1,0,0]
    #pr[32]=[1,1,2,2,1,1]
    #pr[33]=[1,1,1,2,2,1]
    tmp1:=ApplyRootsReflections(rep,[4,32,33]);
    uu:=Unipotent(chevalleyAdj(rep),[[3,-1],[5,1]]);
    tmp1:=Conj(tmp1,uu);
    tmp1:=ConjugateByTori(tmp1,[4,1,2,6],[-1,-1,-1,-1]);

    #pr[20]=[0,1,0,1,1,1]
    #pr[21]=[0,0,1,1,1,1]
    tmp3:=ApplyRootsReflections(rep,[20,21]);
    uu:=Unipotent(chevalleyAdj(rep),[[3,1],[4,1],[5,-1],[9,1],[10,-1]]);
    tmp3:=Conj(tmp3,uu);
    tmp3:=ConjugateByTori(tmp3,[2,4],[-1,-1,-1,-1]);

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ApplyRootsReflections(cent,[4,32,33]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,-1],[5,1]]);
    tmp2:=Conj(tmp2,uu);
    tmp2:=ConjugateByTori(tmp2,[4,1,2,6],[-1,-1,-1,-1]);

    tmp4:=ApplyRootsReflections(cent,[20,21]);
    uu:=Unipotent(chevalleyAdj(cent),[[3,1],[4,1],[5,-1],[9,1],[10,-1]]);
    tmp4:=Conj(tmp4,uu);
    tmp4:=ConjugateByTori(tmp4,[2,4],[-1,-1,-1,-1]);

    return [tmp1,tmp3,tmp2,tmp4];
end;

#
# cls 4: E_6(a_3) for [1,8,9,11,14,19]
#
handle4char2:=function()
    local tmp1,tmp2,o,info,rep,cent,uu;
    o:=AllClasses(orbs)[4];
    info:=infos[4];

    rep:=Representative(o);
    tmp1:=ConjNegUa(rep,3,1);
    tmp1:=ConjNegUa(tmp1,2,1);
    uu:=Unipotent(chevalleyAdj(rep),[[5,1],[6,1],[7,1],[8,1],[13,1],[14,1],[19,1],[20,1],[25,1]],Ordering(rep));
    tmp1:=Conj(tmp1,uu);

    cent:=FromPositiveBorel(o,info[2]);
    tmp2:=ConjNegUa(cent,3,1);
    tmp2:=ConjNegUa(tmp2,2,1);
    uu:=Unipotent(chevalleyAdj(cent),[[5,1],[6,1],[7,1],[8,1],[13,1],[14,1],[19,1],[20,1],[25,1]],Ordering(cent));
    tmp2:=Conj(tmp2,uu);

    return [tmp1,tmp2];
end;
#
# THIS IS FOR THE E_6(a_3) class of E_6 in char 2
#
#vals:=ShallowCopy(avars);
#vals[3]:=0;
#vals[2]:=0;
#vals[9]:=vals[1];
#vals[5]:=One(APR);
#vals[4]:=0;
#vals[7]:=vals[1]+One(APR);
#vals[6]:=vals[1]+One(APR);
#vals[8]:=One(APR);
#vals[13]:=vals[7];
#vals[14]:=vals[1]+One(APR);
#vals[15]:=0;
#vals[11]:=vals[1];
#vals[18]:=vals[1]^2+vals[1]+vals[16];
#vals[19]:=One(APR);
#vals[20]:=vals[1]^2+vals[12]+One(APR);
#vals[21]:=vals[12];
#vals[22]:=vals[12];
#vals[24]:=vals[1]*vals[10]+vals[1]+vals[10]+vals[12]+vals[18];
#vals[25]:=vals[1]^2+vals[17]+One(APR);
#vals[27]:=vals[17];
#vals[28]:=vals[1]^2+vals[1]+vals[12]+vals[23];
#vals[29]:=vals[23];
#vals[31]:=vals[1]^2*vals[10]+vals[1]^2+vals[1]*vals[12]+vals[10]+vals[16]+vals[23]+vals[26];
#vals[33]:=vals[1]^4+vals[1]^3+vals[1]^2*vals[16]+vals[1]^2+vals[1]*vals[12]+vals[12]^2+vals[1]+vals[16]+vals[23]+vals[26];
#vals[34]:=vals[1]^4+vals[1]^3+vals[1]^2*vals[16]+vals[1]^2+vals[12]^2+vals[12]*vals[17]+vals[1]+vals[16]+vals[30]+vals[32];
#
#vv:=Value(generic,avars,vals);
#vvv:=Value(vv,[avars[1],avars[10],avars[12],avars[16],avars[17],avars[23],avars[26],avars[30],avars[32],avars[35],avars[36]],[0,0,0,0,0,0,0,0,0,0,0]);


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

#F IsSPLMat(<x>) - returns true for parametrized matrices (Gath, Scat, Diag, etc)
#F
IsSPLMat := x -> IsRec(x) and IsBound(x._sym) and x._sym;

# ==========================================================================
# BaseMat
# ==========================================================================
Class(BaseMat, ClassSPL, rec(
    _mat := true,
    _short_print := true,
    #-----------------------------------------------------------------------
    isTerminal := True,
    isPermutation := False,
    children   := self >> [],
    terminate  := self >> self,
    # -------- Transformation rules support ---------------------------------
    rChildren := self >> [self.element],
    rSetChild := rSetChildFields("element"),
));


#############################################################################
##
#W  id1600.gi              GAP library of id's             Hans Ulrich Besche
##

ID_GROUP_TREE.next[1600].next[191]:=
rec(
  fp:= [ 2336, 3359, 3566, 4409, 5734, 6716, 7350, 7386, 7764, 12688, 13496,
18471, 18945, 19034, 19184, 20975, 24764, 25529, 25988, 26794, 29528, 29556,
29778, 30192, 31174, 31808, 35666, 37146, 40641, 41354, 41854, 43403, 43492,
45638, 47699, 49222, 50446, 51252, 51491, 51726, 54236, 54650, 55154, 55632,
56266, 56479, 57836, 58509, 61604, 63524, 66312, 67808, 69869, 73661, 73680,
74904, 78694, 79612, 80006, 80937, 82967, 84877, 85694, 89978, 90770, 91762,
92039, 94237, 95962, 96178 ],
  level:= 4,
  next:= [ 6692, rec(
    fp:= [ 88, 51898, 80161, 87396 ],
    next:= [ rec(
      desc:= [ 112011 ],
      fp:= [ 16, 26, 818 ],
      next:= [ rec(
        desc:= [ 302004 ],
        fp:= [ 902, 1801 ],
        next:= [ 6564, rec(
          desc:= [ 209002 ],
          fp:= [ 8, 18 ],
          next:= [ 6665, 6594 ] ) ] ), 6592, rec(
        desc:= [ 108003 ],
        fp:= [ 8, 18, 414 ],
        next:= [ 6558, 6578, 6583 ] ) ] ), rec(
      desc:= [ 113011 ],
      fp:= [ 422, 818, 1214 ],
      next:= [ 6560, 6588, rec(
        desc:= [ 109003 ],
        fp:= [ 4, 14 ],
        next:= [ 6662, 6590 ] ) ] ), rec(
      desc:= [ 110003 ],
      fp:= [ 4, 14, 212 ],
      next:= [ rec(
        desc:= [ 104003, 302003, 302005, 208002 ],
        fp:= [ 2, 12 ],
        next:= [ 6566, 6661 ] ), 6579, 6582 ] ), rec(
      desc:= [ 113011 ],
      fp:= [ 422, 1214 ],
      next:= [ 6584, rec(
        desc:= [ 109003 ],
        fp:= [ 4, 14 ],
        next:= [ 6562, 6591 ] ) ] ) ] ), rec(
    desc:= [ 110003 ],
    fp:= [ 4, 14, 212 ],
    next:= [ 6777, 6776, 6775 ] ), 6679, rec(
    desc:= [ 111003 ],
    fp:= [ 16, 214, 412 ],
    next:= [ 6506, 6511, 6505 ] ), rec(
    fp:= [ 67467, 86989 ],
    next:= [ 5699, 5703 ] ), 6678, rec(
    fp:= [ 28383, 59114 ],
    next:= [ 5677, 5674 ] ), rec(
    fp:= [ 68372, 89029 ],
    next:= [ 6501, 6500 ] ), 6698, rec(
    fp:= [ 22326, 34820, 39910, 49358 ],
    next:= [ 6742, rec(
      desc:= [ 104003 ],
      fp:= [ 4, 14 ],
      next:= [ rec(
        desc:= [ 110003 ],
        fp:= [ 4, 14 ],
        next:= [ 6712, 6717 ] ), 6740 ] ), 6720, 6741 ] ), rec(
    fp:= [ 11506, 97869 ],
    next:= [ 5678, 5681 ] ), rec(
    fp:= [ 30872, 33731 ],
    next:= [ rec(
      desc:= [ 112003 ],
      fp:= [ 2, 12 ],
      next:= [ 5696, 5697 ] ), rec(
      desc:= [ 112003 ],
      fp:= [ 2, 12 ],
      next:= [ 5695, 5694 ] ) ] ), rec(
    fp:= [ 16813, 89614 ],
    next:= [ 6517, 6520 ] ), rec(
    fp:= [ 17432, 76439, 81210, 89426 ],
    next:= [ 6581, 6663, 6664, rec(
      desc:= [ 302014 ],
      fp:= [ 63740, 75240 ],
      next:= [ 6587, rec(
        desc:= [ 107003 ],
        fp:= [ 4, 14 ],
        next:= [ 6565, 6660 ] ) ] ) ] ), rec(
    desc:= [ 110003 ],
    fp:= [ 4, 14, 212 ],
    next:= [ 5684, 5686, 5688 ] ), 6688, rec(
    fp:= [ 5113, 11824, 19609, 48597, 52539, 80636, 83510 ],
    next:= [ rec(
      desc:= [ 111003 ],
      fp:= [ 4, 14, 212 ],
      next:= [ 6546, rec(
        desc:= [ 302004, 302007, 209002 ],
        fp:= [ 2, 12 ],
        next:= [ 6643, 6537 ] ), 6541 ] ), 6542, rec(
      desc:= [ 302005 ],
      fp:= [ 1401, 50901 ],
      next:= [ 6656, rec(
        desc:= [ 117011 ],
        fp:= [ 12, 418 ],
        next:= [ 6551, 6559 ] ) ] ), 6648, rec(
      desc:= [ 110003 ],
      fp:= [ 4, 14, 212 ],
      next:= [ 6552, rec(
        desc:= [ 115011 ],
        fp:= [ 12, 418 ],
        next:= [ 6646, 6585 ] ), 6547 ] ), 6649, 6667 ] ), 6696, 6689, rec(
    desc:= [ 111003 ],
    fp:= [ 4, 14, 212 ],
    next:= [ 6773, 6774, 6772 ] ), rec(
    fp:= [ 84836, 93852 ],
    next:= [ 5676, 5673 ] ), 6674, 6507, rec(
    fp:= [ 13332, 75068 ],
    next:= [ 5700, 5701 ] ), 6675, rec(
    fp:= [ 1266, 7632, 43357, 53685, 70165 ],
    next:= [ 6707, 6739, rec(
      desc:= [ 105003 ],
      fp:= [ 4, 14 ],
      next:= [ rec(
        desc:= [ 111003 ],
        fp:= [ 4, 14 ],
        next:= [ 6709, 6705 ] ), 6737 ] ), 6736, 6738 ] ), rec(
    fp:= [ 4584, 59123 ],
    next:= [ 6699, 6697 ] ), rec(
    fp:= [ 13651, 83336 ],
    next:= [ 5682, 5679 ] ), rec(
    fp:= [ 22006, 43211, 47769, 88448, 91910 ],
    next:= [ 6652, 6651, 6540, rec(
      desc:= [ 116019 ],
      fp:= [ 22, 814 ],
      next:= [ 6642, rec(
        desc:= [ 111003 ],
        fp:= [ 4, 14 ],
        next:= [ 6545, 6549 ] ) ] ), 6640 ] ), 6685, rec(
    fp:= [ 64224, 84969 ],
    next:= [ rec(
      desc:= [ 112003 ],
      fp:= [ 2, 12 ],
      next:= [ 5693, 5692 ] ), rec(
      desc:= [ 112003 ],
      fp:= [ 2, 12 ],
      next:= [ 5690, 5691 ] ) ] ), rec(
    fp:= [ 17115, 54122 ],
    next:= [ 6516, 6515 ] ), 5745, rec(
    fp:= [ 8742, 17698, 37621, 37725, 63931, 68715, 85748 ],
    next:= [ 6644, 6645, 6647, 6666, 6543, 6650, rec(
      desc:= [ 109003 ],
      fp:= [ 8, 18, 414 ],
      next:= [ rec(
        desc:= [ 114011 ],
        fp:= [ 8, 18 ],
        next:= [ 6538, 6550 ] ), 6653, 6655 ] ) ] ), rec(
    fp:= [ 1021, 63186 ],
    next:= [ 6686, 6694 ] ), 6695, 6691, 6744, 5672, rec(
    fp:= [ 15375, 94683 ],
    next:= [ 6673, 6680 ] ), 6508, 6682, rec(
    fp:= [ 22266, 63767 ],
    next:= [ 5698, 5702 ] ), 6677, 6509, rec(
    fp:= [ 32, 31059, 34820, 55974 ],
    next:= [ 6733, 6734, 6735, 6732 ] ), rec(
    fp:= [ 34908, 75785 ],
    next:= [ 6503, 6504 ] ), 6690, rec(
    fp:= [ 10903, 58462, 58539, 89359 ],
    next:= [ 6633, 6629, 6641, 6634 ] ), rec(
    fp:= [ 1754, 3480 ],
    next:= [ 6683, 6684 ] ), 5743, rec(
    fp:= [ 2041, 8592, 13451 ],
    next:= [ 6632, 6654, 6631 ] ), 6743, 6687, 6693, 6672, 6681, 6731,
6510, rec(
    fp:= [ 1797, 27346 ],
    next:= [ 6499, 6502 ] ), 5675, 6630, 5744, 6676, rec(
    fp:= [ 70200, 89024 ],
    next:= [ 6752, rec(
      desc:= [ 109003 ],
      fp:= [ 6, 16, 214, 412 ],
      next:= [ 6751, 6750, 6749, 6748 ] ) ] ), 6635, rec(
    fp:= [ 43650, 55647 ],
    next:= [ 6518, 6519 ] ), rec(
    fp:= [ 86473, 98981 ],
    next:= [ 5680, 5683 ] ), rec(
    desc:= [ 110003 ],
    fp:= [ 4, 14, 212 ],
    next:= [ 5687, 5685, 5689 ] ) ] );


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

########################################################################
#   (A + B) rules
NewRulesFor(TDirectSum, rec(
#   (A + B) terminate
    A_dirsum_B_delayed := rec(
        forTransposition := false,
        children := t -> let(tags := t.getTags(),
            [[ t.params[1].withTags(tags), t.params[2].withTags(tags) ]]
        ),
#D        children := (self, t) >> let (tags:=GetTags(t),
#D            [[ AddTag(t.params[1], tags), AddTag(t.params[2], tags) ]]),
        apply := (t, C, Nonterms) -> DelayedDirectSum(C)
    )
));