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

Declare(TTensorI_OL);
Declare(TTensorI_OL_Vectorize_AVecLast);

#Class(RewriteBarrier,RewritableObject);
#Class(RewriteBarrier,DFT);
#RewriteBarrier.numops:=self>>1;

DropVectorTag:=function(nt)
    return nt.withoutTag(AVecReg);
#D    return SetTag(nt,Filtered(GetTags(nt),t->ObjId(t)<>AVecReg));
end;

DropParTag:=function(nt)
   return nt.withoutTag(AParSMP);
#D    return SetTag(nt,Filtered(GetTags(nt),t->ObjId(t)<>AParSMP));
end;

Devectorize:=function(l,vlen)
  return List(l,e->
      Cond(
          ObjId(e)=TArray and e.t=TUnknown, TArray(e.t,e.size*vlen),
          ObjId(e)=TArray and ObjId(e.t)=TVect, TArray(e.t.t,e.size*e.t.size),
          ObjId(e)=TVect, TArray(e.t,e.size),
          e)
  );
end;

Class(VTensor_OL, Tensor, rec(
    new := (self, L) >> SPL(WithBases(self, rec(
        _children := [L[1]],
        vlen := L[2]))),
    print := (self,i,is) >> Print(self.name, "(",
        self.child(1).print(i+is,is), ", ", self.vlen,")"),
    sums := self >> Inherit(self, rec(_children := [self.child(1).sums()])),
    isPermutation := False,
    rng := meth(self)         #FULL HACK
       return Devectorize(self.child(1).rng(),self.vlen);
    end,
    dmn := meth(self)         #FULL HACK
       return Devectorize(self.child(1).dmn(),self.vlen);
    end,
    dims := meth(self)
       if (IsBound(self.rng)and IsBound(self.dmn)) then
          return [StripList(List(self.rng(),l->l.size)),
                  StripList(List(self.dmn(),l->l.size))];
       fi;
    end,
));

#VTensor.dmn:=  meth(self)         #FULL HACK
#  local x;
#  x:=self.child(1).dmn()[1];
#  if ObjId(x)=TArray and x.t=TUnknown then
#      return [TArray(TUnknown,x.size*self.vlen)];
#  else
#      return Devectorize(self.child(1).dmn());
#  fi;
#end;
#VTensor.rng:=  meth(self)         #FULL HACK
#  local x;
#  x:=self.child(1).rng()[1];
#  if ObjId(x)=TArray and x.t=TUnknown then
#      return [TArray(TUnknown,x.size*self.vlen)];
#  else
#      return Devectorize(self.child(1).rng());
#  fi;
#end;

BlockVPerm.dmn := meth(self)
       return [TArray(self.child(1).dmn()[1].t,self.child(1).dmn()[1].size*self.n)];
end;
BlockVPerm.rng := meth(self)
       return [TArray(self.child(1).rng()[1].t,self.child(1).rng()[1].size*self.n)];
end;

Class(ScatQuestionMark, Scat, rec());
Class(ICScatAcc,Scat,rec(codeletName:="ICSA"));

Class(VScatQuestionMark, VScat, rec());

Class(VScat_svQuestionMark, VScat_sv, rec());

Class(ScatInit, BaseMat, rec(
   new := meth(self, f,con,c)
        local res;
        res := SPL(WithBases(self, rec(func:=f,cond:=con, _children:=c)));
        return res;
   end,
   rng:=self>>self._children.rng(),
   dmn:=self>>self._children.dmn(),
   print := meth(self,i,is)
      Print(self.name, "(", self.func, ", ",self.cond,", ");
      Print("\n", Blanks(i+is));
      SPLOps.Print(self._children, i + is, is);
      Print("\n", Blanks(i),")");
      return;
   end,
   rChildren := self >> [self._children,self.func],
   rSetChild := meth ( self, n, newChild )
     if n= 1  then
         self._children :=newChild;
     elif n=2 then
         self.func := newChild;
else
        Error("<n> must be 1");
     fi;
   end

));

Class(ScatInitProbe,ScatInit,rec());
Class(ScatInitFixed,ScatInit,rec());

Class(KroneckerSymbol, BaseMat, rec(
   abbrevs := [ arg -> [Flat(arg)] ],
   new := meth(self, l)
        local res;
        res := SPL(WithBases(self, rec(element:=l)));
        return res;
   end,
   isExp:=true,
   dims:=self>>[1,1]  #avoid recursive definition for the old-school Tensor
));

## Base Vector
Class(BV, BaseMat, rec(
   new := (self, i) >> #Checked(IsVar(i),SPL(WithBases(self, rec(element:=i))))
       SPL(WithBases(self, rec(element:=i))),
   dims:=self>>self.element.dimensions #[0,0]  #avoid recursive definition for the old-school Tensor
));

## Multiplication operator
## Multiplication(1,n) is I(n)
## Multiplication(2,n) is a point-wise multiplication of two vectors of size n
 Class(Multiplication, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
        local res;
        if l[1]=1 then return Prm(fId(l[2])); fi;
        res := SPL(WithBases(self, rec(element:=l,TType:=Replicate(l[1],TUnknown))));
        return res;
    end,
    isPermutation := self >> false,
#    dmn:=self>>Replicate(self.element[1],TArray(self.TType,self.element[2])),
#HACK
#    dmn:=meth(self) local a; a:=Replicate(self.element[1],TArray(self.TType,self.element[2]));a[1]:=TArray(TReal,1);return a; end,
    dmn:=self >>List(self.TType,x->TArray(x,self.element[2])),
    rng:=self>>let(a:=Try(First(self.TType,x->ObjId(x)=TVect)),t:=Cond(a[1],a[2],self.TType[1]),[TArray(t,self.element[2])]),
    sums:= self>>self,
    numops:=self>>self.element[2]*(self.element[1]-1),
    transpose := self >>self,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ", ",self.element[2],")"); self.printA();
      return;
     end
 ));
 Class(ICMultiplication,Multiplication, rec(

 ));

  _mk_advdim_r := (e) -> List(e, x -> When(IsList(x), _mk_advdim_r(x), [x]));
  _mk_advdim := (d) -> _mk_advdim_r(When(IsList(d), d, [d]));

  Class(Glue, BaseMat, rec(
    abbrevs := [(n,size) ->[n,size]],
    new := meth(self,n,size)
        local res;
        res := SPL(WithBases(self, rec(element:=[n,size],dimensions:=[size*n,Replicate(n,size)],TType:=Replicate(n,TUnknown))));
        return res;
    end,
    isPermutation := self >> false,
    dmn:=self >>List([1..(self.element[1])],x->TArray(self.TType[x],self.element[2])),
    rng:=self>>[TArray(self.TType[1],self.element[1]*self.element[2])],
    sums:= self>>self,
    transpose:=self>>Copy(self),
    numops:=self>>self.element[2]*(self.element[1]-1),
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ", ",self.element[2],")"); self.printA();
      return;
     end,
    rChildren := self >> self.element,
    rSetChild := meth( self, n, newChild ) self.element[n] := Checked(n=1 or n=2, newChild); end,
    advdims := self >> let( d := self.dims(), [ _mk_advdim(d[1]), _mk_advdim(d[2]) ]),
    normalizedArithCost := (self) >> 0,
    isReal := self >> true, # makes no sense
 ));

  Class(Split, BaseMat, rec(
    abbrevs := [(size,n) ->[size,n]],
    new := meth(self,size,n)
        local res;
        res := SPL(WithBases(self, rec(element:=[size,n],dimensions:=[Replicate(n,size / n),size],TType:=[TUnknown])));
        return res;
    end,
    isPermutation := self >> false,
    rng:=self >>List([1..self.element[2]],x->TArray(self.TType[1],self.element[1] / self.element[2])),
    dmn:=self>>[TArray(self.TType[1],self.element[1])],
    sums:= self>>self,
    transpose:= self>>Copy(self),
    numops:=self>>(self.element[1]),
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ", ",self.element[2],")"); self.printA();
      return;
     end,
    rChildren := self >> self.element,
    rSetChild := meth( self, n, newChild ) self.element[n] := Checked(n=1 or n=2, newChild); end,
    advdims := self >> let( d := self.dims(), [ _mk_advdim(d[1]), _mk_advdim(d[2]) ]),
    normalizedArithCost := (self) >> 0,
    isReal := self >> true, # makes no sense
 ));

## NoOp, used with in COND_OL()
Class(NoOp, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(1, TUnknown))));
        fi;
    end,
    isPermutation := self >> false,
    dmn:=self >> [TArray(self.TType[1], self.element[1])],
    rng:=self >> [TArray(self.TType[1], self.element[1])],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

## Constant
Class(Const, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(1, TUnknown))));
        fi;
	# Here
    end,
    isPermutation := self >> false,
    dmn:=self >> [],
# [TArray(self.TType[1], 0)],
    rng:=self >> [TArray(self.TType[1], 1)],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

Class(Or, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(l[1], TUnknown))));
        fi;
	# Here
    end,
    isPermutation := self >> false,
    dmn:=self >> List(self.TType, x->TArray(x,1)),
    rng:=self >> [ TArray(self.TType[1], 1)],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

Class(And, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(l[1], TUnknown))));
        fi;
	# Here
    end,
    isPermutation := self >> false,
    dmn:=self >> List(self.TType, x->TArray(x,1)),
    rng:=self >> [ TArray(self.TType[1], 1)],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

Class(NotEqual, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(l[1], TUnknown))));
        fi;
	# Here
    end,
    isPermutation := self >> false,
    dmn:=self >> List(self.TType, x->TArray(x,1)),
    rng:=self >> [ TArray(self.TType[1], 1)],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

Class(Equal, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(l[1], TUnknown))));
        fi;
	# Here
    end,
    isPermutation := self >> false,
    dmn:=self >> List(self.TType, x->TArray(x,1)),
    rng:=self >> [ TArray(self.TType[1], 1)],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

Class(ExclusiveOr, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(l[1], TUnknown))));
        fi;
	# Here
    end,
    isPermutation := self >> false,
    dmn:=self >> List(self.TType, x->TArray(x,1)),
    rng:=self >> [ TArray(self.TType[1], 1)],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

## Minimums
## Minimums(n) -> Input is n-way vector
Class(Minimums, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(l[1], TUnknown))));
        fi;
	# Here
    end,
    isPermutation := self >> false,
    dmn:=self >> List(self.TType, x->TArray(x,1)),
    rng:=self >> [ TArray(self.TType[1], 1)],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

## Maximums
## Maximums(n) -> Input is n-way vector
Class(Maximums, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
       local res;
        if (Length(l) = 1) then
          return SPL(WithBases(self, rec(element:=l, TType:=Replicate(l[1], TUnknown))));
        fi;
	# Here
    end,
    isPermutation := self >> false,
    dmn:=self >> List(self.TType, x->TArray(x,1)),
    rng:=self >> [ TArray(self.TType[1], 1) ],
    sums:= self>>self,
    numops:=self>> 0,
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ")"); self.printA();
      return;
    end
));

#Addition
Class(Addition, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
        local res;
        if l[1]=1 then return Prm(fId(l[2])); fi;
        res := SPL(WithBases(self, rec(element:=l,TType:=Replicate(l[1],TUnknown))));
        return res.setDims();
    end,
    isPermutation := self >> false,

    dmn:=self >> List( [1..self.element[1]], i -> TArray(TUnknown, self.element[2])),
    rng:=self >> [TArray(TUnknown, self.element[2])],
    sums:= self>>self,
    numops:=self>>self.element[2]*(self.element[1]-1),
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ", ",self.element[2],")"); self.printA();
      return;
     end,
    transpose := self >> InertTranspose(self),
    area := self >> self.element[2]*(self.element[1]-1),
));

Class(Subtraction, BaseMat, rec(
    abbrevs := [ arg -> [Flat(arg)] ],
    new := meth(self, l)
        local res;
        if l[1]=1 then return Prm(fId(l[2])); fi;
        res := SPL(WithBases(self, rec(element:=l,TType:=Replicate(l[1],TUnknown))));
        return res;
    end,
    isPermutation := self >> false,
    dmn:=self >>List(self.TType,x->TArray(x,self.element[2])),
    rng:=self>>let(a:=Try(First(self.TType,x->ObjId(x)=TVect)),t:=Cond(a[1],a[2],self.TType[1]),[TArray(t,self.element[2])]),
    sums:= self>>self,
    numops:=self>>self.element[2]*(self.element[1]-1),
    print := meth(self,i,is)
      Print(self.name, "(", self.element[1], ", ",self.element[2],")"); self.printA();
      return;
     end
));

# Declare(Cross);
# Class(Cross, BaseOperation, rec(
#    abbrevs := [ arg -> [Flat(arg)] ],

#    new := meth(self, L)
#         if Length(L)=1 then return L[1]; fi;
#         return SPL(WithBases(self, rec(_children:=L)));
#    end,

