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

Class(ScriptGenAVX, ScriptGenBase, rec(

	_arch := () -> "AVX",

	_init := meth(self)
		self._setTransform(SGKEY_FFT);
		self._setType(SGKEY_SPCX);
		self._setSize(64);
		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
			if type = SGKEY_DPCX then
				szs := Filtered([1..1024], i->ForAll(Factors(i), j->j<=16) and IsInt(i/16));
				Append(szs, List([11..16], i->2^i));
			else
				szs := Filtered([1..1024], i->ForAll(Factors(i), j->j<=16) and IsInt(i/64));
				Append(szs, List([11..16], i->2^i));
			fi;
		elif xform in [SGKEY_FFT_2D, SGKEY_IFFT_2D] then
			if type = SGKEY_DPCX then
				szs := List([4..8], i -> [2^i, 2^i]);
			else
				szs := List([6..9], i -> [2^i, 2^i]);
			fi;
		elif xform = SGKEY_WHT then
			if type = SGKEY_DPCX then
				szs := List([4..8], i->2^i);
			else
				szs := List([6..8], 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, vec2;

		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    := "AVX_8x32f";
			vec2   := 64;
		else
			isa    := "AVX_4x64f";
			vec2   := 16;
		fi;

		use_cx := ForAny(szs, s -> not IsInt(s / vec2));

		if xform in [SGKEY_FFT, SGKEY_IFFT] then
			optrec := rec(
				globalUnrolling    := 128,
				tsplRader          := false,
				tsplBluestein      := false,
				tsplPFA            := false,
				oddSizes           := false,
				interleavedComplex := true,
				cplxVect           := use_cx,
				realVect           := not use_cx,
			);
			if use_cx then
				optrec.RDFT  := false;
            	optrec.URDFT := true;
				if (type = SGKEY_SPCX) then
					optrec.CT      := false;
					optrec.PD      := false;
					optrec.svct    := true;
					optrec.flipIxA := true;
				elif (type = SGKEY_DPCX) then
			        optrec.svct   := false;
					optrec.splitL := 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(
				verify   := true,
				oddSizes := false,
				svct     := true
			);
			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,

	_localSupport := function()
		return (IsBound(LocalConfig) and IsBound(LocalConfig.cpuinfo) and IsBound(LocalConfig.cpuinfo.SIMD().hasAVX) and
			LocalConfig.cpuinfo.SIMD().hasAVX());
	end,

)); # Class ScriptGenAVX

SetScriptGenConstructor(ScriptGenAVX);


# This is a rather basic helper function to do
# completion. It is related to the completion
# function provided in lib/cmdledit.g in the GAP
# distribution
InstallGlobalFunction(JUPYTER_Complete,
function(code, cursor_pos)
    local default, cand, i, matches, tokens, tok;

    default := rec( matches := [], cursor_start := 0,
                    cursor_end := cursor_pos, metadata := rec(),
                    status := "ok" );

    code := code{[1..cursor_pos]};
    if Length(code) = 0 then
        return default;
    fi;
    tokens := SplitString(code, "():=<>,.[]?-+*/; ");

    if tokens = [] then
        return default;
    fi;

    tok := tokens[Length(tokens)];
    cand := IDENTS_BOUND_GVARS();
    matches := Filtered(cand, i -> PositionSublist(i, tok) = 1);
    SortBy(matches, Length);
    return rec( matches := matches
              , cursor_start := cursor_pos - Length(tok)
              , cursor_end := cursor_pos
              , metadata := rec()
              , status := "ok" );
end);


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

#F PrintActiveRules(<non-terminal>)
#F PrintActiveRules(<non-terminal.name>)
#F PrintActiveRules([<rulelist>])
#F
#F Prints out all applicable rules for for the transform <non-terminal>
#F together with their switches.
#F Accepts a nonterminal spl, its symbol, or a rule list as an argument.
#F

PrintActiveRules := function (nonterm)

local rules, switches, i;

# Check if rules are already found
if IsList(nonterm) and not IsString(nonterm) then
  if ForAll(nonterm, IsRule) then rules:=nonterm;
  else Error("List must be a list of rules");
  fi;
else rules := AllRules(nonterm);
fi;

switches :=
  List([1..Length(rules)],
    function(i)
      if rules[i].switch = false then return "OFF";
      else return "ON";
      fi;
    end
  );
Print("\n");
Print("No. \t\b\bSwitch \tRule\n");
Print("--------------------------------------------\n");
for i in [1..Length(rules)] do
  Print(i,".\t",switches[i],"\t",rules[i],"\n");
od;
Print("\n");
end;

#F SwitchRulesOn( <non-terminal>, <ind> )
#F SwitchRulesOn( <non-terminal.name>, <ind> )
#F SwitchRulesOn( [<rulelist>], <ind> )
#F
#F Switches on the rules with indices <ind> for a given <non-terminal>
#F   <non-terminal> can be either a nonterminal spl or its symbol.
#F   <ind> is either a single index or a list of indices
#F
#F Hint: Use PrintActiveRules(<non-terminal>) to find out more about
#F       rules and their numbering.
#F

SwitchRulesOn := function ( nonterm , index )
local i, L;

# Check if rules are already found
if IsList(nonterm) and not IsString(nonterm) then
  if ForAll(nonterm, IsRule) then L := nonterm;
  else Error("List must be a list of rules");
  fi;
else L := AllRules(nonterm);
fi;

if not IsList(index) then index := [index];fi;
if ForAny(index, k-> not (IsInt(k) and k <= Length(L) and k > 0)) then
     Error("Index to the rule list must be an integer in the proper range");
fi;
for i in index do
  L[i].switch:=true;
od;

PrintActiveRules(L);
end;

Declare(SwitchRulesQuiet, SwitchRulesByNameQuiet);

#F SwitchRules( <non-terminal>, <ind> )
#F SwitchRules( <non-terminal.name>, <ind> )
#F SwitchRules( [<rulelist>], <ind> )
#F
#F Switches on the rules with indices <ind> for a given <non-terminal>
#F   and off all the other rules for the same nonterm
#F   <non-terminal> can be either a nonterminal spl or its symbol.
#F   <ind> is either a single index or a list of indices
#F
#F Hint: Use PrintActiveRules(<non-terminal>) to find out more about
#F       rules and their numbering.
#F
SwitchRules := function ( nonterm , index )
    SwitchRulesQuiet(nonterm, index);
    PrintActiveRules(nonterm);
end;

SwitchRulesQuiet := function ( nonterm , index )
local i, L;

# Check if rules are already found
if IsList(nonterm) and not IsString(nonterm) then
  if ForAll(nonterm, IsRule) then L := nonterm;
  else Error("List must be a list of rules");
  fi;
else L := AllRules(nonterm);
fi;

if not IsList(index) then index := [index];fi;
if ForAny(index, k-> not (IsInt(k) and k <= Length(L) and k > 0)) then
     Error("Index to the rule list must be an integer in the proper range");
fi;

for i in [1..Length(L)] do
  L[i].switch:=false;
od;

for i in index do
  L[i].switch:=true;
od;
end;

SwitchRulesByName := function ( nonterm , rules )
    SwitchRulesByNameQuiet(nonterm, rules);
    PrintActiveRules(nonterm);
end;

SwitchRulesByNameQuiet := function ( nonterm , rules )
    local r, L;
    if not IsNonTerminal(nonterm) then Error("<nonterm> must be a non-terminal");
    elif not (IsList(rules) and ForAll(rules, IsRule)) then Error("<rules> must be a list of rules");
    fi;
    SwitchRulesQuiet(nonterm, []);
    for r in rules do
        r.switch:=true;
    od;
end;

#F SwitchRulesOff( <non-terminal>, <ind> )
#F SwitchRulesOff( <non-terminal.name>, <ind> )
#F SwitchRulesOff( [<rulelist>], <ind> )
#F
#F Switches off the rules with indices <ind> for a given <non-terminal>
#F   <non-terminal> can be either a nonterminal spl or its symbol.
#F   <ind> is either a single index or a list of indices
#F
#F Hint: Use PrintActiveRules(<non-terminal>) to find out more about
#F       rules and their numbering.
#F

SwitchRulesOff := function ( nonterm , index )
local i, L;

# Check if rules are already found
if IsList(nonterm) and not IsString(nonterm) then
  if ForAll(nonterm, IsRule) then L := nonterm;
  else Error("List must be a list of rules");
  fi;
else L := AllRules(nonterm);
fi;

if not IsList(index) then index := [index]; fi;
if ForAny(index, k-> not (IsInt(k) and k <= Length(L) and k > 0)) then
     Error("Index to the rule list must be an integer in the proper range");
fi;
for i in index do
  L[i].switch:=false;
od;

PrintActiveRules(L);
end;

# VIENNA ADDED:
#F SwitchRulesName( [<rulelist>], true|false )
#F
#F Switches the rules on and off by names
#F   <rulelist> is a list of names of rules
#F
SwitchRulesName := function(l,s)
local i;
for i in l do i.switch:=s; od;
end;


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

# This is the code generator I use for generating HDL descriptions.  The
# output from this is sent to my back-end.

Class(HDLCodegen, Codegen, rec(
    initXY := function(x, y, opts)
        if IsBound(opts.XType) and not IsList(x) then
            if not IsArrayT(opts.XType) and not IsPtrT(opts.XType)
                then Error("opts.XType must be a pointer or array type. This has recently changed.",
                           "If you used TReal before, use TPtr(TReal) now"); fi;
            x.t := opts.XType;
            x.t := When(IsBound(opts.useRestrict) and opts.useRestrict, x.t.restrict(), x.t);
        fi;
        if IsBound(opts.YType) and not IsList(y) then
            if not IsArrayT(opts.YType) and not IsPtrT(opts.YType)
                then Error("opts.YType must be a pointer or array type. This has recently changed.",
                           "If you used TReal before, use TPtr(TReal) now"); fi;
            y.t := opts.YType;
            y.t := When(IsBound(opts.useRestrict) and opts.useRestrict, y.t.restrict(), y.t);
        fi;
        return [x, y];
    end,

    Formula := meth(self, o, y, x, opts)

        local code, datas, prog, params, sub, initsub;

	[x, y] := self.initXY(x,y,opts);

	o := o.child(1);
	params := Set(Collect(o, param));

	datas := Collect(o, FDataOfs);
	o := BlockSums(opts.globalUnrolling, o);
	code := self(o, y, x, opts);
    code := ESReduce(code, opts);
	code := RemoveAssignAcc(code);
	code := BlockUnroll(code, opts);
        # code := PowerOpt(code);
	code := DeclareHidden(code);
	if IsBound(opts.isFixedPoint) and opts.isFixedPoint then
	    code := FixedPointCode(code, opts.bits, opts.fracbits);
	fi;

	sub := Cond(IsBound(opts.subName), opts.subName, "transform");
	initsub := Cond(IsBound(opts.subName), Concat("init_", opts.subName), "init");
#	code := func(TVoid, sub, Concatenation(params, [y, x]), code);
#	code :=

#	if IsBound(opts.generateInitFunc) and opts.generateInitFunc then
#	    prog := program(
#		decl(List(datas, x->x.var),
#		    chain(
#			func(TVoid, initsub, [], chain(List(datas, x -> SReduce(x.var.init, opts)))),
#			code
#		    )));
#	else
#	    prog := program(code);
#	fi;
	prog := code;
	prog.dimensions := o.dimensions;
	return prog;
    end,

#    BB := (self,o,y,x,opts) >> MarkForUnrolling(self(o.child(1), y, x, opts)),
    BB := (self,o,y,x,opts) >> MarkForUnrolling(
        When(IsBound(o.bbnum),
            o.bbnum,
            0
        ),
        self(o.child(1), y, x, opts)
    ),

    Buf := (self,o,y,x,opts) >> self(o.child(1), y, x, opts),
    Grp := (self,o,y,x,opts) >> self(o.child(1), y, x, opts),

    COND := (self,o,y,x,opts) >> IF(
        When(IsFunction(o.cond), o.cond.at(0), o.cond),
        self(o.child(1), y, x, opts), self(o.child(2), y, x, opts)),

#    COND := (self,o,y,x,opts) >> IF(o.cond, self(o.child(1), y, x, opts), self(o.child(2), y, x, opts)),

    Diag := (self, o, y, x, opts) >> let(i := Ind(), elt := o.element.lambda(),
	loop(i, elt.domain(), assign(nth(y,i), elt.at(i) * nth(x,i)))),

    RCDiag := (self, o, y, x, opts) >> let(i := Ind(), elt := o.element.lambda(),
	re := elt.at(2*i), im := elt.at(2*i+1),
	loop(i, elt.domain()/2, chain(
		assign(nth(y,2*i),   re * nth(x,2*i) - im * nth(x,2*i+1)),
		assign(nth(y,2*i+1), im * nth(x,2*i) + re * nth(x,2*i+1))))),

    ColVec := (self, o, y, x, opts) >> let(i := Ind(), func := o.element.lambda(),
	loop(i, func.domain(), assign(nth(y,i), mul(func.at(i), nth(x,0))))),

    RowVec := (self, o, y, x, opts) >> let(i := Ind(), func := o.element.lambda(),
	t := TempVar(x.t.t),
	chain(assign(t,0),
	    loop(i, func.domain(), assign(t, add(t, mul(func.at(i), nth(x,i))))),
	    assign(nth(y,0), t))),

    Scale := (self, o, y, x, opts) >> let(i := Ind(),
	chain(self(o.child(1), y, x, opts),
	    loop(i, Rows(o), assign(nth(y,i), mul(o.scalar, nth(y,i)))))),

    I := (self, o, y, x, opts) >> let(i := Ind(Rows(o)),
	loop(i, i.range, assign(nth(y,i), nth(x,i)))),

    Blk := (self, o, y, x, opts) >> Cond(
	Rows(o)=2 and Cols(o)=2, Blk2code(o, y, x),
        Rows(o)=4 and Cols(o)=4, Blk4code(o, y, x),
	let(j:=Ind(), i:=Ind(), t:=TempVar(x.t.t), mat:=V(o.element), d:=Dat(mat.t),
	    data(d, mat,
		loop(j, Rows(o), decl(t, chain(
		     assign(t, 0),
		     loop(i, Cols(o), assign(t, t + nth(nth(d,j),i) * nth(x,i))),
		     assign(nth(y,j), t))))))),

    toeplitz := (self, o, y, x, opts) >> self.Blk(o.obj, y, x, opts),

    Blk1 := (self, o, y, x, opts) >> assign(nth(y,0), mul(toExpArg(o.element), nth(x,0))),

    BlkConj := (self, o, y, x, opts) >> assign(nth(y,0), conj(nth(x,0))),

    Prm := (self, o, y, x, opts) >> let(i:=Ind(), func:=o.func.lambda(),
	loop(i, Rows(o), assign(nth(y, i), nth(x, func.at(i))))),

    L := meth(self, o, y, x, opts)
        return let(i:=Ind(), func:=o.lambda(),
            loop(i, Rows(o), assign(nth(y, i), nth(x, func.at(i)))).unroll());
    end,

    O := (self, o, y, x, opts) >> let(i:=Ind(),
	loop(i, o.params[1], assign(nth(y, i), V(0)))),

    DirectSum := (self,o,y,x,opts) >> self(o.sums(), y, x, opts),
    Tensor := (self,o,y,x,opts) >> self(o.sums(), y, x, opts),
    IDirSum := (self,o,y,x,opts) >> self(o.sums(), y, x, opts),

    Gath := meth(self, o, y, x, opts)
        local i, func, rfunc, ix;
	i := Ind(); func := o.func.lambda();

	if IsBound(o.func.rlambda) then
	    rfunc := o.func.rlambda();
	    ix := var.fresh_t("ix", TInt);
	    return decl(ix, chain(
		assign(ix, func.at(0)),
		assign(nth(y, 0), nth(x, ix)),
		loop(i, o.func.domain()-1,
		    chain(assign(ix, rfunc.at(ix)),
			assign(nth(y, i+1), nth(x, ix))))));
	else
	    return loop(i, o.func.domain(), assign(nth(y,i), nth(x, func.at(i))));
	fi;
    end,

    Scat := meth(self, o, y, x, opts)
        local i, func, rfunc, ix;
	i := Ind(); func := o.func.lambda();
	if IsBound(o.func.rlambda) then
	    rfunc := o.func.rlambda();
	    ix := var.fresh_t("ix", TInt);
	    return decl(ix, chain(
		assign(ix, func.at(0)),
		assign(nth(y, ix), nth(x, 0)),
		loop(i, o.func.domain()-1,
		    chain(assign(ix, rfunc.at(ix)),
			assign(nth(y, ix), nth(x, i+1))))));
	else
	    return loop(i, o.func.domain(), assign(nth(y,func.at(i)), nth(x, i)));
	fi;
    end,

    SUM := (self, o, y, x, opts) >> chain(List(o.children(), c -> self(c, y, x, opts))),

    ISum := (self, o, y, x, opts) >> let(myloop := When(IsSymbolic(o.domain), loopn, loop),
    myloop(o.var, o.domain,
        self(o.child(1), y, x, opts))),

    # NOTE: get rid of _acc
    SUMAcc := (self, o, y, x, opts) >> let(ii := Ind(),
       When(not Same(ObjId(o.child(1)), Gath),  # NOTE: come up with a general condition
       chain(
           loop(ii, Rows(o), assign(nth(y, ii), V(0))),
           List(o.children(), c -> _acc(self(c, y, x, opts), y))),
       chain(
           self(o.child(1), y, x, opts),
           List(Drop(o.children(), 1), c -> _acc(self(c, y, x, opts), y))))),

    ISumAcc := (self, o, y, x, opts) >> let(ii := Ind(), chain(
    loop(ii, Rows(o), assign(nth(y, ii), V(0))),
    loop(o.var, o.domain, _acc(self(o.child(1), y, x, opts), y)))),

#     Compose := meth(self, o,y,x,opts)
#         local ch, numch, vecs, allow, i;
# #   if IsBound(opts._inplace) and opts._inplace then
# #       return chain(
# #       List(Reversed(o.children()), c -> self(c, x, x, opts))); fi;
#         #VIENNA filtering DMPGath, DMPScat out here because they do not generate code
#     ch := Filtered(o.children(), i-> not i.name in ["DMPGath","DMPScat"]);
#     numch := Length(ch);
#     vecs := [y];
#     allow := (x<>y);
#     for i in [1..numch-1] do
#             if allow and ObjId(ch[i])=Inplace then vecs[i+1] := vecs[i];
#         else vecs[i+1] := TempVec(TArray(TempArrayType(y, x), Cols(ch[i])));
#         fi;
#     od;
#     vecs[numch+1] := x;
#     for i in Reversed([1..numch]) do
#             if allow and ObjId(ch[i])=Inplace
#         then vecs[i] := vecs[i+1]; fi;
#     od;

#         # everything was inplace, make it go from x -> y as expected
#     if vecs[1] = vecs[numch+1] then vecs[1] := y; fi;
#     if vecs[1] = vecs[numch+1] then vecs[numch+1] := x; fi;

#     [vecs, ch] := [Reversed(vecs), Reversed(ch)];
#     return decl( Difference(vecs{[2..Length(vecs)-1]}, [x,y]),
#         chain( List([1..numch], i -> When(vecs[i+1]=vecs[i],
#             self(ch[i], vecs[i],   vecs[i], CopyFields(opts, rec(_inplace:=true))),
#             self(ch[i], vecs[i+1], vecs[i], opts)))));
#     end,

    Compose := meth(self, o,y,x,opts)
        local ch, numch, vecs, i, cmd, code;
        ch    := o.children();
        numch := Length(ch);
        vecs  := [y];

        # we like to evaluate right (last) to left (first), where the values
        # in parens refer to the position of the elements in the array.
        #
        # So here, we start at the output (first entry in array) and
        # walk towards the input, creating temporary arrays as necessary
        for i in [1..numch-1] do
            vecs[i+1] := When(ObjId(ch[i]) = Inplace,
                vecs[i],
                TempArray(y,x,ch[i])
            );
        od;

        # the last entry must be the input.
        vecs[numch+1] := x;

        # now we walk in the opposite direction, copying through
        # input as far as we can.
        for i in Reversed([1..numch]) do
            if ObjId(ch[i]) = Inplace then
                vecs[i] := vecs[i+1];
            fi;
        od;

        # If all children were evaluated inplace, the output will be in x
        # Make it go from x -> y as expected
        if vecs[1] = vecs[numch+1] then vecs[1] := y; fi;
        if vecs[1] = vecs[numch+1] then vecs[numch+1] := x; fi;

        # order them so that first to be evaluated is first in array.
        vecs := Reversed(vecs);
        ch   := Reversed(ch);

        # Wrap code in variable declaration. Each entry in vecs will contain multiple
        # arrays in the case of multi-input/output (i.e. OL)
        return decl(
            Difference(Flat(vecs{[2..Length(vecs)-1]}), Flat([x,y])),
            chain(
                List([1..numch], i ->
                     self(ch[i], vecs[i+1], vecs[i], opts)))
        );
    end,

    Inplace := (self, o, y, x, opts) >>
#        self(o.child(1), x, x, CopyFields(opts, rec(_inplace:=true))),
        self(o.child(1), y, x, opts), # Compose will handle these somehow

    Data := meth(self, o, y, x, opts)
        local val;
        o.var.isData := true;
        val := When(IsFunction(o.value), o.value.tolist(), o.value);
        val := When(IsValue(val), val, o.var.t.value(val));
        return data(o.var, val, self(o.child(1), y, x, opts));
    end,

#     Data := (self, o, y, x, opts) >> decl(o.var, chain(
# 	    assign(o.var, o.value.tolist()),
# 	    self(o.child(1), y, x, opts))
#     ),

    #   NOTE: use pointers to do pingpong?
    ICompose := (self, o, y, x, opts) >>
        chain([let(
            t :=  Dat1d(x.t.t, Rows(o)),
            newind := Ind(o.domain),
            decl([t], chain(
            When(IsOddInt(o.domain),
            SubstVars(Copy(self(o.child(1), y, x, opts)), tab((o.var.id) := (V(0)))),
                skip()
            ),
            When(IsEvenInt(o.domain),
                chain(
                    SubstVars(Copy(self(o.child(1), t, x, opts)), tab((o.var.id) := (V(0)))),
                    SubstVars(Copy(self(o.child(1), y, t, opts)), tab((o.var.id) := (V(1))))
                ),
                skip()
            ),
            loop(newind, (o.domain/2),
                chain([
                    SubstVars(Copy(self(o.child(1), x, y, opts)), tab((o.var.id) := (2*newind)+2)),
                    SubstVars(Copy(self(o.child(1), y, x, opts)), tab((o.var.id) := (2*newind)+3))
                ])
            )
            )))])
));


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

Class(_ExpandInvars, RuleSet);
RewriteRules(_ExpandInvars, rec(
    shift_depends_left_in_add := ARule(add, [@(1).cond(e-> ObjId(e)<>depends), @(2,depends)],
        e->[@(2).val, @(1).val]),

    shift_depends_left_in_mul := ARule(mul, [@(1).cond(e-> ObjId(e)<>depends), @(2,depends)],
        e->[@(2).val, @(1).val]),

    fuse_depends_in_add := ARule(add, [@(1,depends), @(2,depends)],
        e->[depends(add(@(1).val.args[1], @(2).val.args[1]), Union(@(1).val.args[2].v, @(2).val.args[2].v))]),

    fuse_depends_in_mul := ARule(mul, [@(1,depends), @(2,depends)],
        e->[depends(mul(@(1).val.args[1], @(2).val.args[1]), Union(@(1).val.args[2].v, @(2).val.args[2].v))]),

    leq_depends := Rule([leq, @(1, depends), @(2, depends), @(3, depends)],
        e->depends(leq(@(1).val.args[1], @(2).val.args[1], @(3).val.args[1]),
                Union(@(1).val.args[2].v, @(2).val.args[2].v, @(3).val.args[2].v))),

    imod_depends := Rule([imod, @(1, depends), @(2, depends)],
        e-> depends(imod(@(1).val.args[1],@(2).val.args[1]), Union(@(1).val.args[2].v, @(2).val.args[2].v))),

    extract_depends := Rule([@(0, [add,mul]), @(1,depends)],
        e-> @(1).val),

    div_depends := Rule([div, @(1, depends), @(2, depends)],
        e-> depends(div(@(1).val.args[1],@(2).val.args[1]), Union(@(1).val.args[2].v, @(2).val.args[2].v))),

    idiv_depends := Rule([idiv, @(1, depends), @(2, depends)],
        e-> depends(idiv(@(1).val.args[1],@(2).val.args[1]), Union(@(1).val.args[2].v, @(2).val.args[2].v))),

    tcast_depends := Rule([tcast, @(1), @(2, depends)],
        e -> depends(tcast(@(1).val,@(2).val.args[1]), @(2).val.args[2])),

    add_assoc := RulesStrengthReduce.rules.add_assoc,

    mul_assoc := RulesExpensiveStrengthReduce.rules.mul_assoc,

    depends_deref := Rule( [deref, @(1,depends)],   #One needs to introduce depends memory to handle loads and stores differently
        e -> depends_memory(deref(@(1).val.args[1]), @(1).val.args[2])),

    nth_depends := Rule([nth, @(1, depends), @(2, depends)],
        e -> depends_memory(nth(@(1).val.args[1],@(2).val.args[1]),  Union(@(1).val.args[2].v, @(2).val.args[2].v))),

    vdup_depends_memory := Rule([vdup, @(1,depends_memory), @(2)],
        e -> depends_memory(vdup(@(1).val.args[1], @(2).val), @(1).val.args[2])),

    assign_load_depends := Rule([assign, @(1), @(2, depends_memory)], #This is a load, good invariant!
        e -> assign(@(1).val, depends(@(2).val.args[1], @(2).val.args[2]))),

    mul_depends_accu := ARule(mul, [@(1,depends, e->Length(e.args[2].v)=0), @(2,accu)],
        e -> [accu(mul(@(1).val, @(2).val.rChildren()[1]),
            mul(@(1).val, @(2).val.rChildren()[2]),
            mul(@(1).val, @(2).val.rChildren()[3]))]),

    add_depends_accu := ARule(add, [@(1,depends, e->Length(e.args[2].v)=0), @(2,accu)],
        e -> [accu(add(@(1).val, @(2).val.rChildren()[1]),
            @(2).val.rChildren()[2],
            add(@(1).val, @(2).val.rChildren()[3]))]),

    div_depends_accu := Rule([div, @(1, accu), @(2,depends, e->Length(e.args[2].v)=0)],
        e -> accu(div(@(1).val.rChildren()[1], @(2).val),
            div(@(1).val.rChildren()[2], @(2).val),
            div(@(1).val.rChildren()[3], @(2).val))),

    accu_init := Rule( [accu, @(1,depends), @(2), @(3)], #The initialization step doesn't need to be hoisted
        e -> accu(@(1).val.args[1], @(2).val, @(3).val)),        #Since it is gonna be out of the loop

    #These are security checks
    depends_depends := Rule([depends, depends],
        e->Error("Your code is somehow aliased and SimplifyLoop will probably mess it up. Fix it first then come back!")),

    assign_load_depends := Rule([assign, depends_memory, @], #This is a store invariant! Too good to be true...
        e -> Error("A store invariant has been found which is unlikely to be correct")),
));

Class(_ExpandInvarsUnsafeXXX, RuleSet);
RewriteRules(_ExpandInvarsUnsafeXXX, rec(
    unsafe_memory_depends := Rule(@(1,depends_memory), e-> depends(@(1).val.args[1], @(1).val.args[2]))
));

_ExpandInvarsUnsafe := MergedRuleSet(_ExpandInvarsUnsafeXXX, _ExpandInvars);

Declare(_CreateVirtualVars);
_CreateVirtualVars := function(dep, str, csetable, opts)
    local idx, rem, n, a, v, l;
    if Length(dep.args[2].v)=0 then
        v := SReduce(dep.args[1], opts);
        if IsValue(v) or IsVar(v) or ObjId(v)=param then
            return [v, skip()];
        else
            if csetable<>false then
                l := csetable.cseLookup(v);
                if (l<>false) then
                    return [l, skip()];
                else
                    n := var.fresh_t(str, dep.t);
                    csetable.cseAdd(n, v);
                    return [n, assign(n, v)];
                fi;
            else
                n := var.fresh_t(str, dep.t);
                return [n, assign(n, v)];
            fi;
        fi;
    else
        idx := dep.args[2].v[1];
        rem := Drop(dep.args[2].v, 1);
        [n, a] := TransposedMat(List([1.. idx.range],
                i -> _CreateVirtualVars(depends(SubstVars(Copy(dep.args[1]), tab((idx.id) := V(i-1))), rem), str, csetable, opts)));
        return [virtual_var(n, idx), a];
    fi;
end;

_SimplifyLoop := function(c, to_be_unrolled, opts)
    local free_vars, invars, l, v, assign_accs_init, assign_accs_final, x, accu_init, accu_acc, _DisableAccumulatorBound,
          loop_var, accu_bound, virtual_free_vars, csetable, selectedaccus, allaccus, allincs, i, j, t;

    if not(ObjId(c) in [loop,loopn]) then
            Error("SimplifyLoop has to be called on a loop!");
    fi;

    #Make sure the loop is not simplified again later!
    #Note that this is *critical* : we do not do any dependence analysis and thus
    #it is assumed that all free variables are loop independent which is not the case
    #anymore after the loop has been processed
    c.simplified := true;

    #If the loop is not gonna be unrolled, we will convert it to an accumulator loop
    #That is, we will use a while() loop instead of a for loop and this will allow us
    # to manipulate the loop index aka do a loop with multiple indices that start at different
    #values and increments differently
    if not(to_be_unrolled) then
        c := When(ObjId(c)=loopn,
            SubstBottomUp(c, c.var, x->accu(V(0), V(1), V(c.range))),
            SubstBottomUp(c, c.var, x->accu(V(0), V(1), V(Last(c.range)+1))));
    fi;

    #All free variables, params and values are assumed to be pure loop invariants (depends on nothing)
    free_vars := Set(c.free());
    c := SubstBottomUp(c, @(1, [param, Value], e -> not (ObjId(e.t) = TSym)), x->depends(x, Set([])));
    c := SubstBottomUp(c, @(1,var, e-> e in free_vars ), x->depends(x, Set([])));

    #Propagation of these pure loop invariants allow us to propagate inside the loop accumulators and
    #Define them properly.
    #NOTE: we could have add accumulators that actually depend on internal unrolled loops (as explained later) but then
    #it would actually require as many accumulators as the internal loop range which would waste registers. Note that
    #it's just a feeling, it wasn't tested.

    #This is a bad HACK
    #depends_memory exists because of the DGEMM assign_accs, but it
    #also breaks the DFTs. So when there's no assign acc, we turn them off.
    c := When(Length(Collect(c, assign_acc))>0, _ExpandInvars(c), _ExpandInvarsUnsafe(c));

    #Loop indices that will be unrolled later are somehow loop invariant of the external loop without
    #being loop invariants of the internal loop. As we want to hoist these variables anyhow, we introduce them as depends(exp, [idx])
    virtual_free_vars := When(to_be_unrolled,
        Set(List(Collect(c.cmd, loop), l -> l.var)),
        Set(List(Collect(c.cmd, @@(0, loop,(e, cx) -> (IsBound(cx.unroll_cmd)) and (Length(cx.unroll_cmd)>0))), l -> l.var)));
    c := SubstBottomUp(c, @(1,var, e-> e in virtual_free_vars ), x->depends(x, Set([x])));

    #Propagation now extends dependent variables. Note that dependent variables do not mix with accu so this prevents
    #dependent accumulators
    #Again, the hack.
    c := When(Length(Collect(c, assign_acc))>0, _ExpandInvars(c), _ExpandInvarsUnsafe(c));

    #Kick out trivial invariants. e.g.: constant are always invariants, useless ones!
    c := SubstBottomUp(c, [depends, @(1, [var, Value, param, tcast]), @], x->@(1).val);

    #This rule speeds up processing. It is not necessary but it works well... usually
    #Invariants that do not comprise params are probably constants so forget them!
    c := SubstBottomUp(c, @(0, depends, e-> Length(Collect(e, param))=0), x->@(0).val.args[1]);

    #As scalar increments are essentially free, we merge accumulators that have the same
    #increment if the difference of their initial values is a scalar
    allaccus := Set(Collect(c, accu));
    allincs := Set(List(allaccus, x->x.args[2]));
    for i in allincs do
        selectedaccus := Filtered(allaccus, x-> x.args[2] = i);
        for j in DropLast(selectedaccus,1) do
            t := j.args[1]-Last(selectedaccus).args[1];
            if ObjId(t)=Value then
                j.substitute := add(Last(selectedaccus), t);
            fi;
        od;
    od;
    c := SubstBottomUp(c, @(1, accu, e ->IsBound(e.substitute)), e->e.substitute);

    #If we are to transform in a while loop, we only really need one loop counter
    #Therefore, we kick out all loop counters but one
    if not(to_be_unrolled) then
        x := true;
        _DisableAccumulatorBound := function(e)
            if x then
                x:=false;
                e.isLoopBound := true;
                return e;
            else
                return accu(e.rChildren()[1], e.rChildren()[2], V(0));
            fi;
        end;
        SubstBottomUp(c.cmd, accu, e -> _DisableAccumulatorBound(e));
    fi;

    #Create new variables for all invariants
    csetable := CSE.init();
    l := Set(Collect(c, depends));
    [v, invars] := let(a := List(l, x->_CreateVirtualVars(x, "ivr", csetable, opts)),
        When(Length(a)>0, TransposedMat(a), [[],[]]));
    c := SubstBottomUp(c, @(1, depends), e->v[Position(l,e)]);

    #Same for all assign_accs!
    l := Set(List(Collect(c, [assign_acc, depends_memory, @]), x->x.loc));
    [v, assign_accs_init] := let(a := List(l, x->_CreateVirtualVars(x, "acc", false, opts)),
        When(Length(a)>0, TransposedMat(a), [[],[]]));
    assign_accs_final := SubstTopDownNR(Copy(assign_accs_init), assign, x->assign(x.exp, x.loc));
    c := SubstBottomUp(c, @(1, depends_memory), e->v[Position(l,e)]);

    #If the loop is to be unrolled then we're done
    if to_be_unrolled then
        return chain(invars, assign_accs_init, c, assign_accs_final);
    else #if not, then we need to finish the transformation in a while loop

        #drop the loop itself, we will replace it by a doloop later
        c := c.cmd;

        #Replace all accus by new vars
        #retrieve the loop bound and set all bounds to V(0) so they can merge in the Set()
        accu_bound := Copy(Collect(c, @(1, accu, x->IsBound(x.isLoopBound)))[1]);
        [loop_var, accu_bound] := [accu(accu_bound.rChildren()[1], accu_bound.rChildren()[2], V(0)),
            accu_bound.rChildren()[3]];
        c := SubstBottomUp(c, [accu, @(1), @(2), @(3)], x->accu(@(1).val, @(2).val, V(0)));
        l := Set(Collect(c, accu));
        v := List(l, x->var.fresh_t("accu", x.t));
        accu_init := List([1..Length(l)], i->assign(v[i], l[i].rChildren()[1]));
        #Copy here prevents aliasing with previous
        accu_acc := List([1..Length(l)], i->assign_acc(Copy(v[i]), l[i].rChildren()[2]));

        loop_var := let(p:=FirstPosition(l, x->(x=loop_var)), v[p]);
        c := SubstBottomUp(c, @(1, accu), e->v[Position(l,e)]);

        #if we cross the unroll_cmd boundary, we need to flag the new variables as alive
        #so copypropagation doesn't mess with it later
        for x in Collect([assign_accs_init, invars, accu_init], assign) do x.loc.live_out := true ; od;

        c := chain(unroll_cmd(chain(invars, assign_accs_init, accu_init)),
             doloop(loop_var, accu_bound, chain(c, accu_acc)), unroll_cmd(chain(assign_accs_final)));
        return c;
    fi;
end;

SimplifyLoops := function(c, opts)
    c := FlattenCode0(c); #Flatten chains, keep unroll_cmds
    c := SReduce(c, opts); #Simplify index expressions

    c := SubstTopDown(c, @.cond( #Remove all aliases!
            e->IsRec(e) and IsBound(e.rChildren) and IsBound(e.from_rChildren)),
        e -> e.from_rChildren(List(e.rChildren(), Copy)));

    c := SubstTopDown(c, @(1, [loop,loopn], e->not(IsBound(e.simplified))),
        (e, cx) -> _SimplifyLoop(e, (IsBound(cx.unroll_cmd)) and (Length(cx.unroll_cmd)>0), opts));
    return c;
end;


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

Declare(requiredFreeVars);
Declare(buildMap);
Declare(collectLoopVars);
Declare(buildGlobalList);
Declare(compareMatrices);
Declare(doAllPromotes);
Declare(permToData);
Declare(PadLeft);
Declare(PadRight);
Declare(isContainedInComposeDistsG);
Declare(isContainedInComposeDistsS);

################################## CANDIDATES FOR REMOVAL -- BEGIN #########################3
Class(ProducOutputSDGR, RuleSet);
RewriteRules(ProducOutputSDGR, rec(

));

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

    vtensor_nopulldist := Rule([@(1, VTensor), @(2, NoPull_Dist)],
        e->NoPull_Dist(VTensor(@(2).val.children(), @(1).val.vlen))
    )
));