#    codeletName:="C",
#    isPermutation := self >> false,
#    dmn:=self>>let(li:=[],Flat(List(self._children, x-> x.dmn()))),
#    rng:=self>>let(li:=[],Flat(List(self._children, x-> x.rng()))),
#    sums:= meth(self)
#    local i;
#    for i in [1..Length(self._children)] do
#       self._children[i]:=self._children[i].sums();
#    od;
#    return self;
#    end,
#    hasLeftGath:=meth(self)
#       local i;
#       for i in self._children do
# #          if (ObjId(i)=Compose and ObjId(i.rChildren()[1])=Gath) or
# #              (ObjId(i)=Gath) then
# #              return true;
#            if (ObjId(i)=Compose and IsBound(i.rChildren()[1].func) and i.rChildren()[1].func.free()<>Set([])) or (IsBound(i.func) and (not ObjId(i)=VGath_dup) and i.func.free()<>Set([])) and not(ObjId(i)=ISum) then
#                return true;
#           fi;
#       od;
#       return false;
#    end,
#    hasRightScat:=meth(self)
#       local i;
#       for i in self._children do
# #          if (ObjId(i)=Compose and ObjId(Last(i.rChildren()))=Scat) or
# #              (ObjId(i)=Scat) then
# #              return true;
# #          fi;
#            if (ObjId(i)=Compose and ((IsBound(Last(i.rChildren()).func) and Last(i.rChildren()).func.free()=Set([])) or (ObjId(Last(i.rChildren()))=ISum) or (ObjId(Last(i.rChildren()))=VGath_dup and Length(Last(i.rChildren()).func.free())=1))) or (IsBound(i.func) and i.func.free()=Set([])) or (ObjId(i)=VGath_dup and Length(i.func.free())=1) or ObjId(i)=ISum  then
#                return true;
#           fi;
#       od;
#       return false;
#    end,
#    splitCross:=meth(self)
#       local l,r;
#       l:=Copy(self);
#       r:=[];
#       for i in [1..Length(l._children)] do
# #          if (ObjId(l._children[i])=Compose and ObjId(Last(l._children[i].rChildren()))=Scat) then
#            if (ObjId(l._children[i])=Compose and ((IsBound(Last(l._children[i].rChildren()).func) and Last(l._children[i].rChildren()).func.free()=Set([])) or (ObjId(Last(l._children[i].rChildren()))=ISum) or (ObjId(Last(l._children[i].rChildren()))=VGath_dup and Length(Last(l._children[i].rChildren()).func.free())=1))) or (ObjId(l._children[i])=ISum) then
#               Add(r,Last(l._children[i].rChildren()));
#               l._children[i]._children:=DropLast(l._children[i]._children,1);
# #          elif (ObjId(l._children[i])=Scat) then
#            elif (IsBound(l._children[i].func) and l._children[i].func.free()=Set([])) or (ObjId(i)=VGath_dup and Length(i.func.free())=1) then
#               Add(r,l._children[i]);
#               l._children[i]:=Prm(fId(l._children[i].dims()[1]));
#           else
#               Add(r,Prm(fId(l._children[i].dims()[2])));
#           fi;
#       od;
#       return [l,Cross(r)];
#    end
#  )
# );

Class(CrossBlockTop,Cross);

Identities:=function(l)
   return Cross(List(l,x->Prm(fId(x.size))));
end;

BB.numops:= self>>self.child(1).numops();
NoPull.numops:= self>>self.child(1).numops();
NoPullRight.numops:= self>>self.child(1).numops();
NoPullLeft.numops:= self>>self.child(1).numops();

PushR.numops:= self>>self.child(1).numops();

ISum.numops:=self>>self.child(1).numops()*self.domain;

SUM.numops:=self>>Sum(List(self._children,c->c.numops()));

Cross.numops:=self>>Sum(List(self._children,c->c.numops()));

Compose.numops:=self>>Sum(List(self._children,c->c.numops()));

Scat.numops:=self>>self.dims()[2];

Gath.numops:=self>>self.dims()[1];

VGath.numops:=self>>self.dims()[1];
VGath_dup.numops:=self>>self.dims()[1];
VReplicate.numops:=self>>self.v;
VHAdd.numops:=self>>self.v*self.v;
VPerm.numops:=self>>self.dimensions[1];
Tensor.numops:=self>>self.dimensions[1];

ScatAcc.numops:=self>>self.dimensions[2]*2; #an add and a store
VScatAcc.numops:=self>>self.dimensions[2]*2; #an add and a store
VScat.numops:=self>>self.dimensions[2];

fBase.numops:=self>>0;
fTensor.numops:=self>>0;
fId.numops:=self>>0;
ScatInit.numops:=self>>self._children.numops();

Prm.numops:=self>>0;
I.numops:=self>>0;

VTensor_OL.numops:=self>>self._children[1].numops()*self.vlen;

Class(AOne,AGenericTag);
Class(AMul,AGenericTag, rec(
    __call__ := meth ( arg )
      local  result, self, params;
      self := arg[1];
      params := arg{[ 2 .. Length(arg) ]};
      result := WithBases(self, rec(
              params := params,
              operations := PrintOps));
      return result;
    end,
    print := self >> Print(self.__name__, "(", PrintCS(self.params), ")")
   ));

Class(VOLWrap, VWrapBase, rec(
    __call__ := (self,isa) >> Checked(IsSIMD_ISA(isa),
        WithBases(self, rec(operations:=PrintOps, isa:=isa))),

    wrap := (self,r,t) >> let(isa := self.isa, v := isa.v,
#This is OBVIOUSLY a hack. only deals with some kind of vectorization
            nontransforms.ol.TTensorI_OL_Vectorize_AVecLast(TTensorI_OL(t, [ AOne, AVec ],[ [ 0, 2 ] ], [ 1, v ], [ AVecReg(isa) ]), r)),

    twrap := (self, t) >> let(isa := self.isa, v := isa.v,
#This is OBVIOUSLY a hack. only deals with some kind of vectorization
           TTensorI_OL(t, [ AOne, AVec ],[ [ 0, 2 ] ], [ 1, v ], [ AVecReg(isa) ])
        ),

    print := self >> Print(self.name, "(", self.isa, ")")
));

Class(TTensorI_OL, Tagged_tSPL, rec(
    abbrevs := [
        (nt,g,s,v)  -> [nt,List([1..Length(g)],i->When(v[i]=1,AOne,g[i])),s,v]
    ],

    dmn := self >> let(
        nt := self.params[1],
        sizes := self.params[4],
        List([1..Length(sizes)], i->
            TArray(nt.dmn()[i].t,sizes[i] * nt.dmn()[i].size)
        )),

    rng := self >> let(
        nt := self.params[1],
        s := self.params[3],
        v := self.params[4],
        List([1..Length(s)], i->
            TArray(nt.rng()[i].t, Product(List(s[i], a ->
                When(a=0,
                    nt.rng()[i].size,
                    v[a]
                )
            )))
        )
    ),

    isReal := self >> self.params[1].isReal(),
    doNotMeasure:=true,
    doNotSaveInHashtable:=true,
    decomposePerformance:=true,
    transpose := self >> Copy(self),
#D    tagpos :=5,
));

#Changed by Marek, looks OK
NewRulesFor(TTensorI_OL, rec(
    TTensorI_OL_Base :=rec(
        applicable := (self,t) >> not(t.hasTag(AParSMP)) and t.getTags() = t.params[1].getTags(),
        freedoms := nt -> let(
            g := nt.params[2],
            nbvars := Length(Filtered(g,x->x=APar or x=AVec)),
            [Arrangements([1..nbvars],nbvars)]
        ),
        child := (nt,freedoms) -> [nt.params[1],InfoNt(freedoms)],
        recompose := (nt,cnt,cperf) -> cperf[1]*Product(List(Filtered(Zip2(nt.params[2],nt.params[4]),l->l[1] in [APar,AVec]),x->x[2])),
        apply := function(nt,c,cnt)
            local g,s,v,perm,ind1,ind,ind2,gathers,scatters,result,z;
            g := nt.params[2];
            s := nt.params[3];
            v := nt.params[4];
            perm := cnt[2].params[1][1];
            ind1 := List([1..Length(g)], i ->
                Cond(g[i]=APar or g[i]=AVec, fBase(Ind(v[i])),
                    g[i]=AOne, fId(1),
                    ObjId(g[i])=AMul, g[i],
                    Error("PV not known!")
                )
            );
            ind := List(ind1, x -> When(ObjId(x)=AMul,ind1[x.params[1]],x));
            gathers := Cross(List([1..Length(g)], i -> let(
                kernelsize := fId(cnt[1].dmn()[i].size),
                When(g[i]=APar or (ObjId(g[i])=AMul and g[i].params[2]=APar),
                    Gath(fTensor(ind[i],kernelsize)),
                    Gath(fTensor(kernelsize,ind[i]))
                )
            )));
            scatters := Cross(List([1..Length(s)], i ->
                ScatQuestionMark( fTensor(
                    List(s[i], y ->
                        When(y=0,
                            fId(cnt[1].rng()[i].size),
                            ind[y]
                        )
                    )
                ))
            ));
            result := scatters*c[1]*gathers;
            ind2:=[];
            for i in [1..Length(g)] do
                if g[i]=APar or g[i]=AVec then
                    Add(ind2,ind[i]);
                fi;
            od;
            for i in [1..Length(ind2)] do
                result:=ISum(ind2[perm[i]].params[2], ind2[perm[i]].params[1], result);
            od;

            return result;
        end
    ),
#D        applicable :=(self,t) >> Length(Filtered(GetTags(t),x->ObjId(x)=AParSMP))=0 and GetTags(t)=GetTags(t.params[1])
#D,
#D            freedoms := nt -> let(g:=nt.params[2],
#D                nbvars:=Length(Filtered(g,x->x=APar or x=AVec)),
#D                [Arrangements([1..nbvars],nbvars)]),
#D            child := (nt,freedoms) -> [nt.params[1],InfoNt(freedoms)],
#D            recompose := (nt,cnt,cperf) -> cperf[1]*Product(List(Filtered(Zip2(nt.params[2],nt.params[4]),l->l[1] in [APar,AVec]),x->x[2])),
#D            apply := function(nt,c,cnt)
#D                local g,s,v,perm,ind1,ind,ind2,gathers,scatters,result,z;
#D                g:=nt.params[2];
#D                s:=nt.params[3];
#D                v:=nt.params[4];
#D                perm:=cnt[2].params[1][1];
#D                ind1:=List([1..Length(g)],
#D                    i->Cond(g[i]=APar or g[i]=AVec,fBase(Ind(v[i])),
#D                    g[i]=AOne,fId(1),
#D                    ObjId(g[i])=AMul,g[i],
#D                    Error("PV not known!")));
#D                ind:=List(ind1,x->When(ObjId(x)=AMul,ind1[x.params[1]],x));
#D                gathers:= Cross(List([1..Length(g)],
#D                        i->let(kernelsize:=fId(cnt[1].dmn()[i].size),
#D                            When(g[i]=APar or (ObjId(g[i])=AMul and g[i].params[2]=APar),
#D                                Gath(fTensor(ind[i],kernelsize)),
#D                                Gath(fTensor(kernelsize,ind[i]))))));
#D                scatters:=Cross(List([1..Length(s)],
#D                            i->ScatQuestionMark(fTensor(
#D                                    List(s[i],y->
#D                                        When(y=0,fId(cnt[1].rng()[i].size),
#D                                            ind[y]))))));
#D                result:=scatters*c[1]*gathers;
#D                ind2:=[];
#D                for i in [1..Length(g)] do
#D                    if g[i]=APar or g[i]=AVec then
#D                        Add(ind2,ind[i]);
#D                    fi;
#D                od;
#D                for i in [1..Length(ind2)] do
#D                        result:=ISum(ind2[perm[i]].params[2],ind2[perm[i]].params[1],result);
#D                od;
#D
#D                return result;
#D            end
#D            ),

#Changed by Marek, looks OK
    TTensorI_OL_Parrallelize_AParFirst := rec(
        applicable :=(self,t) >>
            t.isTag(1, AParSMP)
            and 0 <> t.params[3][1][1]
            and APar = t.params[2][t.params[3][1][1]]
            and t.firstTag().params[1] = t.params[4][t.params[3][1][1]]
            and 1 = t.params[3][1][1], #t.params[3][1][1]=1 is a trick to prevent // the 2nd input because TensorGeneral breaks at the moment

        freedoms := nt -> [[1]],

        child := function (nt,freedoms)
            local PV,sizes;
            sizes := Copy(nt.params[4]);
            sizes[nt.params[3][1][1]] := 1;
            if Length(Filtered(sizes, t -> t<>1)) > 0 then
                PV:=Copy(nt.params[2]);
                PV[nt.params[3][1][1]] := AOne;
                return [TTensorI_OL(
                    DropParTag(nt.params[1]),
                    PV,
                    nt.params[3],
                    sizes,
                    Drop(nt.params[5],1)
                )];
            else
                return [ DropParTag(Copy(nt.params[1])) ];
            fi;
        end,

        apply := function(nt,c,cnt)
            local myCross,a,b,index;

            index := Ind(nt.firstTag().params[1]);

            a := List(c[1].dims()[2], x -> fId(x));
            a[nt.params[3][1][1]] := fTensor(fBase(index), a[nt.params[3][1][1]]);

            b:=List(a, x -> Gath(x));

            myCross:=Cross(b);

#              return SMPSum(GetFirstTag(nt).params[1],index, GetFirstTag(nt).params[1],ScatQuestionMark(fTensor(fBase(index), fId(c[1].dims()[1]))) *c[1]* Cross(Gath(fTensor(fBase(index), fId(c[1].dims()[2][1]))),Gath(fId(c[1].dims()[2][2]))));
            return SMPSum(
                nt.firstTag().params[1],
                index,
                nt.firstTag().params[1],
                ScatQuestionMark(fTensor(
                    fBase(index),
                    fId(c[1].dims()[1])
                ))
                * c[1] * myCross
            );
        end
    ),
#D            applicable :=(self,t) >> FirstTagEq(t, AParSMP) and t.params[3][1][1]<>0 and
#D               t.params[2][t.params[3][1][1]]=APar and
#D               t.params[4][t.params[3][1][1]]=GetFirstTag(t).params[1] and
#D               t.params[3][1][1]=1, #t.params[3][1][1]=1 is a trick to prevent // the 2nd input because TensorGeneral breaks at the moment
#D            freedoms := nt -> [[1]],
#D            child := function (nt,freedoms)
#D              local PV,sizes;
#D              sizes:=Copy(nt.params[4]);
#D              sizes[nt.params[3][1][1]]:=1;
#D              if Length(Filtered(sizes, t->t<>1))>0 then
#D                  PV:=Copy(nt.params[2]);
#D                  PV[nt.params[3][1][1]]:=AOne;
#D              return [TTensorI_OL(DropParTag(nt.params[1]),
#D                          PV,nt.params[3],sizes,Drop(nt.params[5],1))];
#D              else
#D                  return [DropParTag(Copy(nt.params[1]))];
#D              fi;
#D            end,
#D            apply := function(nt,c,cnt)
#D              local myCross,a,b,index;
#D              index:=Ind(GetFirstTag(nt).params[1]);
#D              a:=List(c[1].dims()[2],x->fId(x));
#D              a[nt.params[3][1][1]]:=fTensor(fBase(index),a[nt.params[3][1][1]]);
#D              b:=List(a,x->Gath(x));
#D              myCross:=Cross(b);
#D#              return SMPSum(GetFirstTag(nt).params[1],index, GetFirstTag(nt).params[1],ScatQuestionMark(fTensor(fBase(index), fId(c[1].dims()[1]))) *c[1]* Cross(Gath(fTensor(fBase(index), fId(c[1].dims()[2][1]))),Gath(fId(c[1].dims()[2][2]))));
#D              return SMPSum(GetFirstTag(nt).params[1],index, GetFirstTag(nt).params[1],ScatQuestionMark(fTensor(fBase(index), fId(c[1].dims()[1]))) *c[1]* myCross);
#D            end),

    #That code vectorizes the last guy if it is a AVec of the vector size
    #Hack, only works with one output
#Changed by Marek, looks OK
    TTensorI_OL_Vectorize_AVecLast :=rec(
        applicable := (self,t) >>
            t.isTag(1, AVecReg)
            and Last(t.params[3][1])<>0
            and AVec = t.params[2][Last(t.params[3][1])]
            and t.params[4][Last(t.params[3][1])] = t.firstTag().v,

        freedoms := nt -> [[1]],

        child := function(nt, freedoms)
            local PV, sizes;

            sizes := Copy(nt.params[4]);
            sizes[Last(nt.params[3][1])] := 1;

            if Length(Filtered(sizes, t->t<>1))>0 then
                PV:=Copy(nt.params[2]);
                PV[Last(nt.params[3][1])]:=AOne;
                return [TTensorI_OL(
                    DropVectorTag(nt.params[1]).setWrap(VOLWrap(nt.firstTag().isa)),
                    PV,
                    nt.params[3],
                    sizes,
                    Drop(nt.params[5],1)
                )];
            else
                return [ DropVectorTag(Copy(nt.params[1])).setWrap(VOLWrap(nt.firstTag().isa)) ];
          fi;
        end,

        apply := function(nt,c,cnt)
            local myCross, myScat, mydims, v;

            v := nt.firstTag().v;

            #little hack for KernelDup
            if ObjId(nt.params[1]).name <> "KernelMMMDuped" then
                myCross := Cross(List(nt.dims()[2], t ->
                    VGath_dup(fId(t), v)
                ));
                myCross._children[Last(nt.params[3][1])] :=
                    VPrm_x_I(
                        fId( nt.dims()[2][Last(nt.params[3][1])] / v ),
                        nt.firstTag().v
                    );
            else
                mydims := nt.dims()[2];
                mydims[1] := mydims[1] * v;
                myCross := Cross(List(mydims, t ->
                    VPrm_x_I( fId(t/v), v )
                ));
            fi;

            myScat := VScat(fId( nt.dims()[1]/v ), v );

            return myScat * VTensor_OL(c[1], v) * myCross;
        end
    ),
#D            applicable :=(self,t) >> FirstTagEq(t, AVecReg) and Last(t.params[3][1])<>0 and t.params[2][Last(t.params[3][1])]=AVec and t.params[4][Last(t.params[3][1])]=GetFirstTag(t).v,
#D            freedoms := nt -> [[1]],
#D            child := function (nt,freedoms)
#D              local PV,sizes;
#D              sizes:=Copy(nt.params[4]);
#D              sizes[Last(nt.params[3][1])]:=1;
#D              if Length(Filtered(sizes, t->t<>1))>0 then
#D                  PV:=Copy(nt.params[2]);
#D                  PV[Last(nt.params[3][1])]:=AOne;
#D              return [TTensorI_OL(DropVectorTag(nt.params[1]).setWrap(VOLWrap(GetFirstTag(nt).isa)),
#D                          PV,nt.params[3],sizes,Drop(nt.params[5],1))];
#D              else
#D                  return [DropVectorTag(Copy(nt.params[1])).setWrap(VOLWrap(GetFirstTag(nt).isa))];
#D              fi;
#D            end,
#D            apply := function(nt,c,cnt)
#D              local myCross,myScat,mydims;
#D#little hack for KernelDup
#D              if (ObjId(nt.params[1]).name<>"KernelMMMDuped") then
#D                  myCross:=Cross(List(nt.dims()[2],t->VGath_dup(fId(t),GetFirstTag(nt).v)));
#D                  myCross._children[Last(nt.params[3][1])]:=
#D                  VPrm_x_I(fId(nt.dims()[2][Last(nt.params[3][1])]/GetFirstTag(nt).v),GetFirstTag(nt).v);
#D              else
#D                  mydims:=nt.dims()[2];
#D                  mydims[1]:=mydims[1]*GetFirstTag(nt).v;
#D                  myCross:=Cross(List(mydims,t->VPrm_x_I(fId(t/GetFirstTag(nt).v),GetFirstTag(nt).v)));
#D              fi;
#D              myScat:=VScat(fId(nt.dims()[1]/GetFirstTag(nt).v),GetFirstTag(nt).v);
#D              return myScat*VTensor_OL(c[1],GetFirstTag(nt).v)*myCross;
#D            end),

#Changed by Marek, looks OK
    TTensorI_OL_Vectorize_AParFirst :=rec(
        switch:=false,

        applicable := (self,t) >>
            t.isTag(1, AVecReg)
            and 0 <> First(t.params[3][1], x -> true)
            and APar = t.params[2][First(t.params[3][1],x -> true)]
            and t.firstTag().v = t.params[4][First(t.params[3][1], x -> true)],

        freedoms := nt -> [[1]],

        child := function (nt,freedoms)
            local PV,outorder,theguy;

            PV := Copy(nt.params[2]);
            PV[First(nt.params[3][1],x->true)] := AVec;

            theguy := Copy(First(nt.params[3][1], x->true));
            outorder := Drop(Copy(nt.params[3][1]),1);
            Add(outorder,theguy);

            return [
                TTensorI_OL( nt.params[1], PV, [outorder], nt.params[4], nt.params[5]),
                TL(nt.rng()[1].size, nt.firstTag().v, 1, 1, nt.params[5]),
                TL(
                    nt.dmn()[First(nt.params[3][1], x -> true)].size,
                    nt.dmn()[First(nt.params[3][1], x -> true)].size / nt.firstTag().v,
                    1,
                    1,
                    nt.params[5]
                )
            ];
        end,

        apply := function(nt,c,cnt)
            local i;

            i := Identities(nt.dmn());
            i._children[First(nt.params[3][1], x -> true)] := c[3];

            return c[2] * c[1] * i;
        end
    ),

#D            applicable :=(self,t) >> FirstTagEq(t, AVecReg) and First(t.params[3][1],x->true)<>0 and t.params[2][First(t.params[3][1],x->true)]=APar and t.params[4][First(t.params[3][1],x->true)]=GetFirstTag(t).v,
#D            freedoms := nt -> [[1]],
#D            child := function (nt,freedoms)
#D              local PV,outorder,theguy;
#D              PV:=Copy(nt.params[2]);
#D              PV[First(nt.params[3][1],x->true)]:=AVec;
#D              theguy:=Copy(First(nt.params[3][1],x->true));
#D              outorder:=Drop(Copy(nt.params[3][1]),1);
#D              Add(outorder,theguy);
#D              return [TTensorI_OL(nt.params[1],PV,[outorder],nt.params[4],nt.params[5]),TL(nt.rng()[1].size,GetFirstTag(nt).v,1,1,nt.params[5]),TL(nt.dmn()[First(nt.params[3][1],x->true)].size,nt.dmn()[First(nt.params[3][1],x->true)].size/GetFirstTag(nt).v,1,1,nt.params[5])];
#D            end,
#D            apply := function(nt,c,cnt)
#D              local i;
#D              i:=Identities(nt.dmn());
#D              i._children[First(nt.params[3][1],x->true)]:=c[3];
#D              return c[2]*c[1]*i;
#D            end),
));


InstallMethod( JupyterRender, [ IsRecord ],
               r -> Objectify( JupyterRenderableType
                             , rec( data := rec( text\/plain := String(r) )
                                   , metadata := rec() ) ) );

# This is still an ugly hack, but its already much better than before!
BindGlobal("JupyterSplashDot",
function(dot)
    local fn, fd, r;

    fn := TmpName();
    fd := IO_File(fn, "w");
    IO_Write(fd, dot);
    IO_Close(fd);

    fd := IO_Popen(IO_FindExecutable("dot"), ["-Tsvg", fn], "r");
    r := IO_ReadUntilEOF(fd);
    IO_close(fd);
    IO_unlink(fn);

    return JupyterRenderable( rec( ("image/svg+xml") := r )
                            , rec( ("image/svg+xml") := rec( width := 500, height := 500 ) ) );
end);

# Splash the subgroup lattice of a group
BindGlobal("JupyterSplashSubgroupLattice",
function(group)
    local fn, fd, r, L, dot;

    fn := TmpName();

    L := LatticeSubgroups(group);
    DotFileLatticeSubgroups(L, fn);

    fd := IO_Popen(IO_FindExecutable("dot"), ["-Tsvg", fn], "r");
    r := IO_ReadUntilEOF(fd);
    IO_close(fd);
    IO_unlink(fn);

    return JupyterRenderable( rec( ("image/svg+xml") := r )
                            , rec( ("image/svg+xml") := rec( width := 500, height := 500 ) ) ) ;

end);

# To show TikZ in a GAP jupyter notebook
BindGlobal("JupyterSplashTikZ",
function(tikz)
    local tmpdir, fn, header, ltx, svgfile, stream, svgdata, tojupyter;

    header:=Concatenation( "\\documentclass[crop,tikz]{standalone}\n",
                    "\\usepackage{pgfplots}",
                    "\\makeatletter\n",
                    "\\batchmode\n",
                    "\\nonstopmode\n",
                    "\\begin{document}",
                    "\\begin{tikzpicture}");
    header:=Concatenation(header, tikz);
    header:=Concatenation(header,"\\end{tikzpicture}\n\\end{document}");

    tmpdir := DirectoryTemporary();
    fn := Filename( tmpdir, "svg_get" );

    PrintTo( Concatenation( fn, ".tex" ), header );

    ltx := Concatenation( "pdflatex -shell-escape --output-directory ",
                   Filename( tmpdir, "" ), " ",
                   Concatenation( fn, ".tex" ), " > ", Concatenation( fn, ".log2" ) );
    Exec( ltx );

    if not( IsExistingFile( Concatenation(fn, ".pdf") ) ) then
        tojupyter := rec( json := true, name := "stdout",
                          data := "No pdf was created; pdflatex is installed in your system?" );
    else
        svgfile := Concatenation( fn, ".svg" );
        ltx := Concatenation( "pdf2svg ", Concatenation( fn, ".pdf" ), " ",
                       svgfile, " >> ", Concatenation( fn, ".log2" ) );
        Exec( ltx );

        if not( IsExistingFile( svgfile ) ) then
            tojupyter := rec( json := true, name := "stdout",
                              data := "No svg was created; pdf2svg is installed in your system?" );
        else
            stream := InputTextFile( svgfile );
            if stream <> fail then
                svgdata := ReadAll( stream );
                tojupyter := rec( json := true, source := "gap",
                                  data := rec( ( "image/svg+xml" ) := svgdata ),
                                  metadata := rec( ( "image/svg+xml" ) := rec( width := 500, height := 500 ) ) );
                CloseStream( stream );
            else
                tojupyter := rec( json := true, name := "stdout",
                                  data := Concatenation( "Unable to render ", tikz ), metadata := rec() );
            fi;
        fi;
    fi;

    return JupyterRenderable(tojupyter.data, tojupyter.metadata);
end);