#F Removes NoPull_Dist
Class(RemoveNoPull_Dist, RuleSet);
RewriteRules(RemoveNoPull_Dist, rec(
    remove_sizedkernel := Rule(@(1, NoPull_Dist), e->@(1).val.child(1))
));
################################## CANDIDATES FOR REMOVAL -- END #########################3

#F To convert DistSum(A)*DistSum(B)*... -> DistContainer(Seq(A,B,..))
Class(RulesComposeDists, RuleSet);
RewriteRules(RulesComposeDists, rec(
    dist_to_seq := Rule(@(1, Compose, e->ForAll(e.children(), c->(ObjId(c)=DistSum or ObjId(c)=Comm_Cell))),
        e -> ComposeDists(@(1).val.children()) )
));

#F DistSum|*|DistSum -> DistSum
Class(RulesDistMerge, RuleSet);
RewriteRules(RulesDistMerge, rec(
    #NOTE: If these DistSums have ScatSend or GathRecv, this rule will do the wrong thing!
    # Assuming this rule will only match DMP type DFT algos.
    dist_merge := ARule(ComposeDists, [@(1,DistSum), @(2,DistSum)],
      e -> let(a := @(1).val, b := @(2).val,
          [ DistSum(a.P, a.var, a.domain, a.child(1) * b.child(1)) ])
      ),

    # Above rule produces DistSum(S * child1 * G * S * child2 * G).
    # This rule gets rid of the G * S *

    gathdist_scat_dist := ARule(Compose, [GathDist, ScatDist],
      e -> [])
));