# This is really not what I should be doing here...
InstallGlobalFunction(ISO8601Stamp,
function()
    local tz, gm, pad;

    tz := IO_gettimeofday();
    pad := function(i, l, c)
        local s;
        s := String(i);
        if Length(s) < l then
            return Concatenation(RepeatedString(c, l - Length(s)), s);
        else
            return s;
        fi;
    end;

    gm := IO_gmtime(tz.tv_sec);
    return STRINGIFY( 1900 + gm.tm_year, "-"
                      , pad(gm.tm_mon + 1, 2, '0'), "-"
                      , pad(gm.tm_mday, 2, '0'), "T"
                      , pad(gm.tm_hour, 2, '0'), ":"
                      , pad(gm.tm_min, 2, '0'), ":"
                      , pad(gm.tm_sec, 2, '0'), "."
                      , pad(tz.tv_usec, 6, '0') );
end);


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

IsSIMD_ISA := x -> IsRec(x) and IsBound(x.isSIMD_ISA) and x.isSIMD_ISA=true;
IsISA      := x -> IsRec(x) and IsBound(x.isISA) and x.isISA=true;

Declare(ISAOps);
ISAOps := rec(
    operations := OpsOps,
    name := "ISAOps",
    Print := s->s.print(),
    # backward compatibility hack - descendants equal to parent classes
    \= := (c1, c2) -> Same(c1,c2) or (IsISA(c1) and IsISA(c2) and (Same(ObjId(c1),c2) or Same(c1,ObjId(c2)) or c1.id()=c2.id())),
    \< := (c1, c2) -> not ISAOps.\=(c1, c2) and Cond(not IsISA(c1) or not IsISA(c2), BagAddr(c1) < BagAddr(c2),  c1.id()<c2.id()),
);

Class(ISA, rec(
    verbose := false,
    isISA   := true,
    id      := self >> self.__name__,

    fixProblems := (c,opts) -> c,

    rChildren := self >> [],
    from_rChildren := (self, rch) >> self,

    gt := self >> self.t,

    operations := ISAOps,
    print := self >> Print(self.__name__, When(self._cplx, ".cplx()", "")),

    autolib := rec(
        includes      := () -> [], # list of includes, ex: [ "<pmmintrin.h>" ]
        timerIncludes := () -> [], # list of timer includes
    ),
    # wrap(<spl>) wraps spl to ISA boundaries container (at this moment VContainer)
    wrap := (self, spl) >> spl,
));

Class(SIMD_ISA, ISA, rec(
    isSIMD_ISA := true,

    _cplx := false,

    # isCplx()
    isCplx := self >> self._cplx,

    # cplx()  -- set the complex vectorization flag, which makes .getV() return v/2
    #            which is needed so that VContainer inside complex vectorization
    #            region correctly resolves its vector length
    cplx := self >> CopyFields(self, rec(_cplx := true)),

    # uncplx()  -- unset the complex vectorization flag, which makes .getV() return v
    #              which is needed so that VContainer inside complex vectorization
    #              region correctly resolves its vector length
    uncplx := self >> CopyFields(self, rec(_cplx := false)),

    # getV() -- returns the effective vector length that should be used, it is
    #           equal to ISA's vector length normally, but inside complex vector-
    #           ization regions it is vlen/2.
    getV := self >> Cond(self._cplx, self.v/2, self.v),

    getTags := self >> [AVecReg(self)],
    getTagsCx := self >> [AVecRegCx(self)],
    getOpts := self >> self.splopts,
    rules_loaded := false,
    rules_built := false,
    setRules := meth(self, rules)
                    self.rules := rules;
                    self.rules_loaded := true;
                end,
    flushRules := meth(self)
                      self.rules_loaded := false;
                      self.rules_built := false;
                      if IsBound(self.rules) then Unbind(self.rules); fi;
                  end,

    # realVect()  -- returns true if real vectorization is allowed
    realVect := True,
    # cplxVect()  -- returns true if complex vectorization is allowed
    cplxVect := True,

    simpIndicesInside := [], # to which instructions we should apply expensive index simplification rules,
                             # these should include gather/scatter instructions
    # ISA atomic data type
    gt := self >> self.t.t,

    wrap := (self, spl) >> spiral.paradigms.vector.sigmaspl.VContainer(spl, self),

    #F .loadCont(<n>, <y>, <yofs>, <x>, <xofs>, <xofs_align>)
    #F
    #F Read <n> values from address <x> + <xofs> into (lower) slots in vector pointer at <y> + <yofs>
    #F
    #F <n> - integer, how many points to load, must be <= self.v
    #F <y> - destination pointer
    #F <yofs> - destination offset in vectors
    #F <x> - source pointer
    #F <xofs> - source offset
    #F <xofs_align> - alignment, must be (xofs mod self.v), this parameter allows us
    #F                to pass in a simplified expression which may be constant
    #F
    #F Assumptions: 1 <= n <= self.v
    #F              xofs_align = xofs mod self.v
    #F
    #F When n = self.v, this operation becomes an unaligned load (unless xofs_align=0)
    #F
    loadCont := (self, n, y, yofs, x, xofs, xofs_align, opts) >> let(
	a  := _unwrap(xofs_align),
	nn := _unwrap(n),
	yy := vtref(self.t, y, yofs),
        When(IsBound(self.loadc_align) and IsInt(a) and not IsUnalignedPtrT(x.t),
	     self.loadc_align(nn, a, opts)(yy, x, xofs),
	     self.loadc(nn, opts)(yy, nth(x, xofs)))),

    #F .storeCont(<n>, <y>, <yofs>, <yofs_align>, <x>, <xofs>)
    #F
    #F Store (lower) <n> values from address <x> + <xofs> into address <y> + <yofs>
    #F
    #F <n> - integer, how many points to store, must be <= self.v
    #F <y> - destination pointer
    #F <yofs> - destination offset
    #F <yofs_align> - alignment, must be (yofs mod self.v), this parameter allows us
    #F                to pass in a simplified expression which may be constant
    #F <x> - source pointer
    #F <xofs> - source offset in vectors
    #F
    #F Assumptions: 1 <= n <= self.v
    #F              yofs_align = yofs mod self.v
    #F
    #F When n = self.v, this operation becomes an unaligned load (unless yofs_align=0)
    #F
    storeCont := (self, n, y, yofs, yofs_align, x, xofs, opts) >> let(
	a  := _unwrap(yofs_align),
	nn := _unwrap(n),
	xx := vtref(self.t, x, xofs),
        When(IsBound(self.storec_align) and IsInt(a) and not IsUnalignedPtrT(y.t),
	     self.storec_align(nn, a, opts)(y, yofs, xx), # storec_align is not implemented anywhere
	     self.storec[nn](nth(y, yofs), xx))),

    storeContAcc := (self, n, y, yofs, yofs_align, x, xofs, opts) >> let(
	a  := _unwrap(yofs_align),
	nn := _unwrap(n),
	xx := vtref(self.t, x, xofs),
	t  := TempVec(TArray(xx.t.t, xx.t.size)),
	decl([t], chain(
	    self.loadCont(nn, t, 0, y, yofs, yofs_align, opts),
	    assign(vtref(self.t, t, 0), vtref(self.t, t, 0) + xx),
	    self.storeCont(nn, y, yofs, yofs_align, t, 0, opts)))),

    # ===============================================================================
    # Other fields that must be defined in subclasses

    # Required:
    #  active
    #  bin_shl1  bin_shl2  bin_shr1  bin_shr2  bin_shrev
    #  bits, ctype, t, v
    #  countrec
    #  includes
    #  info
    #  instr
    #  isFixedPoint, isFloat
    #  loadc + loadc_align (optional)  OR  loadCont
    #  mul_cx, mul_cx_conj
    #  reverse
    #  splopts
    #  storec  OR  storeCont
    #  svload
    #  svstore
    #  RCVIxJ2
    #  dupload, duploadn
    #  hadd
    #  swap_cx
    #  vzero

    # Fixed point:
    #  fracbits
    #  saturatedArithmetic

    # Viterbi:
    #  interleavedmask, hmin, average (?), isSigned

    # Hacks (used in 2x32f), supported but not required:
    #  loadop,      requireLoad
    #  storeop,     requireStore
    #  scalarVar,   needScalarVarFix

));

IsSIMD_ISA := x -> IsRec(x) and IsBound(x.isSIMD_ISA) and x.isSIMD_ISA=true;
IsISA      := x -> IsRec(x) and IsBound(x.isISA) and x.isISA=true;

Class(SIMD_ISA_DB, rec(
    verbose := false,
    isa_db := rec(),
    addISA := meth(self, isa) self.isa_db.(isa.name) := isa; end,
    installed := self >> Filtered(RecFields(self.isa_db), x -> not IsSystemRecField(x)),
    active := self >> List(Filtered(Filtered(RecFields(self.isa_db), x -> not IsSystemRecField(x)), e->self.isa_db.(e).active), k->self.isa_db.(k)),
    info := self >> Print("\nSpiral SIMD ISA database\n",
                          "installed ISAs: ", PrintCS(self.installed()), "\n",
                          "active ISAs: ", PrintCS(self.active()), "\n"),
    getISA := (self, isa) >> self.isa_db.(isa),
#------------------------------------------
    HASH_FILE := file -> let(p := Conf("path_sep"), base := Conf("spiral_dir"), Concat(base, p, "namespaces", p, "spiral", p, "platforms", p, "_", file, "_generated1.gi")),
    RULES_FILE := file -> let(p := Conf("path_sep"), base := Conf("spiral_dir"), Concat(base, p, "namespaces", p, "spiral", p, "platforms", p, "_", file, "_generated0.gi")),
    hash := HashTableDP(),
    hashFlush := meth(self) self.hash := HashTableDP(); end,
    hashSave := meth(self)
                    local _entry, entry, item, isa;
                    if self.verbose then Print("saving hash\n"); fi;
                    _entry := Flat(Filtered(self.hash.entries, True));

                    for isa in self.active() do
                        PrintTo(self.HASH_FILE(isa.file), "");
                    od;

                    for isa in self.active() do
#                        Error();
                        for entry in Filtered(_entry, (a) -> a.key.getTags()[1].isa = isa)  do
#                            for item in entry  do
                            item := entry; #only save first hash entry
                            AppendTo(self.HASH_FILE(isa.file), self.name, ".hashAdd(", item.key, ", ", item.data, ");\n");
#                            od;
                        od;
                    od;
                end,
    hashAdd := meth(self, a, b) HashAdd(self.hash, a,b); end,
    getHash:= self >> Copy(self.hash),
    hash_rebuilt := false,
    rules_rebuilt := false,
    init0 := meth(self)
        local isa;
        self.rules_rebuilt := false;
        if self.verbose then Print("\n"); fi;
        for isa in self.active() do
            if self.verbose then Print(isa, ": base cases...\n"); fi;
            if not IsBound(isa.rules) then
                Print(isa, " rules are being rebuilt and saved...\n");
                isa.buildRules();
                self.rules_rebuilt := true;
            fi;
        od;
        if self.rules_rebuilt then self.saveRules(); fi;
    end,
    init1 := meth(self)
        local isa;
        self.hash_rebuilt := false;
        if self.verbose then Print("\n"); fi;
        for isa in self.active() do
            if self.verbose then Print(isa, ": TL hash...\n"); fi;
            if not self.checkBases(isa) then
                Print(isa, " hash is being rebuilt and saved...\n");
                self.buildBases(isa);
                self.hash_rebuilt := true;
            fi;
        od;
        if self.hash_rebuilt then self.hashSave(); fi;
    end,
    saveRules := meth(self)
                    local isa;
                    if self.verbose then Print("saving rules\n"); fi;
                    paradigms.vector.sigmaspl.VPerm.plong();

                    for isa in self.active() do
                        PrintTo(self.RULES_FILE(isa.file), "");
                    od;

                    for isa in self.active() do
                        if IsBound(isa.rules) then
                            AppendTo(self.RULES_FILE(isa.file), isa.name, ".setRules(", isa.rules, ");\n");
                        fi;
                    od;
                    paradigms.vector.sigmaspl.VPerm.pshort();
                 end,
    reset := meth(self)
                local isa, verb;
                verb := self.verbose;
                for isa in self.active() do
                    PrintTo(self.RULES_FILE(isa.file), "");
                    PrintTo(self.HASH_FILE(isa.file), "");
                od;
                self.verbose := true;
                self.hashFlush();
                for isa in self.active() do
                    isa.flushRules();
                od;
                self.init0();
                self.init1();
                self.verbose := verb;
             end,
    required_bases := isa -> Concat(
            When(isa.realVect(), [
                arch -> TL(2*arch.v,2,1,1).withTags(arch.getTags()),
                arch -> TL(2*arch.v,arch.v,1,1).withTags(arch.getTags()),
                arch -> TL(arch.v^2,arch.v,1,1).withTags(arch.getTags()) ], []),
            When(isa.cplxVect(), [
                arch -> TL(arch.v^2/4,arch.v/2,1,2).withTags(arch.getTags()) ], []),
            When(isa.cplxVect() and isa.v=8, [
                arch -> TL(4,2,1,2).withTags(arch.getTags()) ], [])),
    getBases := (self, isa) >> List(self.required_bases(isa), i-> i(isa)),
    lookupBases := (self, isa) >> List(self.getBases(isa), i-> HashLookup(self.hash, i)),
    checkBases := (self, isa) >> not ForAny(self.lookupBases(isa), i -> i=false or i=[])
));


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

FindUnexpandableNonterminal := function(t, opts)
    local trees, res;
    Constraint(IsSPL(t));
    trees := ExpandSPL(t, opts);
    if trees=[] then return [t];
    else
        return ConcatList(trees, tr ->
	    ConcatList(tr.children, c -> FindUnexpandableNonterminal(c, opts)));
    fi;