#F Removes Buf() so other rules can work!
Class(RemoveBuf, RuleSet);
RewriteRules(RemoveBuf, rec(
    remove_buf := Rule(@(1, Buf), e->@(1).val.child(1))
));

#F Convert initial and final GathRecv/ScatSend to Dist (null)
Class(CellDFTBlockCyclicLayoutHack, RuleSet);
RewriteRules(CellDFTBlockCyclicLayoutHack, rec(

    GathRecvDataLayout := Rule([@(1, GathRecv), @(2, [H, fTensor, fCompose, fId]), ...],
        e->GathDist(@1.val.func.range(), @1.val.pkSize, @1.val.P, @1.val.i)),

    ScatSendDataLayout := Rule([@(1, ScatSend), @(2, [H, fTensor, fCompose, fId]), ...],
        e->ScatDist(@1.val.func.range(), @1.val.pkSize, @1.val.P, @1.val.i))

));

#F Blockcyclic layout hack
CellDFTBlockCyclicLayoutHackWrap := function(sums, opts)
    if not (IsBound(opts.doNotUseBlockCyclic) and opts.doNotUseBlockCyclic = true) then
      return(CellDFTBlockCyclicLayoutHack(sums));
    else
      return(sums);
    fi;
end;

# Terminate VRCs (VRC(Scat/Gath) -> Scat/Gath(pkSize*2..)
# (Removed VRCL, VRCR from this mix Tue 09 Sep 2008 09:55:36 PM EDT)
Class(CellVRCTerm, RuleSet);
RewriteRules(CellVRCTerm, rec(
    VRC_ScatDist_Term := Rule([@(1, [RC, VRC,VRCLR]), @(2, [ScatDist])],
    e->ScatDist(@(2).val.N, @(2).val.pkSize*2, @(2).val.P, @(2).val.i)),

    VRC_GathDist_Term := Rule([@(1, [RC, VRC,VRCLR]), @(2, [GathDist])],
    e->GathDist(@(2).val.N, @(2).val.pkSize*2, @(2).val.P, @(2).val.i)),

    VRC_ScatSend_Term := Rule([@(1, [RC, VRC,VRCLR]), @(2, [ScatSend])],
    e->ScatSend(@(2).val.func, @(2).val.pkSize*2, @(2).val.P, @(2).val.i)),

    VRC_GathRecv_Term := Rule([@(1, [RC, VRC,VRCLR]), @(2, [GathRecv])],
    e->GathRecv(@(2).val.func, @(2).val.pkSize*2, @(2).val.P, @(2).val.i)),

    VRC_Comm_Cell := Rule([@(1,[RC, VRC,VRCLR]), @(2,Comm_Cell)],
    e->Comm_Cell(@(2).val.P, @(2).val.pkSize*2))

));