end;

RuleTreeClass_rChildren := self >> [self.node, self.children];
RuleTreeClass_rSetChild := rSetChildFields("node", "children");

EnableRuleTreeRewriting := function(switch)
    if switch then
        RuleTreeClass.rChildren := RuleTreeClass_rChildren;
        RuleTreeClass.rSetChild := RuleTreeClass_rSetChild;
    else
        Unbind(RuleTreeClass.rChildren);
        Unbind(RuleTreeClass.rSetChild);
    fi;
end;

VerifyRulesInRuleTree := function(r, opts)
    local nt, n, bad;
    EnableRuleTreeRewriting(true);
    nt := CollectNR(r, @.cond(IsNonTerminal));
    bad := [];
    for n in nt do
        #if n.free() <> [] then
            n:=HashAsSPL(n);
        #fi;
        if ObjId(n) <> InfoNt then
            Print(Red("-------", n, "-------"), "\n");
            if VerifyRulesForSPL(n, opts) = false then
                Add(bad, n);
            fi;
        fi;
    od;
    EnableRuleTreeRewriting(true);
    Print(Red("Failed:\n    "), bad, "\n");
end;

_VerifySubRuleTrees := function(r, opts, rt_tomat_func, ind)
    local subtrees, rt, bugs, hashrt, t, innerbugs;
    EnableRuleTreeRewriting(true);
    bugs := [];
    for rt in r.children do
        t := HashAsSPL(rt.node);
	hashrt := ApplyRuleTreeSPL(rt, t, opts);
        if InfinityNormMat(MatSPL(rt_tomat_func(hashrt)) - MatSPL(hashrt.node)) > 1e-13 then
	    PrintLine(Blanks(ind), hashrt.node, RedStr("  FAIL"));
	    bugs := [hashrt];
	    innerbugs := _VerifySubRuleTrees(hashrt, opts, rt_tomat_func, ind+4);
	    return Cond(innerbugs=[], bugs, innerbugs);
	else
	    PrintLine(Blanks(ind), hashrt.node, GreenStr("  OK"));
	fi;
    od;
    EnableRuleTreeRewriting(false);
    return bugs;
end;

VerifySubRuleTreesSPL := (r, opts) -> _VerifySubRuleTrees(r, opts, SPLRuleTree, 0);

#VerifySubRuleTreesSums := (r, opts) -> _VerifySubRuleTrees(r, opts, x->SumsRuleTree(x, opts), 0);


LETTERS;
# "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
LETTERS{[5 .. 10]};
# "EFGHIJ"


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

#F ScriptGenBase()
#F

Class(ScriptGenBase, rec(

	_settings := rec(),

    ##
    ## Public methods
    ##

	getAllSettings := (self) >> Copy(self._settings),

	setAllSettings := meth(self, newrec)
		local field;

		if not IsRec(newrec) then
			return false;
		fi;

		for field in RecFields(newrec) do
			self._settings.(field) := Copy(newrec.(field));
		od;

		#later validate new settings

		return true;
	end,

	getSettingsValue := meth(self, key)
		if not IsBound(self._settings.(key)) then
			return "";
		fi;
		return self._settings.(key);
	end,

	setSettingsValue := meth(arg)
		local self, key, value;
		if Length(arg) < 3 then return false; fi;

		self  := arg[1];
		key   := arg[2];
		value := arg[3];

		#quick out if already set
		if IsBound(self._settings.(key)) and (value = self._settings.(key)) then
			return true;
		fi;

		if key = SGKEY_TRANSFORM then
			return self._setTransform(value);
		elif key = SGKEY_SIZE then
			return self._setSize(value);
		elif (key = SGKEY_DATATYPE) then
			return self._setType(value);
		elif key = SGKEY_FILENAME then
			return self._setFilename(value);
		elif key = SGKEY_FUNCNAME then
			return self._setFuncname(value);
		fi;

		self._settings.(key) := value;

		return true;
	end,

	# TEMP simple set of sequences for early release, just works for FFT
	getSettingsSequence := meth(self)
		local seq;

		seq  := [SGKEY_TRANSFORM, SGKEY_DATATYPE, SGKEY_SIZE];

		return seq;
	end,

	getSettingsDetails := meth(self, key)
		local retrec;

		if not IsString(key) then
			return rec();
		fi;

		retrec := rec(
			(SGKEY_NAME)		:= key,
			(SGKEY_DISPLAYNAME)	:= self.getDisplayName(key),
		);

		if key = SGKEY_SIZE then
			if self.getSettingsValue(SGKEY_TRANSFORM) in [SGKEY_FFT_2D, SGKEY_IFFT_2D] then
				retrec.(SGKEY_TYPE) := [SGTYPE_INT, SGTYPE_INT];
			else
				retrec.(SGKEY_TYPE) := SGTYPE_INT;
			fi;
			retrec.(SGKEY_MULTIPLEVALUES) := true;
		elif key in [SGKEY_DATATYPE, SGKEY_FILENAME, SGKEY_TRANSFORM] then
			retrec.(SGKEY_TYPE) := SGTYPE_STRING;
		fi;

		return retrec;
	end,

	getValidValues := meth(self, key)
		if (key = SGKEY_TRANSFORM) then
			return Cond(IsBound(self._validTransforms), self._validTransforms(), []);
		elif (key = SGKEY_SIZE) then
			return Cond(IsBound(self._validSizes), self._validSizes(), []);
		elif (key = SGKEY_DATATYPE) then
			return Cond(IsBound(self._validTypes), self._validTypes(), []);
		else
			return [];
		fi;
	end,

	getDisplayName := key -> GetScriptGenDisplayName(key),

	setDisplayName := function(key, string)
		if not ( IsString(key) and IsString(string) ) then
			Error("usage: setDisplayName(key, string)\n  both <key> and <string> must be strings");
		fi;

		SetScriptGenDisplayName(key, string);
	end,

    getDocumentation := key -> GetScriptGenDocumentation(key),

	setDocumentation := function(key, string)
		if not ( IsString(key) and IsString(string) ) then
			Error("usage: setDocumentation(key, string)\n  both <key> and <string> must be strings");
		fi;

		SetScriptGenDocumentation(key, string);
	end,

	writeScript := meth(arg)
		local self, key, choiceKey, fname, myPrint, allKeys;

		self := arg[1];
		if Length(arg) > 1 then
			key := arg[2];
		else
			key := SGSTR_ALL;
		fi;

		fname   := self.getSettingsValue(SGKEY_FILENAME);

		if fname = SGSTR_STDOUT then
			myPrint := (arg) -> ApplyFunc(Print, arg);
		else
			# create file or overwrite existing, then use AppendTo for actual writing
			PrintTo(fname, "");
			myPrint := (arg) -> ApplyFunc(AppendTo, Concat([fname], arg));
		fi;

		if key in self.getScriptChoices() then
			myPrint(self._genScript(key));
		elif key = SGSTR_CONSTRUCTOR then
			myPrint("NewScriptGen(\""::self._arch()::"\", ");
			myPrint(self._settings);
			myPrint(");\n");
		elif key = SGSTR_ALL then
			myPrint("\n##! BEGIN CONSTRUCTOR\n\n");
			myPrint("NewScriptGen(\""::self._arch()::"\", ");
			myPrint(self._settings);
			myPrint(");\n");
			myPrint("\n##! END CONSTRUCTOR\n\n");

			for choiceKey in self.getScriptChoices() do
				myPrint("##! BEGIN SCRIPT "::choiceKey::"\n");
				myPrint("# "::self.getDisplayName(choiceKey)::"\n\n");
				myPrint(self._genScript(choiceKey));
				myPrint("\n##! END SCRIPT "::choiceKey::"\n\n");
			od;
		else
			allKeys := Concat([SGSTR_ALL, SGSTR_CONSTRUCTOR], self.getScriptChoices());
			Error("key must be one of: ", allKeys);
		fi;
	end,

	getScriptChoices := (self) >> [SGSTR_RUNRANDOMALL, SGSTR_RUNALL],

	getAllChoicesAsJSON := meth(self)
		local json, indentStr, tree;

		indentStr := "    ";

		json := "{\n" :: indentStr :: "\"isa\" : \"" :: self._arch() :: "\"";

		tree := self._getChoicesDetailsJSON(1, indentStr);

		if Length(tree) > 0 then
			json := json :: ",\n" :: tree;
		fi;

		json := json :: "\n}\n";

		return json;
	end,

	writeJSONChoices := meth(arg)
		local self, file, json;

		self := arg[1];
		if Length(arg) > 1 then
			file := arg[2];
		else
			file := SGSTR_STDOUT;
		fi;

		json := self.getAllChoicesAsJSON();

		PrintTo(file, json);
	end,

    ##
    ## Private methods
	##

	__call__ := meth(arg)
		local self, me, field;
		self := arg[1];

		# create a new instance of the class

		me := WithBases(self, rec(_settings := rec()));

		me._init();

		if IsBound(arg[2]) and IsRec(arg[2]) then
			for field in RecFields(arg[2]) do
				me._settings.(field) := Copy(arg[2].(field));
			od;
		fi;

		return me;
	end,

	# subclass must implement _init()
	_init := (self) >> Error("Cannot instantiate abstract class\n"),

	_setTransform := meth(self, tr)
		local szs;
		if not tr in self._validTransforms() then
			return false;
		fi;
		self._settings.(SGKEY_TRANSFORM) := tr;

		# make sure size is still valid
		self._validateAndFixSize();

		return true;
	end,

	_setType := meth(self, type)
		local szs;
		if not type in self._validTypes() then
			return false;
		fi;
		self._settings.(SGKEY_DATATYPE) := type;

		# make sure size is still valid
		self._validateAndFixSize();

		return true;
	end,

	_setSize := meth(arg)
		local self, sz, szlist, idx, xform;

		if Length(arg) < 2 then return false; fi;

		self := arg[1];
		sz   := arg[2];

		# 1D is a list of one or more valid ints
		# 2D is a list of one or more valid pairs of ints

		xform := self.getSettingsValue(SGKEY_TRANSFORM);
		if xform in [SGKEY_FFT_2D, SGKEY_IFFT_2D] then
			if (not IsList(sz)) or (Length(sz) < 1) then
				return false;
			fi;
			if IsInt(sz[1]) then
				szlist := [ sz ];
			else
				szlist := sz;
			fi;
		else
			if IsList(sz) and (Length(sz) > 0) then
				szlist := sz;
			else
				szlist := [ sz ];
			fi;
		fi;

		if szlist <> self._validateSize(szlist) then
			return false;
		fi;

		self._settings.(SGKEY_SIZE) := szlist;
		return true;
	end,

	_setFilename := meth(self, fname)
		if not IsString(fname) then
			return false;
		fi;
		if Length(fname) = 0 then
			self._settings.(SGKEY_FILENAME) := SGSTR_STDOUT;
		else
			self._settings.(SGKEY_FILENAME) := fname;
		fi;
		return true;
	end,

	_setFuncname := meth(self, fname)
		if not IsString(fname) then
			return false;
		fi;

		### NOTE don't allow whitespace in string

		if Length(fname) = 0 then
			Unbind(self._settings.(SGKEY_FUNCNAME));
		else
			self._settings.(SGKEY_FUNCNAME) := fname;
		fi;
		return true;
	end,

	_validateSize := meth(self, szlist)
		local goodsizes;
		goodsizes := self._validSizes();
		if (Length(szlist) > 0) and ForAll(szlist, s -> s in goodsizes) then
			return szlist;
		fi;

		return Cond(Length(goodsizes) > 0, [ goodsizes[1] ], []);
	end,

	_validateAndFixSize := meth(self)
		local oldsz, newsz;
		oldsz := Cond(IsBound(self._settings.(SGKEY_SIZE)), self._settings.(SGKEY_SIZE), [ 0 ]) ;
		newsz := self._validateSize(oldsz);
		if newsz <> oldsz then
			self._settings.(SGKEY_SIZE) := newsz;
		fi;
	end,

	_getChoicesDetailsJSON := meth(arg)
		local self, level, indentStr, id, descr, json, indent, i, sequence, key, choices, value;

		if Length(arg) < 3 then
			return "";
		fi;
		self := arg[1];
		level := arg[2];
		indentStr := arg[3];
		id    := Cond(IsBound(arg[4]) and IsString(arg[4]), arg[4], "");
		descr := self.getDisplayName(id);

		indent := "";
		for i in [2..level] do
			indent := indent :: indentStr :: indentStr;
		od;

		sequence := self.getSettingsSequence();
		if level > Length(sequence) then return ""; fi;
		key := sequence[level];

		if level > 1 then
			json := indent :: indentStr :: "\"ident\" : \"" :: id :: "\",\n";
			json := json :: indent :: indentStr :: "\"descr\" : \"" :: descr :: "\",\n";
		else
			json := "";
		fi;

		json := json :: indent :: indentStr :: "\"choice_name\" : \"" :: key :: "\",\n";
		json := json :: indent :: indentStr :: "\"choice_display\" : \"" :: self.getDisplayName(key) :: "\",\n";

		choices := self.getValidValues(key);

		if level >= Length(sequence) then
			return json :: indent :: indentStr :: "\"choices\" : " :: String(choices);
		fi;

		json := json :: indent :: indentStr :: "\"choices\" : [\n";

		for i in [1..Length(choices)] do
			json := json :: indent :: indentStr :: indentStr :: "{\n";

			value := choices[i];
			self.setSettingsValue(key, value);
			json := json :: self._getChoicesDetailsJSON(level+1, indentStr, value) :: "\n";

			json := json :: indent :: indentStr :: indentStr :: Cond(i < Length(choices), "},\n", "}\n");
		od;

		json := json :: indent :: indentStr :: "]";

		return json;
	end,

	_genScript := (self, runType) >> "",

	_localSupport := () -> true,

)); # Class ScriptGen

_constructors := rec();

SetScriptGenConstructor := function(gen)
	if IsCallable(gen) and IsBound(gen._arch) then
		_constructors.(gen._arch()) := gen;
	else
		Error("Invalid argument\n");
	fi;
end;


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

_sum2 := function(lst)
    local i, res;
    res := 0;
    for i in [2..Length(lst)] do
        res := res + lst[i][1] + lst[i][2];
    od;
    return res;
end;

# objhead = [ objid_addr, param1_uid, param2_uid, ... ]
# uid is unique position in the hashtable returned by HashAdd
Class(ObjHashBase, HashTable(
    (objhead,size) -> When(IsInt(objhead), 1 + (objhead mod size),
                       1 + ((objhead[1] + _sum2(objhead)) mod size)),
    (objhead1, objhead2) -> objhead1 = objhead2)
);

ObjHashBase.operations := WithBases(HashOps, rec(
    Print := o -> Print(o.name)
));

Class(ObjHash, ObjHashBase, rec(
    liveEntries := self >> Filtered(self.entries, True),
    numLiveEntries := self >> Sum(List(self.liveEntries(), Length)),

    nonrecLookupAdd := meth(self, o)
        local lkup;
	lkup := HashLookupUID(self, InternalHash(o));
	if Same(lkup, false) then
	    return HashAdd(self, InternalHash(o), o);
	else return lkup;
	fi;
    end,

    listLookupAdd := meth(self, o)
        local lkup, uids;
	uids := [T_LIST];
	Append(uids, List(o, e -> self.uidObj(e)));
	lkup := HashLookupUID(self, uids);
	if Same(lkup, false) then
	    return HashAdd(self, uids, o);
	else return lkup;
	fi;
    end,

    uidObj := (self,o) >> Cond(
    IsRec(o) and IsBound(o.uid), o.uid,
    IsRec(o), Error("Unhashed object <o>"),
    IsList(o), self.listLookupAdd(o),
    self.nonrecLookupAdd(o)),

    objAdd := meth(self, res, h)
        local uid;
    uid := HashAdd(self, h, res);
    res.h := h;
    res.uid := uid;
    return res;
    end,

    singletonAdd := (self, o) >> self.objAdd(o, [BagAddr(o)]),

    _prList := meth(self, lst)
        local p;
    for p in lst do
            if IsRec(p) then Print(ObjId(p));
        elif IsList(p) then Print("(",self._prList(p), ")");
        else Print(p);
        fi;
        Print(":",self.uidObj(p), " ");
        od;
    end,

    debug := true,

    objLookup := meth(self, objid, params)
        local h,lkup,hcodes;
	h := [BagAddr(objid)];
	Append(h, List(params, p -> self.uidObj(p)));
	lkup := HashLookup(self, h);

	if self.debug then
	    if Same(lkup,false) then
		Print("(", objid, " ", self._prList(params), ")\n");
	    else
		Print(objid, " : hit ", lkup.uid, "\n");
	    fi;
	fi;

	return [lkup,h];
    end,

    memClassFunc := meth(self, cls, orig, bck)
        Constraint(IsBound(cls.(orig)));
        #Constraint(not IsBound(cls.__call_no_memo__));
    cls.(bck) := cls.(orig);
    cls._hash := self;
    bck := RecName(bck); # this will make lookup a bit faster

    cls.(orig) := meth(arg)
            local clsself, params, lkup, res, h;
        clsself := arg[1]; params := Drop(arg,1);
        h := clsself._hash;
        if h<>false then
        lkup := h.objLookup(clsself, params);
        if lkup[1] <> false then return lkup[1]; fi;
        fi;
        res := ApplyFunc(clsself.(bck), params);
        if h<>false then
        return h.objAdd(res, lkup[2]);
        else return res;
        fi;
    end;
    end,

    memClass := (self,cls) >> self.memClassFunc(cls, "__call__", "__call_no_memo__")
));


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

# the rule set to promote nonterminals
Class(RulesFFTXPromoteNT, RuleSet);

Class(RulesFFTXPromoteNT_Cleanup, RuleSet);

#RewriteRules(RulesFFTXPromoteNT, rec(
#    IPRDFT_RCDiag_PRDFT__Circulant_Rule := Rule([Compose, @(1,IPRDFT), [@(2,RCDiag), @(4, FDataOfs, e->e.ofs = 0), @(5,I)], @(3,PRDFT)],
#        e->let(n := @(1).val.params[1], fdata := @(2).val.element,
#            Circulant(n, FDataNT(fdata.var, @(1).val), -n))
#    )
#));

_toSymList := l -> [Minimum(l)..Maximum(l)];

RewriteRules(RulesFFTXPromoteNT, rec(
#   batch1_MDXFFT := Rule(@@(1, [MDDFT, MDPRDFT, IMDPRDFT, TColMajor],
#       (e,cx) -> ForAll([TTensorI, TColMajor, TDAG, TDAGNode, HStack, VStack, Compose], i -> (not IsBound(cx.(i.name)) or cx.(i.name) = []))),
#       e-> TTensorI(@@(1).val, 1, AVec, AVec)),

    Scat_Circulant_Gath__IOPrunedRConv := ARule(Compose, [[@(1, Gath), fAdd],  @(2,Circulant), [@(3, Scat), fAdd]],
        e-> [IOPrunedRConv(@(2).val.params[1],
            FDataOfs(@(2).val.params[2].var, 2*(@(2).val.params[1]/2+1), 0),
            1, _toSymList(List(@(1).val.func.tolist(), _unwrap)),
            1, _toSymList(List(@(3).val.func.tolist(), _unwrap)), true)]),

# This is one mega promotion rule for Hockney that needs to be broken apart after MDRConv and/or PrunedMDRDFT is introduced
    Hockney_hack := ARule(Compose, [[@(6, Gath), @(8,fTensor, e->ForAll(e.children(), i->ObjId(i)=fAdd))],
                                     @(1,IMDPRDFT, e -> e.params[2] = 1), [@(2,RCDiag), @(4, FDataOfs, e->e.ofs = 0), @(5,I)],
                                     @(3,MDPRDFT, e -> e.params[2] = Product(e.params[1])-1),
                                     [@(7, Scat), @(9,fTensor, e->ForAll(e.children(), i->ObjId(i)=fAdd))]],
        e-> let(ii := Ind(Rows(@(2).val)),
            sym := @(2).val.element.var,
            symf := Lambda(ii, nth(sym,ii)),
            opat := List(@(8).val.children(), i-> _toSymList(List(i.tolist(), _unwrap))),
            ipat := List(@(9).val.children(), i-> _toSymList(List(i.tolist(), _unwrap))),
        [ IOPrunedMDRConv(@(1).val.params[1], symf, 1, opat, 1, ipat, true) ])),

# This is one mega promotion rule for Hockney that needs to be broken apart after MDRConv and/or PrunedMDRDFT is introduced
    Hockney_hack2 := ARule(Compose, [[@(6, Gath), @(8,fTensor, e->ForAll(e.children(), i->ObjId(i)=fAdd))],
                                     @(1,IMDPRDFT, e -> e.params[2] = 1), [@(2,RCDiag), @(4, FData), @(5,I)],
                                     @(3,MDPRDFT, e -> e.params[2] = Product(e.params[1])-1),
                                     [@(7, Scat), @(9,fTensor, e->ForAll(e.children(), i->ObjId(i)=fAdd))]],
        e-> let(ii := Ind(Rows(@(2).val)),
            sym := @(2).val.element.var,
            symf := Lambda(ii, nth(sym,ii)),
            opat := List(@(8).val.children(), i-> _toSymList(List(i.tolist(), _unwrap))),
            ipat := List(@(9).val.children(), i-> _toSymList(List(i.tolist(), _unwrap))),
        [ IOPrunedMDRConv(@(1).val.params[1], symf, 1, opat, 1, ipat, true) ])),

# DAG to Compose rules
    DAG_collapse1 := Rule([@(1, TDAG), ..., @(2, TDAGNode, e->ForAny(Drop(@(1).val.params[1], 1), k->k.params[3] = e.params[2])), ...],
        e->  let(nbr := Filtered(@(1).val.params[1], k->k.params[3] = @(2).val.params[2])[1],
                 ch := Filtered(Drop(@(1).val.params[1],1) , i -> i<> nbr),
                 dn := TDAGNode(nbr.params[1] * @(2).val.params[1], nbr.params[2], @(2).val.params[3]),
                 TDAG([dn]::ch))),
    DAG_remove := Rule([@(1,TDAG), [@(2, TDAGNode), @(3),
            @(4).cond(e->IsList(e) and Length(e) = 1 and let(_e := e[1].free(), Length(_e) = 1 and ObjId(_e[1]) = var and _e[1].id in ["Y", "Yptr"] and Collect(e[1], TColMaj) = [])),
            @(5).cond(e->IsList(e) and Length(e) = 1 and let(_e := e[1].free(), Length(_e) = 1 and ObjId(_e[1]) = var and _e[1].id in ["X", "Xptr"] and Collect(e[1], TColMaj) = [])),
            ...],...],
        e-> @(2).val.params[1]),

    propagate_ColMaj := Rule([@(1,TDAGNode), [TRC, @(2,MDDFT),...],
            @(3).cond(e->ObjId(e[1])=tcast and ObjId(e[1].args[1].t) = TColMaj),
            @(4).cond(e->ObjId(e[1])=tcast and ObjId(e[1].args[1].t) = TColMaj),...],
        e -> TDAGNode(TRC(TColMajor(@(2).val)), @(3).val[1].args[2], @(4).val[1].args[2])),

    Drop_TDecl := Rule(@(1, TDecl, e->ForAll(e.params[2], i->not i in e.params[1].free())),
        e -> @(1).val.params[1]),

    Drop_TTensorI := Rule(@(1, TTensorI, e -> e.params[2] = 1), e -> e.params[1])
));

RewriteRules(RulesFFTXPromoteNT_Cleanup, rec(
# FIXME: the promotion rule does not (yet) have the guard to ensure the value of k is correct
    IPRDFT_RCDiag_PRDFT__Circulant_ARule := ARule(Compose, [@(1,IPRDFT, e -> e.params[2] = 1), [@(2,RCDiag),
            @(4, FDataOfs, e->e.ofs = 0), @(5,I)], @(3,PRDFT, e -> e.params[2] = e.params[1]-1)],
        e->let(n := @(1).val.params[1], fdata := @(2).val.element,
            [Circulant(n, FDataNT(fdata.var, @(1).val), -n) ])),

# FIXME: the promotion rule does not (yet) have the guard to ensure the value of k is correct
    IMDPRDFT_RCDiag_MDPRDFT__RConv_ARule := ARule(Compose, [@(1,IMDPRDFT, e -> e.params[2] = 1), [@(2,RCDiag), @(4, FDataOfs, e->e.ofs = 0), @(5,I)],
            @(3,MDPRDFT, e -> e.params[2] = Product(e.params[1])-1)],
        e->let(n := @(1).val.params[1], fdata := @(2).val.element,
            [MDRConv(n, fdata.var, true) ])),

# FIXME: the promotion rule does not (yet) have the guard to ensure the value of k is correct
    IMDPRDFT_Diag_MDPRDFT__RConv_ARule := ARule(Compose, [@(1,IMDPRDFT, e -> e.params[2] = 1), [@(2,Diag), [diagTensor, @(4, FDataOfs, e->e.ofs = 0), @(5,fConst, e->e.params = [ TReal, 2, 1 ])]],
            @(3,MDPRDFT, e -> e.params[2] = Product(e.params[1])-1)],
        e->let(n := @(1).val.params[1], fdata := @(4).val,
            [MDRConvR(n, fdata.var, true) ])),

# MDDFT * Scat -> PrunedMDDFT
    MDDFT_Scat__PrunedMDDFT := ARule(Compose, [@(1,MDDFT), [@(2, Scat), @(3,fTensor, e->ForAll(e.children(), i->ObjId(i)=fAdd))]],
        e -> [ PrunedMDDFT(@(1).val.params[1], @(1).val.params[2], 1,  List(@(3).val.children(), i-> _toSymList(List(i.tolist(), _unwrap))))] ),

# Gath * MDDFT -> PrunedIMDDFT
    Gath_MDDFT__PrunedIMDDFT := ARule(Compose, [[@(1, Gath), @(2,fTensor, e->ForAll(e.children(), i->ObjId(i)=fAdd))], @(3, MDDFT)],
        e -> [PrunedIMDDFT(@(3).val.params[1], @(3).val.params[2], 1,  List(@(2).val.children(), i-> _toSymList(List(i.tolist(), _unwrap))))]),

# PRDFT * Scat -> PrunedPRDFT
    PRDFT_Scat__PrunedPRDFT := ARule(Compose, [@(1,PRDFT), [@(2, Scat), @(3,fAdd)]],
        e -> [ PrunedPRDFT(@(1).val.params[1], @(1).val.params[2], 1,  _toSymList(List(@(3).val.tolist(), _unwrap)))] ),

# MDPRDFT * Scat -> PrunedMDPRDFT
    MDPRDFT_Scat__PrunedMDPRDFT := ARule(Compose, [@(1,MDPRDFT), [@(2, Scat), @(3,fTensor, e->ForAll(e.children(), i->ObjId(i)=fAdd))]],
        e -> [ PrunedMDPRDFT(@(1).val.params[1], List(@(3).val.children(), i-> _toSymList(List(i.tolist(), _unwrap))), @(1).val.params[2])] ),

# Gath * IMDPRDFT -> PrunedIMDPRDFT
    Gath_IMDPRDFT__PrunedIMDPRDFT := ARule(Compose, [[@(1, Gath), @(2,fTensor, e->ForAll(e.children(), i->ObjId(i)=fAdd))], @(3, IMDPRDFT)],
        e -> [ PrunedIMDPRDFT(@(3).val.params[1], List(@(2).val.children(), i-> _toSymList(List(i.tolist(), _unwrap))), @(3).val.params[2])] ),

# Gath * PRDFT -> PrunedIPRDFT
    Gath_PRDFT__PrunedIPRDFT := ARule(Compose, [[@(1, Gath), @(2,fAdd)], @(3, IPRDFT)],
        e -> [PrunedIPRDFT(@(3).val.params[1], @(3).val.params[2], 1,  _toSymList(List(@(2).val.tolist(), _unwrap)))]),

    Compose_TCompose := Rule(@@(1, Compose, (e,cx) -> IsBound(cx.TFCall) and Length(cx.TFCall) = 1 and cx.TFCall[1].hasTags()),
        e -> TCompose(e.children()))
));