# VRC(DistSum.. -> DistSum(VRC...
RewriteRules(RulesVRC, rec(
    VRC_DistSum := Rule([@(1, [VRC,VRCL,VRCR,VRCLR]), @(2, [DistSum, DistSumLoop])],
       e->let(s := @(2).val,
       CopyFields(s, rec(_children := List(s.children(), c->ObjId(@(1).val)(c, @(1).val.v)),
                  dimensions := @(1).val.dimensions))
       ))
));

RewriteRules(RulesRC, rec(
    RC_DistSum := Rule([RC, @(1, DistSum)],
        e -> let(s:=@(1).val, DistSum(s.P, s.var, s.domain, RC(s.child(1))))),
));

# Pull Diag into DistSum (but not into Gath/Scat)
RewriteRules(RulesDiag, rec(
    CellPullInDiagRight := ARule(Compose,  [ @(1, [RCDiag, Diag, Prm, Scat]), @(2, DistSum) ],
     e -> let(s:=@(2).val, [ DistSum(s.P, s.var, s.domain, @(1).val * s.child(1)) ])),

    CellPullInDiagLeft := ARule(Compose, [ @(1, DistSum), @(2, [Prm, Gath, Diag, RCDiag]) ],
     e -> let(s:=@(1).val, [ DistSum(s.P, s.var, s.domain, s.child(1) * @(2).val) ])),

));

# Pull Diag into SAG (D*SAG -> SDAG and SAG*D -> SADG)
RewriteRules(RulesDiagStandalone, rec(
 # Gath * Diag
 CellCommuteGathDiag := ARule( Compose,
       [ @(1, [ GathDist, GathRecv ]), @(2, Diag) ], # o 1-> 2->
  e -> [ Diag(fCompose(@2.val.element, fTensor(@1.val.func, fId(@1.val.pkSize)))).attrs(@(2).val), @1.val ]),

 # Diag * Scat
 CellCommuteDiagScat := ARule( Compose,
       [ @(1, Diag), @(2, [ScatDist, ScatSend]) ], # <-1 <-2 o
  e -> [ @2.val, Diag(fCompose(@1.val.element, fTensor(@2.val.func, fId(@2.val.pkSize))  )).attrs(@(1).val) ]),

 # Gath * RCDiag
 CellCommuteGathRCDiag := ARule( Compose,
       [ [@(1, [ GathDist, GathRecv ]), [@(0,fTensor), ..., [fId,@(2).cond(IsEvenInt)]]],
      @(4, RCDiag) ],
  e -> [ RCDiag(fCompose(@(4).val.element, @(0).val), @(4).val.post),
         @(1).val ]),

 # RCDiag * Scat
 CellCommuteRCDiagScat := ARule( Compose,
       [ @(4, RCDiag),
     [@(1, [Scat, ScatDist, ScatSend]), [@(0,fTensor), ..., [fId,@(2).cond(IsEvenInt)]]] ],
  e -> [ @(1).val,
         RCDiag(fCompose(@(4).val.element, @(0).val), @(4).val.post) ]),

));

# Cell DMP: Fuse and remove PTensors that are next to Sigmas
Class(PTensorRules, RuleSet);
RewriteRules(PTensorRules, rec(
PTensorFuseRight := ARule(Compose, [ @(1,DistSum), @(2,PTensor) ],
    e -> [ DistSum( @(1).val.P, @(1).val.var, @(1).val.domain, Compose( @(1).val.child(1), @(2).val ) ) ] ),

PTensorFuseLeft := ARule(Compose, [  @(2,PTensor), @(1,DistSum) ],
    e -> [ DistSum( @(1).val.P, @(1).val.var, @(1).val.domain, Compose( @(2).val,  @(1).val.child(1) ) ) ] ),

PTensorFlipRight := ARule(Compose, [ @(1,GathDist), @(2,PTensor) ],
    e -> [ @(2).val.L, @(1).val ] ),

PTensorFlipLeft := ARule(Compose, [ @(2,PTensor), @(1,ScatDist) ],
    e -> [ @(1).val, @(2).val.L ] )
));

#i := Ind(P),
# Cell DMP: Convert standalone PTensors to Sigmas
Class(PTensorConvertRules, RuleSet);
RewriteRules(PTensorConvertRules, rec(
PTensorConvert := Rule( @(1,PTensor),
    e -> let(P := @(1).val.P,
             i := var("spuid", TInt, P),
             N := @(1).val.dims()[1],
            DistSum(P, i, P, ScatDist(N, 1, P, i) * @(1).val.L * GathDist(N, 1, P, i))
          )
    )
));

#F Gets all the DistSums under a single Compose so we can deal with them effectively
#F Mainly there because of VContainer
Class(DistSumChains, RuleSet);
RewriteRules(DistSumChains, rec(
    ChainDistSumsLeft := ARule(Compose,  [ @(1,DistSum), @(2,VContainer, e->ForAny(e.rChildren()[1].rChildren(), i->ObjId(i)=DistSum)) ],
     e -> let(ds:=@(1).val, vc:=@(2).val, [ VContainer(Compose(ds, vc.rChildren()), vc.isa)  ])),

    ChainDistSumsRight := ARule(Compose,  [ @(2,VContainer,  e->ForAny(e.rChildren()[1].rChildren(), i->ObjId(i)=DistSum)), @(1,DistSum) ],
     e -> let(ds:=@(1).val, vc:=@(2).val, [ VContainer(Compose(vc.rChildren(), ds), vc.isa)  ]))
));

#F Fuse output (SDGR*SS...) to just an SS
Class(FixBorder, RuleSet);
RewriteRules(FixBorder, rec(
    FixLeftBorder := Rule( [@(1,ComposeDists), [DistSum, ..., [Compose, ScatDist, GathRecv]], ...],
     e ->  let(c := @(1).val.rChildren(), ComposeDists(ListWithout(c,1))) )
));

#F Mark Cell Scat/Gaths as Inplace (ultimately, no-ops)
#F GDs, SDs, and GRs are no-ops for the Cell (SS becomes explicit DMA - SCATSEND)
applyCellInplace := function(sums, opts)
    SubstBottomUp(sums, GathDist, e->Inplace(e));
    SubstBottomUp(sums, GathRecv, e->Inplace(e));
    SubstBottomUp(sums, ScatDist, e->Inplace(e));

    # BB(Inplace()) -> Inplace() (So that a copy operation need not take place)
    SubstBottomUp(sums, [BB, Inplace], e->e.rChildren()[1]);
    return(sums);
end;

#NOTE: In the following padding functions, we previously produced pads with
#maximum packet size so that they merged nicely with multibuffered scats and
#gaths. But now, we don't do any merging between scats and gaths of the
#distributed and multibuffer paradigms. So it's better to get these padding
#functions to produce packet sizes that match the adjacent sigmaspl expressions.

#F This produces an SDGR on the left
PadLeft := function(e)
    local ss, N, pkSize, P,i, GR, SD;
    ss      := e.leftMostParScat();
    N       := ss.func.range();
    pkSize  := ss.pkSize;
    P       := ss.P;
    i       := var("spuid", TInt, P);
    GR      := GathRecv(fTensor(fBase(P, i), fId(N/P)), pkSize, P, i);
    SD      := ScatDist(N, pkSize, P, i);
    #SD     := ScatDist(P, (N*pkSize)/P, P, i); # Produce ScatDist with maximum packet size
    return(ComposeDists(DistSum(P, i, P, SD*GR),   e.rChildren()));
end;

#F This produces an SDGR on the left
PadLeftSep := function(e)
    local ss, N, pkSize, P,i, GR, SD;
    ss      := e.leftMostParScat();
    N       := ss.func.range();
    pkSize  := ss.pkSize;
    P       := ss.P;
    i       := var("spuid", TInt, P);
    GR      := GathRecv(fTensor(fBase(P, i), fId(N/P)), pkSize, P, i);
    SD      := ScatDist(N, pkSize, P, i);
    #SD     := ScatDist(P, (N*pkSize)/P, P, i); # Produce ScatDist with maximum packet size
    return(ComposeDists(DistSum(P, i, P, SD*GR), e));
end;

#F This produces a SSGD on the right
PadRight := function(e)
   local gr, N, P, i, pkSize, SS, GD, di;
   gr     := e.rightMostParGath();
   N      := gr.func.range();
   pkSize := gr.pkSize;
   P      := gr.P;
   i      := var("spuid", TInt, P);
   SS     := ScatSend(fAdd(N, N/P, i*(N/P)), pkSize, P, i);
   GD     := GathDist(N, pkSize, P, i);
   #GD     := GathDist(P, (N*pkSize)/P, P, i); # Produce GathDist with maximum packet size
   return(ComposeDists(e.rChildren(), DistSum(P, i, P, SS*GD)));
end;

#F This produces a SSGD on the right
PadRightSep := function(e)
   local gr, N, P, i, pkSize, SS, GD, di;
   gr     := e.rightMostParGath();
   N      := gr.func.range();
   pkSize := gr.pkSize;
   P      := gr.P;
   i      := var("spuid", TInt, P);
   SS     := ScatSend(fAdd(N, N/P, i*(N/P)), pkSize, P, i);
   GD     := GathDist(N, pkSize, P, i);
   #GD     := GathDist(P, (N*pkSize)/P, P, i); # Produce GathDist with maximum packet size
   return(ComposeDists(e, DistSum(P, i, P, SS*GD)));
end;

# These are required only for the CellSMP model, but shouldn't affect DMP, since DMP doesn't inject any SSs or GRs.
#F applyCellRules:
#F Assumes:
#F - There are no Buf()s in code
#F - Composes within a parallel region have already been converted to ComposeDistss
applyCellRules := function(sums, opts)
    local sumsorig, ss, sdgr, gr, ssgd, border;

#applyCellRules should not touch anything within a GT_ParStream. However, a
#sums expression can contain a mixture of ParStream and StreamChip generated
#expressions. We want to leave the former untouched, but want to act on the
#latter. We do this by marking things to be left untouched, and unmarking them at the end. This has 2 PRORLBEMS:
#
# 1) We shouldn't run ComposeDists before applyCellRules, though we also need to. Fix this.
# 2) If there's a GT_StreamPar rule, this whole thing falls apart.
#
# Essentially, we need a way of figuring out which were generated by StreamCore vs. StreamChip.
# Another approach: we could fuse the par and mbuf loops together via StreamCore's breakdown rule.
# In general, we could take care of StreamCore structures before we execute
# applyCellRules -- we could run DistMBuf in sigmaspl earlier, for instance.

    # Promote {Scat,Gath}Dist to ScatSend/GathRecv as needed
    SubstTopDown(sums, [ComposeDists, DistSum, DistSum], doAllPromotes);

    # Right-Pad DistSums that are standing by themselves (NOTE: cleanup this and the functions it calls)
    # Left-padding will be taken care of by statement below
    # NOTE: assumption here is, this is only for DFTxI.
    if IsBound(opts.doNotUseBlockCyclic) and opts.doNotUseBlockCyclic = true then
      SubstBottomUp(sums, @(1, DistSum, e->ObjId(e.rightMostParGath())=GathRecv), isContainedInComposeDistsG);
      SubstBottomUp(sums, @(1, DistSum, e->ObjId(e.leftMostParScat())=ScatSend),  isContainedInComposeDistsS);
    fi;

    # I/O padding: Don't do any input/output padding if these might get cancelled as a part of the data format change
    if IsBound(opts.doNotUseBlockCyclic) and opts.doNotUseBlockCyclic = true then
        SubstTopDown(sums, @(1, ComposeDists, e->ObjId(e.leftMostParScat())=ScatSend),  e->PadLeft(e));
        SubstTopDown(sums, @(1, ComposeDists, e->ObjId(e.rightMostParGath())=GathRecv),  e->PadRight(e));
    fi;

    #Error("BP");

    # NOTE: We shouldn't be doing this if we have a compose of multiple ParStream structures, for instance
    # Create table to map ScatSend/GathRecv pairs, so DMA can be done

    # NOTE: Why is this an else if structure? For StreamParChip, where we have
    # multiple stream stages each of which contain parallelism, all these
    # patterns could exist within the same sums expression.

    SubstTopDown(sums, [ComposeDists, DistSum, DistSum], permToData);
    SubstTopDown(sums, [ComposeDists, DistSum, DistSum, DistSum], permToData);
    SubstTopDown(sums, [ComposeDists, DistSum, DistSum, DistSum, DistSum], permToData);

    #if Length(Collect(sums, [ComposeDists, DistSum, DistSum])) = 1 then
    #     SubstTopDown(sums, [ComposeDists, DistSum, DistSum], permToData);
    #     else if Length(Collect(sums, [ComposeDists, DistSum, DistSum, DistSum])) = 1 then
    #               SubstTopDown(sums, [ComposeDists, DistSum, DistSum, DistSum], permToData);
    #     else if Length(Collect(sums, [ComposeDists, DistSum, DistSum, DistSum, DistSum])) = 1 then
    #               SubstTopDown(sums, [ComposeDists, DistSum, DistSum, DistSum, DistSum], permToData);
    #          fi;
    #          fi;
    #fi;

    # Fuse output (SDGR*SS...) to just an SS
    FixBorder(sums);

    return(sums);
end;

doAllPromotes := function(e, cx)
   local gathrecv, gathdist, scatsend, scatdist, i;

   # Assume: only one ScatSend and one GathRecv per DistSum.
   i := 1;

   # Check for GR/SD pair
   gathrecv := Collect(e._children[i],   GathRecv);
   scatdist := Collect(e._children[i+1], ScatDist);

   #Error("BP-doAllPromotes");

   if (Length(gathrecv) = 1 and Length(scatdist) = 1) then
     SubstTopDown(e, ScatDist,
        gs-> let(pkf := gs.pkSize / gathrecv[1].pkSize,
            ScatSend(fAdd(gs.N*pkf, gs.N*pkf/gs.P, gs.i*(gs.N*pkf/gs.P)), gs.pkSize/pkf, gs.P, gs.i))
     );
     return(e);
   fi;

   # Check for GD/SS pair
   gathdist := Collect(e._children[i],   GathDist);
   scatsend := Collect(e._children[i+1], ScatSend);

   if (Length(gathdist) = 1 and Length(scatsend) = 1) then
     SubstTopDown(e, GathDist,
        gs-> let(pkf := gs.pkSize / scatsend[1].pkSize,
            GathRecv(fAdd(gs.N, gs.N/gs.P, gs.i*(gs.N*pkf/gs.P)), gs.pkSize, gs.P, gs.i))
            );
     return(e);
   fi;

   return(e);

end;

#F permToData(e, cx) (e=expression, cx=context)
#F converts an expressions's function to an FList/FData
permToData := function(e, cx)
#permToData := function(d1, d2, cx)
   local gathrecv, scatsend, readMap, writeMap, loopnest, i;

   #gathrecv := e.rChildren()[1].rChildren()[2].rChildren()[Length(e.rChildren()[1].rChildren()[2].rChildren())];
   #scatsend := e.rChildren()[2].rChildren()[2].rChildren()[1];

   # Assume: only one ScatSend and one GathRecv per DistSum (no nested parallelism)
   for i in [1..(Length(Collect(e, DistSum))-1)] do
      gathrecv := Collect(e._children[i], GathRecv)[1];
      scatsend := Collect(e._children[i+1], ScatSend)[1];

      #PrintLine("permToData working on: ", gathrecv, " / ", scatsend);

      if gathrecv.func.__name__ = "FData" or scatsend.func.__name__ = "FData" then
         if gathrecv.func.__name__ = "FData" and scatsend.func.__name__ = "FData" then
            Error("Both are already FData. Why?\n");
         else
            Error("One of these funcs is an FData, the other is not.\n");
            #NOTE: Handle things correctly if both are already FDatas/FDataLists
         fi;
      fi;

      #loopnest := Concat(collectLoopVars(e, cx), [e.rChildren()[i].var, e.rChildren()[i+1].var]);
      loopnest := Concat(collectLoopVars(e, cx), [e._children[i].var, e._children[i+1].var]);

      [readMap, writeMap] := buildGlobalList(gathrecv, scatsend, loopnest);

      #NOTE: Use rSetChild or use .func directly here?
      SubstTopDown(e._children[i], GathRecv, e->GathRecv(FData([0..Length(readMap)-1]), e.pkSize, e.P, e.i));
      SubstTopDown(e._children[i+1], ScatSend, e->ScatSend(FData(readMap), e.pkSize, e.P, e.i));

      # Since we're doing a Gather-side normalize for the cell (the scatter
      # knows where to put an element, the gather is dumb), we write the
      # normalized FData instead of the actual readMap here.

   od;

   #PrintLine(gathrecv, "\n", scatsend, "\n", readMap, writeMap, "\n--\n", scatsend, "\n------------------\n\n");
   return e;
end;

#F Goes through all parents of e as given in cx.parents, and creates a list of
#F all parent loop variables in loop nest order.
collectLoopVars := function(e, cx)
   local parent, retList;
   retList := [];

   for parent in cx.parents do
      if (IsBound(parent.isSums) and parent.isSums = true) then
         if IsBound(parent.var) then
            retList := Concat(retList, [parent.var]);
         fi;
      fi;
   od;

   return retList;
end;

#F buildGlobalList(gathrecv, scatsend, loopnest)
#F Returns the read and write maps (as lists) for a given gath/scat pair
#F loopnest is a list of loop vars ordered by the gath/scat nesting
buildGlobalList := function(gathrecv, scatsend, loopnest)
   local i, j, gathMap, scatMap, t, readMap, writeMap;

   #NOTE: Need to add some error checking.

   gathMap := buildMap(gathrecv, loopnest);
   scatMap := buildMap(scatsend, loopnest);

   if Length(gathMap) <> Length(scatMap) then
      Error("buildGlobalList: packet sizes for scatter and gather don't match.");
   fi;

   t       := List([1..Length(gathMap)], i->0);
   readMap := List([1..Length(gathMap)], i->0);
   writeMap:= List([1..Length(gathMap)], i->0);

   for i in [1..Length(gathMap)] do
      t[gathMap[i]] := i;
   od;

   for i in [1..Length(gathMap)] do
      readMap[i] := t[scatMap[i]];
   od;

   for i in [1..Length(gathMap)] do
      for j in [1..Length(gathMap)] do
         if readMap[j] = i then
            writeMap[i] := j;
         fi;
      od;
   od;

   #NOTE: assuming that element values will range from 0..(n-1).
   readMap := List([1..Length(readMap)], i->readMap[i]-1);
   writeMap := List([1..Length(writeMap)], i->writeMap[i]-1);

   return [readMap, writeMap];
end;

#F buildMap(gathscat, loopnest)
#F Fully unrolls a gath/scat's function to a list based on the loop nest ordering given by loopnest
#F If loopnest has vars that the gathscat doesn't depend on, they will be ignored
buildMap := function(gathscat, loopnest)
   local map, reqFreeVars, loopVar;

   reqFreeVars := requiredFreeVars(loopnest, Flat(gathscat.free()));
   map := gathscat.func.lambda().tolist();
   for loopVar in reqFreeVars do
       map := Flat( List([0..(loopVar.range)-1], i->SubstVars(Copy(map), rec((loopVar.id) := V(i)))) );
   od;
   map := List([1..Length(map)], i->(map[i].ev())+1);

   return map;
end;

#F requiredFreeVars(loopnest, freevars)
#F
requiredFreeVars := function(loopnest, freevars)
   local returnList, i;
   returnList := [];
   for i in loopnest do
      if i in freevars then
         if not i in returnList then
            returnList := Concat(returnList, [i]);
         fi;
      fi;
   od;
   return returnList;
end;

isContainedInComposeDistsG := function(e,cx)
  local parent, foundComposeDist;
  foundComposeDist := false;
  for parent in cx.parents do
     if ObjId(parent)=ComposeDists then foundComposeDist := true; fi;
  od;

  if foundComposeDist=true then
     return(e);
  else
     return(PadRightSep(e));
  fi;
end;

isContainedInComposeDistsS := function(e,cx)
  local parent, foundComposeDist;
  foundComposeDist := false;
  for parent in cx.parents do
     if ObjId(parent)=ComposeDists then foundComposeDist := true; fi;
  od;

  if foundComposeDist=true then
     return(e);
  else
     return(PadLeftSep(e));
  fi;
end;


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

_avxT := (t, opts) -> IsVecT(t) and IsBound(opts.vector) and let( isa := opts.vector.isa,
    Cond( t.t = TReal and t.size = 4, isa=AVX_4x64f,
          t.t = TReal and t.size = 8, isa=AVX_8x32f,
          t.t = TInt  and t.size = 4, isa=AVX_4x64f,
          t.t = TInt  and t.size = 8, isa=AVX_8x32f,
          t.t in [T_Real(64), T_Int(64), T_UInt(64)] and t.size=4, true,
          t.t in [T_Real(32), T_Int(32), T_UInt(32)] and t.size=8, true,
          false));


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

_dim3 := d -> "dim3("::d.id::")";

Class(HIPUnparser, CudaUnparser, rec(
    cu_call := (self, o, i, is) >>
        Print(Blanks(i), "hipLaunchKernelGGL(",
                self.infix([o.func, _dim3(o.dim_grid), _dim3(o.dim_block), "0", "0"]::o.args, ", "), ");\n"),
));

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

doHIPify := function(opts)
    opts.originalCudaOptsID := Copy(opts.operations.Print);
    opts.operations := rec(Print := s -> Print("<FFTX HIPified CUDA options record>"));
    opts.unparser := HIPUnparser;
    opts.includes := ["\"hip/hip_runtime.h\""];
#    opts.postProcessCode := (c, opts) -> FixUpHIP_Code(PingPong_3Stages(c, opts), opts);
    opts.postProcessCode := (c, opts) -> FixUpHIP_Code(c, opts);

    return opts;
end;

Class(FFTXHIPDefaultConf, rec(
    __call__ := self >> self,
    getOpts := (self, t) >> doHIPify(ParseOptsCUDA(FFTXCUDADeviceDefaultConf, t)),
    operations := rec(Print := s -> Print("<FFTX FFTX HIPified CUDA Configuration>")),
    useHIP := true
));

hipConf := rec(
    defaultName := "defaultHIPConf",
    defaultOpts := (arg) >> FFTXHIPDefaultConf,
    confHandler := doHIPify
);

fftx.FFTXGlobals.registerConf(hipConf);


IsBound(a);
# true

Unbind(a);

IsBound(a);
# false


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

Import(paradigms.vector.breakdown, paradigms.vector.rewrite, paradigms.vector.sigmaspl);

SwitchRulesName([IxLxI_kmn_n, IxLxI_kmn_km, IxLxI_IxLxI_up, IxLxI_IxLxI_down], false);
SwitchRulesName([L_base, L_nv_n_vec, L_nv_v_vec, L_mn_m_vec, IxLxI_vtensor], true);

# FF: if this is turned off, DP tries to measure TL and complex code using CR(.) dies. Why did I need it??
#TL.doNotMeasure := false;

FixDataType := function(c, opts)
    local vars, v, srec;

    # Vectors of vectors must be flattened
#    c := SubstTopDown(c, @(1, TVect, e->ObjId(e.t)=TVect), e-> TVect(@(1).val.t.t, @(1).val.size*@(1).val.t.size));

    if IsBound(opts.vector.isa.needScalarVarFix) and opts.vector.isa.needScalarVarFix then
        vars := Collect(c, @(1, var, e->e.t=TVect(TReal, 2)));
        srec := rec();

        for v in vars do
            srec.(v.id) := opts.vector.isa.scalarVar();
        od;
        SubstVars(c, srec);
#        c := DeclareVars(c);
    fi;
    return c;
end;

# Populate this with default parameter values
VectorDefaults := rec(
    expandConstants := false,
    dontRuntimeLocalize := false,
);

DEFAULT_UNROLL := 55;   # largest prime: 13, 4-way -> need 40 and 52 be unrolled
UNROLL_LO := 16;
UNROLL_HI := 64;
VERBOSITY := 0;
FAILTOL := false;
HASH := false;

_help := function()
    PrintLine("\nopts := SIMDGlobals.getOpts(<arch>, flags);" );
    PrintLine("dpopts := SIMDGlobals.getDPOpts(<arch>, opts); # for correct DP options");
    PrintLine("\nsupported flags:");
    PrintLine("scalar DFT configuartion: CT, PFA, PFA_maxSize, PD, Rader, RealRader, Rader_maxSize, Bluestein, Bluestein_maxSize, minCost, splitRadix, PRDFT, URDFT");
    PrintLine("DFT format: interleavedComplex");
    PrintLine("tspl DFT configuartion: tsplCT, tsplPFA, tsplRader, tsplBluestein, bluesteinMinPrime, bluesteinExtraSizes, raderAvoidSizes");
    PrintLine("vectorization: svct, splitL, oddSizes, realVect, cplxVect, useConj, stdTTensor, flipIxA, pushTag");
    PrintLine("options: globalUnrolling, useArea, verify, verifyDP, verifyTolerance, verbosity, faultTolerant, mode, language, highPerf, propagateNth, useDeref\n");
end;