# WarpX stuff
Class(RulesFFTXPromoteWarpX1, RuleSet);
RewriteRules(RulesFFTXPromoteWarpX1, rec(
    MultiX := Rule([@(0, TDAGNode), @(1), @(2), @(3).cond(e->IsList(e) and Length(e) = 1 and ObjId(e[1]) = nth and e[1].loc = X), ...],
        e -> TDAGNode(@(1).val * GathPtr(@(3).val[1], fId(Cols(@(1).val))), @(2).val, [ @(3).val[1].loc ]).withTags(@(0).val.getTags())),

    MultiY := Rule([@(0, TDAGNode), @(1), @(2).cond(e->IsList(e) and Length(e) = 1 and ObjId(e[1]) = nth and e[1].loc = Y), @(3), ...],
        e -> TDAGNode(ScatPtr(@(2).val[1], fId(Rows(@(1).val))) * @(1).val, [ @(2).val[1].loc ], @(3).val).withTags(@(0).val.getTags())),
));

Class(RulesFFTXPromoteWarpX2, RuleSet);
RewriteRules(RulesFFTXPromoteWarpX2, rec(
    TDAGNode_VStack1 := Rule([@(1, TDAG), ..., @(2, TDAGNode,
            e-> IsList(e.params[2]) and Length(e.params[2]) = 1 and ObjId(e.params[2][1]) = nth and e.params[2][1].loc <> Y and _unwrap(e.params[2][1].idx) = 0
                and not ObjId(e.params[3][1]) = nth),
            ...],
        e -> let(
            vr := @(2).val.params[2][1].loc,
            ch := Filtered(@(1).val.params[1], c->IsList(c.params[2]) and Length(c.params[2]) = 1 and ObjId(c.params[2][1]) = nth and c.params[2][1].loc = vr
                     and c.params[3] = @(2).val.params[3]),
            sl := Flat(SortRecordList(ch, c->_unwrap(c.params[2][1].idx))),
            spls := List(sl, i-> i.params[1]),
            vstk := VStack(spls),
            dn := TDAGNode(vstk, [vr], @(2).val.params[3]),
            nch := Filtered(@(1).val.params[1], c->(not IsList(c.params[2])) or (not Length(c.params[2]) = 1) or (not ObjId(c.params[2][1]) = nth) or (not c.params[2][1].loc = vr)
                     or (not c.params[3] = @(2).val.params[3])),
            TDAG([dn]::nch).withTags(@(1).val.tags)
        )),

    TDAGNode_VStack2 := Rule([@(1, TDAG), ..., @(2, TDAGNode,
            e-> IsList(e.params[3]) and Length(e.params[3]) = 1 and ObjId(e.params[3][1]) = nth and e.params[3][1].loc <> X and _unwrap(e.params[3][1].idx) = 0
                and not ObjId(e.params[2][1]) = nth),
            ...],
        e -> let(
            vr := @(2).val.params[3][1].loc,
            ch := Filtered(@(1).val.params[1], c->IsList(c.params[3]) and Length(c.params[3]) = 1 and ObjId(c.params[3][1]) = nth and c.params[3][1].loc = vr
                     and c.params[2] = @(2).val.params[2]),
            sl := Flat(SortRecordList(ch, c->_unwrap(c.params[3][1].idx))),
            spls := List(sl, i-> i.params[1]),
            hstk := HStack(spls),
            dn := TDAGNode(hstk,  @(2).val.params[2], [vr]),
            nch := Filtered(@(1).val.params[1], c->(not IsList(c.params[3])) or (not Length(c.params[3]) = 1) or (not ObjId(c.params[3][1]) = nth) or (not c.params[3][1].loc = vr)
                     or (not c.params[2] = @(2).val.params[2])),
            TDAG(nch::[dn]).withTags(@(1).val.tags)
        )),

   TResample_TGath := Rule(@(1, TResample, e->TResample_TGath.applicable(e)),
       e->let(l := @(1).val,
              c := TResample_TGath.children(l)[1],
              TResample_TGath.apply(l, c, c)
           )),

   TResample_TScat := Rule(@(1, TResample, e->TResample_TScat.applicable(e)),
       e->let(l := @(1).val,
              c := TResample_TScat.children(l)[1],
              TResample_TScat.apply(l, c, c)
           )),

   TResample_MD_nofrac := Rule(@(1, TResample, e->TResample_MD_nofrac.applicable(e)),
       e->let(l := @(1).val,
              c := TResample_MD_nofrac.children(l)[1],
              TResample_MD_nofrac.apply(l, c, c)
           )),

   TGath_Gath := Rule(@(1, TGath, e-> TGath_base.applicable(e)), e->ApplyRuleSPL(TGath_base, @(1).val)),

   TScat_Scat := Rule(@(1, TScat, e-> TScat_base.applicable(e)), e->ApplyRuleSPL(TScat_base, @(1).val)),

   Gath_GathPtr := ARule(Compose, [@(1, Gath, e-> ObjId(e.func) = fTensor and ForAll(e.func.children(), c->ObjId(c) = fId or (ObjId(c) = fAdd and c.params[3] = 0))), @(2, GathPtr)],
       e->[ GathPtr(@(2).val.ptr, fId(Rows(@(1).val))) ]),

   ScatPtr_Scat := ARule(Compose, [@(1, ScatPtr), @(2, Scat, e-> ObjId(e.func) = fTensor and ForAll(e.func.children(), c->ObjId(c) = fId or (ObjId(c) = fAdd and c.params[3] = 0)))],
       e->[ ScatPtr(@(1).val.ptr, fId(Cols(@(2).val))) ]),

   Expand_TResample := Rule(@(1, TResample, e->TResample_MD_frac.applicable(e)),
       e->let(trs := @(1).val,
               c := TResample_MD_frac.children(trs)[1],
               TResample_MD_frac.apply(trs, c, c)
           )),
));

Class(RulesFFTXPromoteWarpX3, RuleSet);
RewriteRules(RulesFFTXPromoteWarpX3, rec(
    Merge_TTensorI_VStack := ARule(Compose, [ @(1, TTensorI, e->e.params[3] = APar and e.params[4] = APar),
        @(2, VStack, e->@(1).val.params[2] = Length(e.children()) and ForAll(e.children(), e->Rows(e) = Cols(@(1).val.params[1]))) ],
        e->[VStack(List(@(2).val.children(), c->@(1).val.params[1] * c))]),

    Merge_HStack_TTensorI := ARule(Compose, [ @(1, HStack),
        @(2, TTensorI, e->e.params[3] = APar and e.params[4] = APar and
            e.params[2] = Length(@(1).val.children()) and ForAll(@(1).val.children(), j->Cols(j) = Rows(e.params[1]))) ],
        e->[HStack(List(@(1).val.children(), c->c * @(2).val.params[1]))]),

# this rule is WRONG: the sign needs to be inverse of each other!
    MDPRDFT_IMDPRDFT := ARule(Compose, [@(1, MDPRDFT), @(2, IMDPRDFT, e->Length(@(1).val.params[1]) = Length(e.params[1]) and Mod(@(1).val.params[2] + e.params[2], Product(e.params[1])) = 0 and
        ForAll([1..Length(e.params[1])], j->e.params[1][j] = @(1).val.params[1][j]))],
        e->[Diag(fConst(TReal, Rows(@(1).val), Product(@(1).val.params[1])))]),

    Diag_RCDiag := ARule(Compose, [@(1, Diag, e->ObjId(e.element) = fConst and e.element.range() = TReal), @(2, RCDiag)],
        e -> [RCDiag(diagMul(@(1).val.element, @(2).val.element))]),

    RCDiag_Diag := ARule(Compose, [@(1, RCDiag), @(2, Diag, e->ObjId(e.element) = fConst and e.element.range() = TReal)],
        e -> [RCDiag(diagMul(@(2).val.element, @(1).val.element))]),

 # these rules need a bit more guards to not misfire
    Peel_HStack_RCDiag := Rule(@(1, HStack, e->ForAll(e.children(), c->let(cc := c.children(), Length(cc) = 3 and
            ObjId(cc[1]) = ScatPtr and ObjId(cc[2]) = IMDPRDFT and ObjId(cc[3]) = RCDiag  and ObjId(cc[3].element) = RCData))),
        e -> let(cc := @(1).val.children(), i := Ind(Length(cc)), c1 := cc[1].children()[1], c2 := cc[1].children()[2],
#                Error(),
                fts := Flat(List(cc, c->Collect(c, fTensor))),
#                rng := List(fts, f->List([1..Length(f.children())], ii->f.child(ii).range())),
                _rng := Flat(List(fts, f->List([1..Length(f.children())], ii->f.child(ii).range()))),
#                rngf := FData(rng),
                _rngf := FData(_rng),
                dom := fts[1].child(1).domain(),
#                fts2 := fTensor(List([0..Length(fts[1].children())-1],  ii->fAdd(nth(rngf.at(i), ii), dom, 0))),
                _fts2 := fTensor(List([0..Length(fts[1].children())-1],  ii->fAdd(_rngf.at(Length(fts[1].children()) * i + ii), dom, 0))),
            TNoDiagPullinRight(TIterHStack(TCompose([ScatPtr(nth(c1.ptr.loc, i), _fts2), c2]), i)) *
                TNoPullLeft(RCDiag(RCData(diagDirsum(List(cc, c->Last(c.children()).element.func))))))),

    Peel_RCDiag_VStack := Rule(@(1, VStack, e->ForAll(e.children(), c->let(cc := Reversed(c.children()), Length(cc) in [2, 3] and
            ObjId(cc[1]) = GathPtr and ObjId(cc[2]) = MDPRDFT and When(Length(cc) = 3, ObjId(cc[3]) = RCDiag and ObjId(cc[3].element) = RCData, true)))),
        e-> let(cc := @(1).val.children(), i := Ind(Length(cc)), rcc := Reversed(cc[1].children()),
                fts := Flat(List(cc, c->Collect(c, fTensor))),
#                rng := List(fts, f->List([1..Length(f.children())], ii->f.child(ii).range())),
                _rng := Flat(List(fts, f->List([1..Length(f.children())], ii->f.child(ii).range()))),
#                rngf := FData(rng),
                _rngf := FData(_rng),
                dom := fts[1].child(1).domain(),
#                fts2 := fTensor(List([0..Length(fts[1].children())-1],  ii->fAdd(nth(rngf.at(i), ii), dom, 0))),
                _fts2 := fTensor(List([0..Length(fts[1].children())-1],  ii->fAdd(_rngf.at(Length(fts[1].children()) * i + ii), dom, 0))),

            TNoPullRight(RCDiag(RCData(diagDirsum(List(cc, c->When(ObjId(c.children()[1]) = RCDiag, c.children()[1].element.func,
                fConst(TComplex, Rows(rcc[2])/2, V(1)))))))) *
            TNoDiagPullinLeft(TIterVStack(TCompose([rcc[2], GathPtr(nth(rcc[1].ptr.loc, i), _fts2)]), i))
        )),

    Fuse_Diag := ARule(Compose, [ @(1, TNoPullLeft, e->ObjId(e.params[1]) = RCDiag), @(2, TRC), @(3, TNoPullRight, e->ObjId(e.params[1]) = RCDiag)],
        e->[ TRC(TGrp(TCompose([Diag(@(1).val.params[1].element.func), @(2).val.params[1], Diag(@(3).val.params[1].element.func)])))]),

    Promote_Compose := Rule(@(1, Compose, e->ForAll(e.children(), c->ObjId(c) in [TRC, TNoDiagPullinLeft, TNoDiagPullinRight])),
        e->TCompose(@(1).val.children())),

    flatten_I := Rule(@(1, Tensor, e->ForAll(e.children(), c->ObjId(c)=I)), e->I(Rows(@(1).val))),

));