Class(SIMDGlobals, rec(
    svct := True,
    DPOpts := rec(
        globalUnrolling := true,
        globalUnrollingMin := 128,
        globalUnrollingMax := 128,
        defaultUnrolling := 128, # NOTE: THIS AFFECTS Rader.maxSize and other things. BAD IDEA.
        verbosity := 0,
        faultTolerant := false
    ),

    getDPOpts := meth(arg)
        local self, isa, opts;
        if Length(arg) = 1 then
            _help();
            return;
        fi;
        self := arg[1];
        isa := arg[2];
        opts := When(Length(arg) >=3, arg[3], rec());

        return rec(
            globalUnrolling := When(IsBound(opts.globalUnrolling) or (IsBound(opts.globalUnrollingMin) and IsBound(opts.globalUnrollingMax)), true, self.DPOpts.globalUnrolling),
            globalUnrollingMin := Cond(IsBound(opts.globalUnrolling), opts.globalUnrolling, IsBound(opts.globalUnrollingMin), opts.globalUnrollingMin, self.DPOpts.globalUnrollingMin * isa.v),
            globalUnrollingMax := Cond(IsBound(opts.globalUnrolling), opts.globalUnrolling, IsBound(opts.globalUnrollingMax), opts.globalUnrollingMax, self.DPOpts.globalUnrollingMax * isa.v),
            verbosity := When(IsBound(opts.verbosity), opts.verbosity, self.DPOpts.verbosity),
            timeBaseCases := true
        );
    end,

#------------------------------------------
    getOpts := meth(arg)

         local self, isa, argrec, opts, profile_rec, arg3, bsizes, unr;

        if Length(arg) = 1 then
            _help();
            return;
        fi;
        self := arg[1];
        isa := arg[2];

        #   some defaults
        argrec := rec(
            #   stride permutation configuration
            stride := false,

            #   DFT configuration
            #   scalar algorithms
            splitRadix := false,
            SplitRadix_maxSize := SIMDGlobals.DPOpts.defaultUnrolling/2,
            CT  := true,
            CT_forcePrimeFactor := false,
            PFA := true,
            PFA_maxSize := SIMDGlobals.DPOpts.defaultUnrolling/2,
            PD  := true,
            Rader := false,
            RealRader := true,
            Rader_maxSize := SIMDGlobals.DPOpts.defaultUnrolling/2,
            Rader_minSize := 3,
            Bluestein := false,
            Bluestein_maxSize := false,
            minCost := false,
            Mincost_maxSize := SIMDGlobals.DPOpts.defaultUnrolling/2,
            PRDFT := false,
            PRDFT_PF_maxSize := SIMDGlobals.DPOpts.defaultUnrolling,
            URDFT := false,
            URDFT_maxRadix := SIMDGlobals.DPOpts.defaultUnrolling/2,
            #   vector algorithms -- preset for small sizes unrolled code.
            svct := true,
            splitL := false,    #   turn on for large sizes, as SVCT's payoff diminishes and complicates matters
            useConj := false,   #   not supported right now
            oddSizes := false,   #   only for unrolled code. turn off splitL as DP takes a wrong turn for small sizes
            realVect := true,   #   use real-based vectorization
            cplxVect := false,   #   use complex-based vectorization
            pushTag := true,
            flipIxA := true,
            splitComplexTPrm := false, # used in SplitComplex DFTs and in DCTs
            TRCDiag_VRCLR := false,
            #   tSPL breakdown rules
            tsplBluestein := true,
            bluesteinMinPrime := 997,   # maybe level-based threshold? basically no automatic Bluestein applicability for now
            bluesteinExtraSizes := [ 23, 46, 47, 49, 59, 67, 79, 83, 94, 103, 106, 107, 115 ],  #   for these sizes I needed Bluestein so far
            raderAvoidSizes := [ 47, 59, 107 ], # avoid Rader for these sizes as codesize explodes for unrolled code
            tsplRader := true,
            tsplPFA := true,
            stdTTensor := true,
            tsplCT := true,
            tsplVBase := true,
            tsplCxVBase := true,
            tsplCT_oddvloop := false,
            loopOddSizes := false,
            TRCbyDef := false,
            #   other defaults
            includeMath   := true,
            faultTolerant := SIMDGlobals.DPOpts.faultTolerant,
            language      := Cond(LocalConfig.cpuinfo.default_lang <> "", LocalConfig.cpuinfo.default_lang, SpiralDefaults.language),
            verify        := false,
            verifyDP := false,
            verifyTolerance := 1E-3,
            verbosity := 0,
            interleavedComplex := true,
            highPerf := true,
            SIMD := LocalConfig.cpuinfo.SIMDname,
            fracbits := When(IsBound(isa.fracbits), isa.fracbits, false),
            useArea := false,
            processIntTables := false,
            propagateNth := false,
            useDeref := true,
            globalUnrolling := SIMDGlobals.DPOpts.defaultUnrolling,

            fma := false,
            cxfma := false,
            splitVDiag := true,
            finalSReduce := false,
            fixUnalignedLoadStore := false
        );

        if Length(arg) = 3 then
            arg3 := arg[3];
            if IsBound(arg3.__name__)  then Unbind(arg3.__name__); fi;
            if IsBound(arg3.__doc__)   then Unbind(arg3.__doc__); fi;
            if IsBound(arg3.__bases__) then Unbind(arg3.__bases__); fi;
            argrec := CopyFields(argrec, arg3);
        fi;

     profile_rec :=
        When(IsBound(SpiralDefaults.profile),
           rec(
              profile := SpiralDefaults.profile,
	          measureFunction := SpiralDefaults.profile.meas
           ),
           rec()
     );

     bsizes := Filtered(argrec.bluesteinExtraSizes, i -> not IsInt(i/isa.v^2));

     opts := CopyFields(When(argrec.highPerf, SpiralDefaults.highPerf(), SpiralDefaults), isa.getOpts(), profile_rec, rec(
         baseHashes := When(argrec.svct or argrec.flipIxA, [SIMD_ISA_DB.getHash()], []),
         breakdownRules := rec(
             TTwiddle := [TTwiddle_Tw1],
             DFT := Concat([DFT_Base],
                When(argrec.realVect and argrec.tsplVBase,   [DFT_tSPL_VBase], []),
                When(argrec.cplxVect and argrec.tsplCxVBase, [DFT_tSPL_CxVBase, DFT_tSPL_CxVBase2], []),
                When(argrec.splitRadix, [CopyFields(DFT_SplitRadix, rec(maxSize := argrec.SplitRadix_maxSize)) ], []),
                When(argrec.PD,         [DFT_PD], []),

                When(argrec.Bluestein,  [CopyFields(DFT_Bluestein, rec(
		            maxSize := argrec.Bluestein_maxSize, switch := true))], []),

                When(argrec.CT,         [CopyFields(DFT_CT, rec(forcePrimeFactor := argrec.CT_forcePrimeFactor))],
		            When(argrec.URDFT and not argrec.minCost, [CopyFields(DFT_CT, rec(maxSize := 4))], [])),

                When(argrec.PFA,        [CopyFields(DFT_GoodThomas, rec(maxSize := argrec.PFA_maxSize))], []),
                When(argrec.Rader,      [CopyFields(DFT_Rader,      rec(minSize := argrec.Rader_minSize, maxSize := argrec.Rader_maxSize))], []),
                When(argrec.RealRader,  [CopyFields(DFT_RealRader,  rec(maxSize := argrec.Rader_maxSize))], []),
                When(argrec.minCost,    [CopyFields(DFT_CT_Mincost, rec(maxSize := argrec.Mincost_maxSize))], []),
                When(argrec.tsplCT,     [DFT_tSPL_CT], []),
                When(argrec.tsplCT_oddvloop,[DFT_tSPL_CT_oddvloop], []),
                When(argrec.tsplBluestein, [ CopyFields(DFT_tSPL_Bluestein, rec(
                    applicableSizes := i -> (i in bsizes) or (not IsInt(i/isa.v^2)
			                    and ForAny(Factors(i), j -> j >= argrec.bluesteinMinPrime)),
                    minRoundup := isa.v^2,
                    customFilter := DetachFunc(Subst(i -> IsInt(i/$(isa.v^2)))))) ], []),

                When(argrec.tsplPFA,    [DFT_tSPL_GoodThomas], []),

                When(argrec.tsplRader,  [CopyFields(DFT_tSPL_Rader, rec(
                    useSymmetricAlgorithm := true,
                    avoidSizes := argrec.raderAvoidSizes)) ], []),

                When(argrec.PRDFT,      [DFT_PRDFT], []),
                When(argrec.URDFT,      [CopyFields(spiral.sym.DFT_URDFT_Decomp, rec(
		    forTransposition := true,
		    maxRadix := argrec.URDFT_maxRadix))], [])
             ),
             DFT3 := [ DFT3_Base, DFT3_CT ],
             TDCT2 := [ DCT2_DCT4_tSPL ],
             TDCT3 := [ DCT3_DCT4_tSPL ],
             TDCT4 := [ DCT4_CT_tSPL ],
             TDST2 := [ DST2_DST4_tSPL ],
             TDST3 := [ DST3_DST4_tSPL ],
             TDST4 := [ DST4_CT_tSPL ],
             TMDCT := [TMDCT_DCT4_tSPL],
             TIMDCT   := [TIMDCT_TMDCT_tSPL],
             TRDFT    := [TRDFT_By_Def, TRDFT_By_Def_tr, TRDFT_DFT_NR_tSPL_New, TRDFT_CT_tSPL_New],
             TRDFT2D  := [TRDFT2D_ColRow_tSPL],
             TIRDFT2D := [TIRDFT2D_RowCol_tSPL],
             TRConv2D := [TRConv2D_TRDFT2D_tSPL],
             TDHT := [DHT_DFT_tSPL],
             TS := [ TS_vect ],
             TConjEven := [TConjEven_vec, TConjEven_vec_tr ],
             TXMatDHT := [TXMatDHT_vec],
             WHT := [ WHT_Base, WHT_BinSplit, WHT_tSPL_BinSplit, WHT_tSPL_Base ],
             MDDFT := [ MDDFT_Base, MDDFT_RowCol, MDDFT_tSPL_RowCol ],
             PrunedDFT := [ PrunedDFT_base, PrunedDFT_DFT, PrunedDFT_CT, PrunedDFT_tSPL_CT ],
             IOPrunedDFT := [
		 IOPrunedDFT_tSPL_CT, IOPrunedDFT_base, IOPrunedDFT__PrunedDFT,
		 IOPrunedDFT__PrunedDFT_T, IOPrunedDFT__Gath_PrunedDFT, IOPrunedDFT__PrunedDFT_T_Scat, IOPrunedDFT_CT ],
             InterpolateDFT := [ InterpolateDFT_tSPL_PrunedDFT ],
             Downsample := [ Downsample_base, Downsample_tag ],
             InterpolateSegmentDFT := [ InterpolateSegmentDFT_PrunedDFT, InterpolateSegmentDFT_tSPL_PrunedDFT ],

             TTensor := Concat(When(argrec.stdTTensor, [ AxI_IxB, IxB_AxI], []),
                        When(argrec.tsplPFA, [splitL_BxI__L_AxI, AxI_L__BxI_splitL], [L_BxI__L_AxI, AxI_L__BxI_L ])),

             TTensorI := Concat([ IxA_base, AxI_base, IxA_L_base, L_IxA_base, AxI_vec ],
                            When(argrec.pushTag, [ IxA_vec_push ], []),
                            When(argrec.flipIxA, [ IxA_vec ], []),
                            When(argrec.svct, [ IxA_L_vec, L_IxA_vec], []),
                            When(argrec.splitL, [ IxA_L_split_vec, L_IxA_split_vec, IxA_split_vec ], []),
                            When(argrec.oddSizes, [ AxI_svec ], []),
                            When(argrec.loopOddSizes, [ TTensorI_oddvloop ], []),
                            When(argrec.oddSizes and argrec.svct, [ IxA_L_svec ], []),
                            When(argrec.useConj, [ IxA_conj_vec ], [])
                            ),
             TL := Concat(When(argrec.cplxVect, [L_cx_real], []),
                        When(argrec.stride, [L_GV1_vtensor], []),#[L_base_vec, L_mn_m_vec, IxLxI_vtensor],
                        When(argrec.svct or argrec.flipIxA, [ L_nv_n_vec, L_nv_v_vec, L_mn_m_vec, IxLxI_vtensor ], [])
#                        When(argrec.splitL,[ L_base_vec, IxLxI_vtensor ], [])
                    ),
             TTensorInd := [ dsA_base_vec_push, L_dsA_L_base_vec, L_dsA_L_vec, L_dsA_L_base ],

             TRC := Concat(
                When(argrec.realVect, [TRC_vect], []),
                When(argrec.cplxVect, [TRC_cplx, TRC_cplx_v2], []),
		        When(argrec.TRCbyDef, [CopyFields(TRC_By_Def, rec(maxSize := 2*isa.v))], []),
                When(not(argrec.realVect) and argrec.cplxVect, [TRC_cplxvect], [])
             ),
	     TMat    := [ TMat_Base, TMat_Vec],
             TDiag   := [ TDiag_tag ],
             TRCDiag := When(argrec.TRCDiag_VRCLR, [TRCDiag_VRCLR], [ TRCDiag_tag ]),
             TGath   := [ TGath_base ],
             TScat   := [ TScat_base ],
             TRDiag  := [ TRDiag_Vec ],
             TPrm    := When(argrec.splitComplexTPrm, [TPrm_format], [ TPrm_IJ_Vec, TPrm_IP_Base1, TPrm_J, TPrm_Jv ]),
             TGrp    := [ TGrp_tag ],
             TCompose   := [ TCompose_tag ],
             TICompose  := [ TICompose_tag ],
             TRaderMid  := [ Pad_vec ],
             TDirectSum := [ A_dirsum_B_delayed ],

             TConj   := [ TConj_perm ],
             PRDFT   := [ PRDFT1_Base1, PRDFT1_Base2, PRDFT1_CT,
		 CopyFields(PRDFT1_PF, rec(maxSize := argrec.PRDFT_PF_maxSize)), PRDFT_PD, PRDFT_Rader],
             IPRDFT  := [ IPRDFT1_Base1, IPRDFT1_Base2, IPRDFT1_CT, IPRDFT_PD, IPRDFT_Rader],
             IPRDFT2 := [ IPRDFT2_Base1, IPRDFT2_Base2, IPRDFT2_CT],
             PRDFT3  := [ PRDFT3_Base1, PRDFT3_Base2, PRDFT3_CT, PRDFT3_OddToPRDFT1],
             URDFT   := [ URDFT1_Base1, URDFT1_Base2, URDFT1_Base4, CopyFields(URDFT1_CT, rec(maxRadix := argrec.URDFT_maxRadix)) ],
             GT     := [ CopyFields(GT_Base, rec(maxSize:=false)), GT_NthLoop, GT_Vec_AxI ],
             InfoNt := [ Info_Base],
             Filt   := [ spiral.transforms.filtering.Filt_Base, spiral.transforms.filtering.Filt_Blocking ],

             # old DCT/DST rules without DFT/RDFT termination
             DCT2   := [ DCT2_DCT2and4],
             DCT3   := [ DCT3_Base2, DCT3_Base3, DCT3_Base5],
             DCT4   := [ DCT4_Base2, DCT4_Base3, DCT4_DCT2andDST2, DCT4_DST4andDST2, DCT4_DCT2, DCT4_DCT2t],
             DST2   := [ DST2_Base2, DST2_toDCT2],
             DST4   := [ DST4_Base, DST4_toDCT4]
         ),
         formulaStrategies := rec(
             sigmaSpl := VectorStrategySum,
             postProcess := Concatenation(
                VectorStrategySum,
                VectorStrategyTerm,
                VectorStrategySum,
                VectorStrategySum,
                VectorStrategyTerm2,
                VectorStrategySum,
                VectorStrategySum,
                VectorStrategyRC,
                VectorStrategyRC,
                [RulesVRCTerm, s -> SubstBottomUp(s, VIxL, e->e.implement(isa))], # NOTE: WHY do I need to have these???
                VectorStrategySum,
                VectorStrategyRC,
                VectorStrategySum,
                [ BlockSumsOpts,
                  (s, opts) -> Process_fPrecompute(s, opts)
                   ],
                fix_fAdd
             ),
             preRC := [MergedRuleSet(StandardSumsRules, RulesPropagate, JoinDirectSums),
                       MergedRuleSet(StandardSumsRules, RulesPropagate, StretchRaderMid),
                       MergedRuleSet(StandardSumsRules, RulesPropagate, RulesSplitComplex),
                       TerminateDirectSums],
             rc := []
         ),

         includes := Concatenation(
            When(argrec.includeMath = false, "", ["<math.h>"]),
            When(IsBound(isa.includes), isa.includes(), [])
         ),

         generateInitFunc := true,

         XType := TPtr(isa.t.base_t()),
         YType := TPtr(isa.t.base_t()),

     # NOTE: use layering w/ CopyFields instead of When/IsBound
     language := argrec.language,
     faultTolerant := argrec.faultTolerant,
     verify := argrec.verify,
     verifyDP := argrec.verifyDP,
     verifyTolerance := argrec.verifyTolerance,
     verbosity := argrec.verbosity,
     interleavedComplex := argrec.interleavedComplex,
     useDeref := argrec.useDeref,
     propagateNth := argrec.propagateNth,
     unparser := When(IsBound(isa.unparser), isa.unparser, CMacroUnparserProg),
     codegen := VectorCodegen,
     compileStrategy := Concatenation(
                            Cond( argrec.cxfma, IndicesCS_CXFMA,
                                  argrec.fma,   IndicesCS_FMA,
                                  # else
                                  SpiralDefaults.compileStrategy),
                            When( isa.isFixedPoint, [ c -> FixedPointCode(c, isa.bits, argrec.fracbits) ] ,[]),
                            [(c,opts) -> opts.vector.isa.fixProblems(c, opts)],
                            [HashConsts, FixDataType]),

     simpIndicesInside := SpiralDefaults.simpIndicesInside :: isa.simpIndicesInside,

     vector := CopyFields(VectorDefaults, rec(
            vlen := isa.v,
            isa := isa,
            conf := argrec,
            SIMD := argrec.SIMD
        )),
        tags := isa.getTags(),
        cxtags := isa.getTagsCx()
     ));

     if IsBound(isa.countrec) then opts.countrec := isa.countrec; fi;

     if IsBound(isa.useDeref) then opts.useDeref := isa.useDeref; fi;
     if IsBound(isa.codegenStrat) then opts.codegenStrat := isa.codegenStrat; fi;
     if IsBound(isa.compileStrategy) then opts.compileStrategy := isa.compileStrategy(); fi;

     if IsBound(isa.declareConstants) then opts.declareConstants := isa.declareConstants; fi;
     opts.scalarDataModifier := "const";
     if IsBound(isa.expandVectorConstants) then opts.expandVectorConstants := isa.expandVectorConstants; fi;

     if IsBound(isa.backendConfig) then
        opts.profile := isa.backendConfig.profile;
        opts.measureFunction := isa.backendConfig.measureFunction;
     fi;

     # vector has VRC to deal with complex code so whe turn off implicit RC business in SumsRuleTreeXXX()
     opts.generateComplexCode := true;

     if IsBound(isa.arrayBufModifier) then
        opts.arrayBufModifier :=isa.arrayBufModifier;
	 elif IsBound(isa.alignmentBytes) then
	     opts.arrayBufModifier := Concat("static ", LocalConfig.compilerinfo.alignmentSpecifier(isa.alignmentBytes));
     else
         opts.arrayBufModifier := Concat("static ", LocalConfig.compilerinfo.alignmentSpecifier());
     fi;
     if IsBound(isa.arrayDataModifier) then
        opts.arrayDataModifier :=isa.arrayDataModifier;
	 elif IsBound(isa.alignmentBytes) then
	     opts.arrayDataModifier := Concat("static ", LocalConfig.compilerinfo.alignmentSpecifier(isa.alignmentBytes));
     else
         opts.arrayDataModifier := Concat("static ", LocalConfig.compilerinfo.alignmentSpecifier());
     fi;

     if IsBound(argrec.globalUnrolling) then
        # complex vectorization requires smaller global unrolling
        opts.globalUnrolling := argrec.globalUnrolling * isa.v; # /When(argrec.cplxVect, 2, 1); #NOTE: bad problem when wrapping complex guys...
     fi;
     if argrec.useArea then
        opts.globalUnrolling := 2 * opts.globalUnrolling; # * Log2Int(opts.globalUnrolling);
        opts.markBlock := MarkBlocksAreaSums;
     fi;

     if argrec.splitVDiag then
        opts.hack_vRC_VDiag := "split";
     else
        opts.hack_vRC_VDiag := "compact";
     fi;
     opts.finalSReduce := argrec.finalSReduce;
     opts.fixUnalignedLoadStore := argrec.fixUnalignedLoadStore;

#     # use Fred's magic flags to make the compiler behave -- hopefully
#     opts.propagateNth:=false;
#     opts.useDeref := true;
#     opts.doScalarReplacement:=false;
#     if argrec.safeMode then
#         opts.useDeref:=false;
#         opts.doScalarReplacement:=false;
#     fi;

      if argrec.processIntTables then
          Append(opts.formulaStrategies.preRC, [compiler.MergeIntData]);
      fi;

     opts.operations := rec(Print := (s) -> Print("<Spiral SIMD options>"));

     if IsBound(argrec.measureFinal) then opts.measureFinal := argrec.measureFinal; fi;

     return opts;

    end
));


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

FFTXGlobals.confGPU := (arg) >> ApplyFunc(arg[1].defaultCUDADeviceConf, Drop(arg,1));
spiral.LocalConfig.fftx := FFTXGlobals;


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

Class(RulesSMP, RuleSet);

RewriteRules(RulesSMP, rec(
    PSD_SMPSumRight := ARule(Compose,  [ @(1, [RCDiag, Diag, Prm, Scat, FormatPrm]), @(2, SMPSum) ],
     e -> let(s:=@(2).val, [ SMPSum(s.nthreads, s.tid, s.var, s.domain, @(1).val * s.child(1)) ])),

    PGD_SMPSumLeft  := ARule(Compose, [ @(1, SMPSum), @(2, [Prm, Gath, Diag, RCDiag, FormatPrm]) ],
     e -> let(s:=@(1).val, [ SMPSum(s.nthreads, s.tid, s.var, s.domain, s.child(1) * @(2).val) ])),

    PSD_SMPBarrierRight := ARule(Compose,  [ @(1, [RCDiag, Diag, Prm, Scat, FormatPrm]), @(2, SMPBarrier) ],
     e -> let(s:=@(2).val, [ SMPBarrier(s.nthreads, s.tid, @(1).val * s.child(1)) ])),

    PGD_SMPBarrierLeft  := ARule(Compose, [ @(1, SMPBarrier), @(2, [Prm, Gath, Diag, RCDiag, FormatPrm]) ],
     e -> let(s:=@(1).val, [ SMPBarrier(s.nthreads, s.tid, s.child(1) * @(2).val) ])),

    Drop_GrpSPSum := Rule([Grp, @(1, SMPSum)], e -> @(1).val),
    Drop_GrpISum := Rule([Grp, @(1, ISum)], e -> @(1).val),

    SMP_ISum := Rule([SMP, @(1), @(2), @(3, ISum)], e ->
        SMPSum(@(1).val, @(2).val, @(3).val.var, @(3).val.domain, @(3).val.child(1))),
));

RewriteRules(RulesRC, rec(
    RC_SMPSum := Rule([RC, @(1, SMPSum)],
        e -> let(s:=@(1).val, SMPSum(s.nthreads, s.tid, s.var, s.domain, RC(s.child(1))))),

    RC_SMPBarrier := Rule([RC, @(1, SMPBarrier)],
        e -> let(s:=@(1).val, SMPBarrier(s.nthreads, s.tid, RC(s.child(1))))),
));


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

UnparseQASM := function (spl)
    PrintTo("./qspiralout", spl);
    Exec( "./namespaces/packages/quantum/unparser/bin/unparser ./qspiralout");
end;


#
# JupyterZMQ: UUID function
#

#! @Description
#!   Create a new zero UUID
InstallGlobalFunction(NewUUID,
function()
    local uuid;
    uuid := rec( bits := BlistList([1..128], []));
    return Objectify(UUIDType, uuid);
end);

#! @Description
#!   Generate a random UUID according to RFC4122
InstallGlobalFunction(RandomUUID,
function()
    local puuid, uuid;

    puuid := HexStringInt(Random(0,2^128-1));
    if Length(puuid) < 32 then
        puuid := Concatenation(RepeatedString("0", 32 - Length(puuid)), puuid);
    fi;
    uuid := BlistStringDecode(puuid);
    # Set version to 4
    uuid{[49..52]} := [false, true, false, false];
    # Set variant to RFC4122
    uuid{[70..72]} := [true, false, false];

    return Objectify( UUIDType, rec( bits := uuid ) );
end);

InstallGlobalFunction(StringUUID,
function(uuid)
    local hex;
    hex := LowercaseString(HexStringBlist(uuid!.bits));
    return JoinStringsWithSeparator([
                hex{[1..8]}, hex{[9..12]}, hex{[13..16]},
                hex{[17..20]}, hex{[20..32]}],
                "-");
end);

InstallGlobalFunction(HexStringUUID,
uuid -> HexStringBlist(uuid!.bits));

InstallMethod(ViewString, "for UUID", [IsUUID and IsUUIDBlistRep],
uuid -> Concatenation("<uuid ", String(uuid), ">"));

InstallMethod(String, "for UUID", [IsUUID and IsUUIDBlistRep],
    StringUUID);


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

Class(VWrapBase, rec(
    opts := (self, t, opts) >> opts,
));

Class(VWrapId, VWrapBase, rec(
    __call__ := self >> self,
    wrap := (self,r,t,opts) >> r,
    twrap := (self,t,opts) >> t
));

Class(DPWrapper, SumsBase, BaseContainer, rec(

    _short_print := true,

    new := (self, spl, wrap) >> SPL(WithBases(self, rec(
        _children  := [spl],
        dimensions := spl.dimensions,
        wrap       := wrap,
        ))),

    rChildren := self >> [self._children[1], self.wrap],

    rSetChild := meth(self, n, newC)
        if n=1 then self._children[1] := newC;
        elif n=2 then self.wrap := newC;
        else Error("<n> must be in [1..2]");
        fi;
    end,

    sums := self >> self._children[1].sums(),

    print := (self, i, is) >> Cond(self._short_print,
        Print(self.__name__, "(", self._children[1].print(i+is, is), ", ", self.wrap, ")"),
        Print(self.__name__, "(\n", Blanks(i+is),
	    self._children[1].print(i+is, is), ",\n", Blanks(i+is), self.wrap, "\n", Blanks(i), ")")),

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

    vcost := self >> self.child(1).vcost()
));

#F DPSWrapper - wrapper for stackable VWraps;
#F all stackable wrappers applied to formula in _DPSPLRec, innermost first.

Class(DPSWrapper, DPWrapper);

ClassSPL.setWrap := (self, wrap) >> DPWrapper(self, wrap).takeAobj(self);
ClassSPL.addWrap := (self, wrap) >> DPSWrapper(self, wrap).takeAobj(self);


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

# NOTE: a hack, because we essentially ignore doHashValues
_constHashPat := @@(1, Value, (e, cx) ->
    e.t.doHashValues and not IsIntT(e.t) and e.t<>TString and e.t<>TBool
    and not Last(cx.parents) _is data
    and not ForAny(cx.parents, p -> p _is Value)
);

# ^^ NOTE: strings remapped to vars cause trouble in make_env

# Below could be done automatically in Value constructor (Value.new),
# but HashTable object is not implemented efficiently, leading
# to long runtimes when huge data(..) blocks were used (e.g., SAR)
#
HashConstantsCode := (code, hashtab, hashadd_func, pat) -> SubstTopDownRulesNR(code, rec(
    hash_constant := Rule(pat, e -> hashadd_func(hashtab, e)
)));