RewriteRules(RulesSums, rec(
 PullInRightScatPtr := ARule( Compose,
       [ @(1, [ScatPtr, TNoPullLeft]),
         @(2, [RecursStep, Grp, BB, SUM, Buf, ISum, Data, COND, TNoDiagPullin, TNoDiagPullinLeft, TNoDiagPullinRight, NeedInterleavedComplex, SIMTISum]) ],
    e -> [ CopyFields(@(2).val, rec(
             _children :=  List(@(2).val._children, c -> @(1).val * c),
             dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),

 PullInLeftGathPtr := ARule( Compose,
       [ @(1, [RecursStep, Grp, BB, SUM, SUMAcc, Buf, ISum, ISumAcc, Data, COND, TNoDiagPullin, TNoDiagPullinLeft, TNoDiagPullinRight, NeedInterleavedComplex, SIMTISum]),
         @(2, [GathPtr, TNoPullRight]) ],
    e -> [ CopyFields(@(1).val, rec(
                _children := List(@(1).val._children, c -> c * @(2).val),
                dimensions := [Rows(@(1).val), Cols(@(2).val)] )) ]),
 ComposeGathGathPtr := ARule(Compose, [ @(1, Gath), @(2, GathPtr) ], # o 1-> 2->
     e -> [ GathPtr(@(2).val.ptr, fCompose(@(2).val.func, @(1).val.func)) ]),

 ComposeScatPtrScat := ARule(Compose, [ @(1, ScatPtr), @(2, Scat) ], # <-1 <-2 o
     e -> [ ScatPtr(@(1).val.ptr, fCompose(@(1).val.func, @(2).val.func)) ]),

));

# IOPrunedRConv(@(2).val.params[1], @(2).val.params[2], 1, _toSymList(List(@(1).val.func.tolist(), _unwrap)), 1, _toSymList(List(@(3).val.func.tolist(), _unwrap)));
# IOPrunedRConv(@(2).val.params[1], FDataOfs(@(2).val.params[2].var, 2*(@(2).val.params[1]/2+1), 0), 1, _toSymList(List(@(1).val.func.tolist(), _unwrap)), 1, _toSymList(List(@(3).val.func.tolist(), _unwrap)), true);


Reversed("abcdef");
# "fedcba"


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

EmptyCS := [
    Compile.pullDataDeclsRefs,
    Compile.declareVars
];

BaseCS := [
    c -> Compile.pullDataDeclsRefs(c),
    c -> Compile.fastScalarize(c),
    c -> UnrollCode(c),
    c -> FlattenCode(c),
    c -> UntangleChain(c),
    CopyPropagate,
    (c, opts) -> HashConsts(c, opts),
];

NoCSE := Concatenation(BaseCS, [
    Compile.declareVars
]);

NoSchedCSE_CS := Concatenation(BaseCS, [
    (c, opts) -> BinSplit(c, opts),
    CSE,  MarkDefUse, CopyPropagate,
    Compile.declareVars
]);

SimpleCS := Concatenation(BaseCS, [
    (c, opts) -> BinSplit(c, opts),
    CSE, MarkDefUse, DFSChain, CopyPropagate,
    Compile.declareVars
]);

# IsCoarseType: checks if <coarse_t> is more general version of <fine_t> data type as defined by UnifyPair.
#        ex: TReal is a general version of T_Real(32) data type.
IsCoarseType := (coarse_t, fine_t) -> When(
    coarse_t = fine_t, false,
    Try(UnifyPair(fine_t, coarse_t)) = [true, fine_t]
);

# FixValueTypes: unifies value type with the type of surrounding expression.
#        SSE unparser needs this for figuring out actual data type of constants.
#        Fixed point backends rely on this to convert constants to fixed point...
#
FixValueTypes := c -> SubstTopDownRulesNR(c, rec(
    fixValueTypes := Rule(
        @@(1, Value, (x, cx) ->
            ObjId(Last(cx.parents)) in
	        [add, sub, mul, bin_and, bin_xor, bin_or, absdiff, absdiff2, idiv, ddiv] and
		IsCoarseType(x.t, Last(cx.parents).t) and not IsPtrT(Last(cx.parents).t)),
	(e, cx) -> Last(cx.parents).t.value(e.v)
    )
));

DerefNthCode := c -> SubstTopDownRules(c, rec(
    deref_nth := Rule(
	[nth, @(1).cond(e -> not(ObjId(e) in [Value, param])), @(2)],
	e -> let(
	    b := @(1).val, idx := @(2).val,
	    Cond(
		ObjId(idx) = add, deref(ApplyFunc(add, [b] :: idx.args)),
		ObjId(idx) = sub, deref(ApplyFunc(add, [b] :: [idx.args[1], neg(idx.args)])),
                                  deref(b + idx))))
));

NthDerefCode := c -> SubstTopDownRules(c, rec(
    deref_var := Rule([deref, @(1, var)], e -> nth(@(1).val, TInt.value(0))),
    deref_add := Rule([deref, [add, @(1, var), @(2, Value)]], e -> nth(@(1).val, @(2).val))
));

BaseIndicesCS := [
    c -> Compile.pullDataDeclsRefs(c),
    c -> Compile.fastScalarize(c),
    c -> UnrollCode(c),
    c -> FlattenCode(c),
    c -> UntangleChain(c),
    (c, opts) -> CopyPropagate.initial(c, opts),
    (c, opts) -> HashConsts(c, opts),
    c -> MarkDefUse(c),
    (c, opts) -> BinSplit(c, opts),
    c -> MarkDefUse(c),
    CopyPropagate, # does CSE
];

# Uses a fast (no strength reduction) final CopyPropagate pass, which
# kicks out vars used only once. Sometimes (MMM?) its not good (prevents hoisting)
# and then IndicesCS2 should be used.
#
IndicesCS0 := Concatenation(BaseIndicesCS, [
    c -> MarkDefUse(c),
    # kicks out vars used only once or never
    (c, opts) -> CopyPropagate.fast(c, CopyFields(opts, rec(autoinline := true))),
    (c, opts) -> Cond(opts.finalBinSplit, BinSplit(c, opts), c),
    (c, opts) -> Cond(IsBound(opts.scheduler), opts.scheduler(c, opts), c),
    c -> Compile.declareVars(c),
]);

# Uses a full final CopyPropagate pass, which kicks out vars used only once
# and properly simplifies out redundant double butterfly structures, i.e. F(2)*F(2)
#
IndicesCS := Concatenation(BaseIndicesCS, [
    c -> MarkDefUse(c),
    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))),
    c -> MarkDefUse(c),
    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))),
    (c, opts) -> Cond(opts.finalBinSplit, BinSplit(c, opts), c),
    (c, opts) -> Cond(IsBound(opts.scheduler), opts.scheduler(c, opts), c),
    c -> FixValueTypes(c),
    c -> Compile.declareVars(c)
]);

# IndicesCS + extra CopyPropagate pass to do DAG pruning
IndicesCS_Prune := Concatenation(BaseIndicesCS, [
    c -> MarkDefUse(c),
    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))),
    c -> MarkDefUse(c),
    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))),
    (c, opts) -> Cond(opts.finalBinSplit, BinSplit(c, opts), c),
    (c, opts) -> Cond(IsBound(opts.scheduler), opts.scheduler(c, opts), c),
    c -> FixValueTypes(c),
    c -> Compile.declareVars(c)
]);

# Does not use a final CopyPropagate pass, to keep variables used once, and
# not prevent hoisting.
#
IndicesCS2 := Concatenation(BaseIndicesCS, [
    # - kicking out variables never used is fine.
    # - kicking out variables used once should be a GLOBAL pass because
    # - right now it prevents hoisting
    (c, opts) -> Cond(opts.finalBinSplit, BinSplit(c, opts), c),
    (c, opts) -> Cond(IsBound(opts.scheduler), opts.scheduler(c, opts), c),
    c -> FixValueTypes(c),
    c -> Compile.declareVars(c)
]);

IndicesCS_FMA := Concatenation(BaseIndicesCS, [
    DoFMA,
    MarkDefUse, #
    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))),
    (c, opts) -> Cond(opts.finalBinSplit, BinSplit(c, opts), c),
    (c, opts) -> Cond(IsBound(opts.scheduler), opts.scheduler(c, opts), c),
    c -> FixValueTypes(c),
    c -> Compile.declareVars(c)
]);

IndicesCS_Fixed := (bitwidth, fracbits) -> (
    IndicesCS ::
    [ c -> FixedPointCode(c, bitwidth, fracbits) ]
);

IndicesCS_FixedNew :=
    IndicesCS ::
    [ (c, opts) -> FixedPointCode2(c) ];

IndicesCS2_FMA := Concatenation(BaseIndicesCS, [
    DoFMA,
    CopyPropagate,
    (c, opts) -> Cond(opts.finalBinSplit, BinSplit(c, opts), c),
    (c, opts) -> Cond(IsBound(opts.scheduler), opts.scheduler(c, opts), c),
    c -> FixValueTypes(c),
    c -> Compile.declareVars(c)
]);

IndicesCS_CXFMA := Concatenation(BaseIndicesCS, [
    DoCXFMA,
    MarkDefUse, #
    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))),
    (c, opts) -> Cond(opts.finalBinSplit, BinSplit(c, opts), c),
    (c, opts) -> Cond(IsBound(opts.scheduler), opts.scheduler(c, opts), c),
    c -> FixValueTypes(c),
    c -> Compile.declareVars(c)
]);

IndicesCS2_CXFMA := Concatenation(BaseIndicesCS, [
    DoCXFMA,
    CopyPropagate,
    (c, opts) -> Cond(opts.finalBinSplit, BinSplit(c, opts), c),
    (c, opts) -> Cond(IsBound(opts.scheduler), opts.scheduler(c, opts), c),
    c -> FixValueTypes(c),
    c -> Compile.declareVars(c)
]);

# OLD STUFF

# RCSE_CS := Concatenation(BaseCS, [
#     (c, opts) -> BinSplit(c, opts), RCSE,
#     MarkDefUse, DFSChain, CopyPropagate,
#     Compile.declareVars
# ]);

# FFTW_CS := Concatenation(BaseCS, [
#     (c, opts) -> BinSplit(c, opts), RCSE,
#     MarkDefUse, FFTWScheduleAssignments, CopyPropagate,
#     Compile.declareVars
# ]);

#
# FMA
#
FMA_CS := Concatenation(BaseCS, [
    (c, opts) -> BinSplit(c, opts), (c, opts) -> BinSplit(c, opts), CSE,
    MarkDefUse, DFSChain, CopyPropagate,
    DoFMA,
    Compile.declareVars
]);

FMA_FFTW_CS := Concatenation(BaseCS, [
    (c, opts) -> BinSplit(c, opts), (c, opts) -> BinSplit(c, opts), CSE,
    MarkDefUse, FMA, ClearDefUse,
    MarkDefUse, FFTWScheduleAssignments, CopyPropagate,
    DoFMA,
    Compile.declareVars
]);

# # seems to be slower
# NewUnrollCS := [
#     myUnrollCode, CopyPropagate,
#     HashConstantsCode,
#     MarkDefUse, DFSChain, CopyPropagate,
#     Compile.declareVars
# ];

# CompileStrategyFull := [
#     Compile.pullDataDeclsRefs,
#     UnrollCode, FlattenCode, SSA,  CopyPropagate,
#     FoldIf, SSA, CopyPropagate,   # Remove dead IF branches
# #    Compile.scalarize,
#     SSA, CopyPropagate,
#     EliminatePhiSSA, CopyPropagate, # Eliminate Phi functions
#     HashConstantsCode,
#     (c, opts) -> BinSplit(c, opts), CSE, CopyPropagate,  # CSE
#     MarkDefUse, #DFSChain,
#     Compile.declareVars
# ];

# This compile strategy is safe for IFs inside basic blocks. but does not
# fully perform copy propagation. Thus the name 'conservative'. To fully and
# safely optimize, we need to extend the CopyPropagate pass
#
conservativeCompileSSA := [
    Compile.pullDataDeclsRefs, # -- 1
    Compile.fastScalarize,     # -- 2
    UnrollCode,    # -- 3
    FlattenCode,   # -- 4
#    SimpIndicesCode, # -- 5   (YSV: simpIndices was disabled before, not clear why)
    FoldIf,        # -- 6   FoldIf happens before SSA and SSA has to happen before Copyprop
    SSA,           # -- 7
    UntangleChain, # -- 8
    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(doScalarReplacement:=false))),  # -- 9
    (c, opts) -> HashConsts(c, opts),                                                  # -- 10
    (c, opts) -> When(IsBound(opts.useDeref) and opts.useDeref, DerefNthCode(c), c), # -- 11
    MarkPreds,
    (c, opts) -> BinSplit(c, opts),                                                        # -- 12, 13
    MarkDefUse,
    (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(doScalarReplacement:=false))), # 14
    EliminatePhiSSA,
    Compile.declareVars
];

CompileSSA := [
    Compile.pullDataDeclsRefs,
    Compile.fastScalarize, UnrollCode,
    FlattenCode,
    FoldIf,
    UntangleChain,
    CopyPropagate,
    SSA,
    (c, opts) -> HashConsts(c, opts),
    (c, opts) -> When(IsBound(opts.useDeref) and opts.useDeref, DerefNthCode(c), c),
    MarkPreds,
    (c, opts) -> BinSplit(c, opts),
    ClearDefUse,
    MarkDefUse,
    CopyPropagate,
    EliminatePhiSSA,
    CopyPropagate,
    Compile.declareVars
];