#
# Hash for constants
#
NewConstantHash := () -> HashTable( (key,size) -> key[1].t.hash(key[1].v, size) );
GlobalConstantHash := NewConstantHash();

HashedValue := function(conhash, val)
    local hashed, h;
    if not val.t.doHashValues then return val; fi;
    hashed := HashLookup(conhash, [val, val.t]);
    if hashed = false then
        h := val;
        HashAdd(conhash, [val,val.t], h);
        return h;
    else
      return hashed;
    fi;
end;

#
# Hash for constants remapped to variables
#
NewConstantVarHash := () -> HashTable( (key,size) -> key[1].t.hash(key[1].v, size) );
GlobalConstantVarHash := NewConstantVarHash();

VHashedValue := function(conhash, val)
    local hashed, h;
    if not val.t.doHashValues then return val; fi;
    hashed := HashLookup(conhash, [val, val.t]);
    if hashed = false then
        h := var.fresh_t("C", val.t);
        h.value := val;
        HashAdd(conhash, [val, val.t], h);
        return h;
    else
      return hashed;
    fi;
end;

#
# Compiler interface
#
FlushConsts := function()
    GlobalConstantHash    := NewConstantHash();
    GlobalConstantVarHash := NewConstantVarHash();
end;

HashConsts := function (c, opts)
    if IsBound(opts.declareConstants) and opts.declareConstants then
        c := HashConstantsCode(c, GlobalConstantVarHash, VHashedValue, _constHashPat);
    else
        c := HashConstantsCode(c, GlobalConstantHash, HashedValue, _constHashPat);
    fi;
    return c;
end;

#F DeclareConstantsHash(<c>, <htab>)
#F
_declareConstantsHash := function(c, htab)
    local hh, h;
    for hh in htab.entries do
        for h in hh do
	    c := data(h.data, h.key[1], c);
	od;
    od;
    return c;
end;

#F DeclareConstantsCode(<c>, <opts>)
#F
#F Declares constants in the code as variables, without polluting the global hash.
#F Constants are declared at the top level, without regard of placement.
#F
#F To better localize use DeclareConstantsCodeLocally(c, [func], opts)
#F
DeclareConstantsCode := function(c, opts)
    local htab, hh, h;
    htab := NewConstantVarHash();
    c := HashConstantsCode(c, htab, VHashedValue, _constHashPat);
    return _declareConstantsHash(c, htab);
end;

_localDeclareConstants := function(x, opts)
    local ch, c;
    ch := x.rChildren();
    ch := List(ch, c -> Cond(not IsCommand(c), c, DeclareConstantsCode(c, opts)));
    return x.from_rChildren(ch);
end;

#F DeclareConstantsCode(<c>, <patterns>, <opts>)
#F
#F Applies DeclareConstantsCode locally to each of the patterns.
#F This function is non-recursive, i.e. if one of the patterns is matched, it won't
#F go inside the body. So for best results all patterns should be mutually exclusive.
#F
DeclareConstantsCodeLocally := (c, patterns, opts) -> SubstTopDownRulesNR(
    c, List(patterns, p -> Rule(p, _localDeclareConstants))
);


#
#
#

Read("~/Workspace/Chevalley.gap/init.gi");

Read(Filename(home_dir,"lib/rsys.gd"));
Read(Filename(home_dir,"lib/rsys.gi"));

Read(Filename(home_dir,"lib/chvadj.gd"));
Read(Filename(home_dir,"lib/chvadj.gi"));

Read(Filename(home_dir,"lib/nilchv.gd"));
Read(Filename(home_dir,"lib/nilchv.gi"));

Read(Filename(home_dir,"lib/algU.gd"));
Read(Filename(home_dir,"lib/algU.gi"));

#
# UipotentChv needs SolveRelations
#
Read(Filename(home_dir,"lib/poly.gd"));
Read(Filename(home_dir,"lib/poly.gi"));

#
# Unialg needed
#
Read(Filename(home_dir,"lib/unichv.gd"));
Read(Filename(home_dir,"lib/witt.gd"));
Read(Filename(home_dir,"lib/unialg.gd"));

Read(Filename(home_dir,"lib/unichv.gi"));


#
# francy: Interactive Discrete Mathematics in GAP
#

#############################################################################
##
#M  Callback( <callback type>, <trigger Type>,  <function>, <known args> ) .
##
## triggers a callback with a list of known args.
## Extra args, or required args, should be registered using:
##      Callback!.add(CallbackRequiredArg)
##
InstallMethod(Callback,
  "a function, a list of known args",
  true,
  [IsTriggerType,
   IsFunction,
   IsList],
  0,
function(triggerType, func, knownArgs)
  local object;
  object := Objectify(CallbackObjectType, rec(
    id           := GenerateID(),
    trigger      := triggerType!.value,
    func         := func,
    knownArgs    := knownArgs,
    requiredArgs := rec()
  ));
  FrancyCallbacks!.(object!.id) := object;
  return object;
end);

InstallOtherMethod(Callback,
  "a trigger type, a function",
  true,
  [IsTriggerType,
   IsFunction],
  0,
function(triggerType, func)
  return Callback(triggerType, func, []);
end);

InstallOtherMethod(Callback,
  "a function, a list of knownArgs",
  true,
  [IsFunction,
   IsList],
  0,
function(func, knownArgs)
  return Callback(TriggerType.CLICK, func, knownArgs);
end);

InstallOtherMethod(Callback,
  "a function",
  true,
  [IsFunction],
  0,
function(func)
  return Callback(TriggerType.CLICK, func, []);
end);

#############################################################################
##
#M  NoopCallback( )
##
## Creates an empty Callback object that does nothing
##
InstallMethod(NoopCallback,
  "",
  true,
  [],
  0,
function()
  return Objectify(CallbackObjectType, rec());
end);

#############################################################################
##
#M  RequiredArg( <callback arg type>, <title> )
##
InstallMethod(RequiredArg,
  "a callback arg type, a title",
  true,
  [IsArgType,
   IsString],
  0,
function(argType, title)
  # FIXME might have to add a new property with order of the arg!
  return Objectify(RequiredArgObjectType, rec(
    id    := GenerateID(),
    type  := argType!.value,
    title := title,
    value := ""
  ));
end);

#############################################################################
##
#M  Add( <callback>, <required arg> ) . . . . . add objects to canvas
##
InstallOtherMethod(Add,
  "a callback, a required arg",
  true,
  [IsCallback,
   IsRequiredArg],
  0,
function(callback, arg)
  callback!.requiredArgs!.(arg!.id) := arg;
  return callback;
end);

InstallOtherMethod(Add,
  "a callback, a list of francy objects",
  true,
  [IsCallback,
   IsList],
  0,
function(callback, objects)
  local object;
  for object in objects do
    Add(callback, object);
  od;
  return callback;
end);

#############################################################################
##
#M  Remove( <callback>, <required arg> ) . . . . . add objects to canvas
##
InstallOtherMethod(Remove,
  "a callback, a required arg",
  true,
  [IsCallback,
   IsRequiredArg],
  0,
function(callback, arg)
  Unbind(callback!.requiredArgs!.(arg!.id));
  return callback;
end);

InstallOtherMethod(Remove,
  "a callback, a list of francy objects",
  true,
  [IsCallback,
   IsList],
  0,
function(callback, objects)
  local object;
  for object in objects do
    Remove(callback, object);
  od;
  return callback;
end);

#############################################################################
##
#M  Trigger( <a json string> ) . triggers a callback
##
InstallMethod(Trigger,
  "a json string",
  true,
  [IsString],
  0,
function(json)
  local callback, object, requiredArgs, arg;
  object := JsonStringToGap(json);
  # FIXME need to validate the callback!
  #if not IsCallbackRep(object) then
  #  Error("Not a valid Callback!");
  #fi;
  callback := FrancyCallbacks!.(object!.id);
  requiredArgs := [];
  for arg in NamesOfComponents(object!.requiredArgs) do
    Add(requiredArgs, object!.requiredArgs!.(arg)!.value);
  od;
  return CallFuncList(callback!.func, Concatenation(callback!.knownArgs, requiredArgs));
end);


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

_dbgPrintRuleName := rule -> Cond(
    IsBound(rule.owner),
        Print(spiral._color(Blue, rule.name), "   /  ",
	      spiral._color(DarkBlue, rule.owner), "\n"),
    Print(spiral._color(Blue, rule.name), "\n")
);

_dbgRuleBelongsTo := (rule, rset) ->
    IsBound(rule.owner) and (rule.owner in rset);

#F DebugRewriting(<rsets.)
DebugRewriting := function(rsets)
    if rsets=true then
        rewrite.RuleTrace := rule -> _dbgPrintRuleName(rule);
        rewrite.RuleStatus := (rule, hd, str) -> Print(
	    spiral._color(Yellow, hd), ApplyFunc(Print, str));
    elif rsets=false then
        rewrite.RuleTrace := Ignore;
        rewrite.RuleStatus := Ignore;
    else
		if not IsList(rsets) then
			rsets := [rsets];
		fi;
        rewrite.RuleTrace := rule ->
			When(_dbgRuleBelongsTo(rule, rsets), _dbgPrintRuleName(rule));
        rewrite.RuleStatus := (rule, hd, str) ->
			When(_dbgRuleBelongsTo(rule, rsets),
		Print(spiral._color(Yellow, hd), ApplyFunc(Print, str)));
    fi;
end;

DebugRuleStrategies := function(switch)
    Constraint(IsBool(switch));
    if switch then
        rewrite.RuleTrace := rule -> _dbgPrintRuleName(rule);
        rewrite.RuleStrategyTrace := (i, rset, expr) -> Print(
			spiral._color(Red, i),
			spiral._color(Red, " ----------------\n"),
			rset, "\n",
			Doc(rset),
			expr, "\n");
    else
        rewrite.RuleTrace := Ignore;
        rewrite.RuleStrategyTrace := Ignore;
    fi;
end;


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

# Option Records
# ==============

#F SPL Options Records
#F -------------------

#F An spl options record is used to collect options passed to the
#F spl compiler by functions that communicate with the spl compiler,
#F such as measure, verify, and search functions.
#F
#F There is a system wide options record, SpiralDefailts (set in config.g),
#F which sets the default values.
#F
#F An spl options record is a record, which contains a subset of the
#F following fields. Adding a new fields requires to change the
#F functions PrintSpecSPLOptionsRecord, CheckSPLOptionsRecord, and
#F MakeSPLCallSPLOptionsRecord.
#F
#F rec(
#F   customDataType  = "int_cplx" | "int_fpN" | "Ipp16sc" (XScale) | "Ipp32sc" (XScale)
#F                                     where N - number of fractional bits, i.e. int_fp8
#F   zeroBits        = zero | <positive int>
#F   dataType        = "real" | "complex",
#F   precision       = "single" | "double" | "extended"
#F   subName         = <string>
#F   schedule        = <integer>
#F   globalUnrolling = <positive int> | "none" | "full",
#F   language        = "fortran" | "c"    # see config.g for languages
#F   compiler        = not to be used
#F   compflags       = <flags for compiler as string>
#F   splflags        = <flags for spl compiler as string>
#F )
#F
#F Note: switching language automatically switches compilers
#F and flags.
#F
#F spl options records should be used as follows:
#F - create your desired spl options record R
#F - merge with defaults, R1 := MergeSPLOptionsRecord(R)
#F - create spl prog for external operations with ProgSPL(SPL, R)
#F

#F CheckSPLOptionsRecord ( <spl-options-record> )
#F   checks whether <spl-options-record> is a valid spl options record
#F   with valid spl options set. If a field name or a field value
#F   is invalid, then an error is signaled, otherwise true is
#F   returned.
#F
CheckSPLOptionsRecord := function ( R )
  local r;
  if not IsRec(R) then Error("<R> must be an spl options record"); fi;

  for r in RecFields(R) do
    Cond(r = "dataType",
        Constraint(R.dataType in ["no default", "real", "complex"]),
         r = "customDataType",
        Constraint(IsString(R.customDataType)),
         r = "customReal",
        Constraint(IsString(R.customReal)),
         r = "customComplex",
        Constraint(IsString(R.customComplex)),
     r = "zeroBits",
        Constraint(IsInt(R.zeroBits) and R.zeroBits >= 0),
         r = "precision",
            Constraint(R.precision in  ["single", "double", "extended"]),
         r = "subName",
        Constraint(IsString(R.subName)),
         r = "file",
        Constraint(IsString(R.file)),
         r = "schedule",
        Constraint(IsInt(R.schedule) and R.schedule > 0),
         r = "globalUnrolling",
        Constraint(
        (IsInt(R.globalUnrolling) and R.globalUnrolling > 0)
        or R.globalUnrolling in ["none", "full"]),
         r = "compiler",
        Constraint(IsString(R.compiler)),
         r = "compflags",
        Constraint(IsString(R.compflags)),
         r = "dmpcompflags",
        Constraint(IsString(R.dmpcompflags)),
       r = "faultTolerant",
        Constraint(IsBool(R.faultTolerant)),
     r = "cgen",
        Constraint(IsFunc(R.cgen)),
     r = "x" or IsSystemRecField(r),
        Ignore(),
     0); # do nothing if field is unrecognized
  od;

  return true;
end;

#F MergeSPLOptionsRecord ( <spl-options-record> )
#F   returns the option record obtained by starting with SpiralDefaults
#F   (config.g, set at installation) and merging or overwriting with the
#F   spl options given by <spl-options-record>
#F
MergeSPLOptionsRecord := R -> Checked(CheckSPLOptionsRecord(R),
    CopyFields(SpiralDefaults, R)
);