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

#
# Typ ::
#
# Value ::
#    t = <type>
#    v = <.>
#
# Types:
#   TReal
#   TComplex
#   TInt
#   TArray(<type>, <size>)
#   TVect(<type>, <vlen>)
#
# Value(<type>, <.>)
# V(<.>)   infers type automatically
#

Declare(Value, IsExp, IsValue, TArray, TVect, BitVector, T_UInt);

_evInt := v -> Cond(IsInt(v), v, IsList(v), List(v, i->_evInt(i)), v.ev());

#----------------------------------------------------------------------------------------------
# Typ : data types
#----------------------------------------------------------------------------------------------
Class(TypOps, rec(
    Print := x-> When(IsBound(x.print), x.print(), Print(x.__name__)),
    \= := RewritableObjectOps.\=,
    \< := RewritableObjectOps.\<
));
Class(TypOpsNoPrint, ClassOps, rec(
    \= := RewritableObjectOps.\=,
    \< := RewritableObjectOps.\<
));

Declare(RangeT);

Class(RangeTOps, PrintOps, rec(
    \= := (v1,v2) -> When( ObjId(v1)<>RangeT or ObjId(v2)<>RangeT, false,
        v1.max=v2.max and v1.min=v2.min and v1.eps=v2.eps),
    \< := (v1,v2) -> Error("Operation '<' is undefined for RangeT."),
    \+ := (v1,v2) -> When( ObjId(v1)<>RangeT or ObjId(v2)<>RangeT, Error("'+' is defined for RangeT only"),
        RangeT(Min2(v1.min, v2.min), Max2(v1.max, v2.max), Max2(v1.eps, v2.eps))),
    \* := (v1,v2) -> When( ObjId(v1)<>RangeT or ObjId(v2)<>RangeT, Error("'*' is defined for RangeT only"),
        RangeT(Max2(v1.min, v2.min), Min2(v1.max, v2.max), Max2(v1.eps, v2.eps))),
));

#F RangeT(<min>, <max>, <eps>): data type range
#F  <min> smallest value, <max> largest value, <eps> unit roundoff
Class(RangeT, rec(
    __call__ := (self, min, max, eps) >>
        WithBases(self, rec( min := min, max := max, eps := eps, operations := RangeTOps)),
    print    := self >> Print(self.__name__, "(", self.min, ", ", self.max, ", ", self.eps, ")"),
));

Class(Typ, rec(
    operations := TypOps,
    isType := true,
    isSigned := self >> true,
    doHashValues := false,
    check := v -> v,
    #normalize := (self, v) >> Value(self,v),
    eval := self >> self,

    vbase := rec(),

    value := meth(self, v)
        local ev;
        if IsExp(v) then
            ev := v.eval();
            if IsSymbolic(ev) and not IsValue(ev) then
                v.t := self;
                return v;
            fi;
        fi;

        if IsValue(v) then return Value.new(self, self.check(v.v));
        else return Value.new(self,self.check(v));
        fi;
    end,

    realType := self >> self,

    product  := (v1, v2) -> v1 * v2,
    sum      := (v1, v2) -> v1 + v2,
    base_t   := self >> self, # composite types should return base data type (without recursion).
    saturate := abstract(), # (self, v) >> ...
    # range should return RangeT
    range    := abstract(), # (self) >> ...
));

Class(CompositeTyp, Typ, rec(operations := TypOpsNoPrint));

Class(AtomicTyp, Typ, rec(
    doHashValues := true,
    isAtomic := true,
    rChildren := self >> [],
    from_rChildren := (self, rch) >> Checked(rch=[], self),
    free := self >> Union(List(self.rChildren(), FreeVars)),
    vtype := (self,v) >> TVect(self, v),
    csize := self >> sizeof(self)
));

IsType := x -> IsRec(x) and IsBound(x.isType) and x.isType;

Class(TFunc, RewritableObject, Typ, rec(
    check := v -> v, #Checked(IsFunction(v), v),
    product := (v1, v2) -> Error("Can not multiply functions"),
    sum  := (v1, v2) -> Error("Can not add functions"),
    zero := (v1, v2) -> Error("TFunc.zero() is not supported"),
    one  := (v1, v2) -> Error("TFunc.one() is not supported"),
    free := self >> Union(List(self.params, FreeVars)),
    updateParams := self >> Checked(ForAll(self.params, e->IsType(e) or IsValue(e) or IsInt(e) or IsSymbolic(e)), true),
    csize := self >> sizeof(self)
));

IsFuncT := x -> IsType(x) and ObjId(x)=TFunc;

Class(ValueOps, PrintOps, rec(
    \= := (v1,v2) -> Cond(
        not IsValue(v2), v1.v=v2,
        not IsValue(v1), v1=v2.v,
        IsBound(v1.t.vequals), v1.t.vequals(v1.v, v2.v),
        IsBound(v2.t.vequals), v2.t.vequals(v1.v, v2.v),
        v1.v = v2.v),
    \< := (v1,v2) -> Cond(
        not IsValue(v2), When(IsRec(v2), ObjId(v1) < ObjId(v2), v1.v < v2),
        not IsValue(v1), When(IsRec(v1), ObjId(v1) < ObjId(v2), v1   < v2.v),
        v1.v < v2.v)
 ));

#----------------------------------------------------------------------------------------------
# Value : values or constants
# NB: All values are automatically hashed in GlobalConstantHash
#     This can reduce memory footprint, since lots of values are repetitive,
#     like 1s and 0s, and also float values that are too close to each other will
#     hash to same value (by virtue of ValueOps.\=), which will prevent compiler
#     from putting them in separate registers, and thus degrading performance.
#
#     This has negligible effect on accuracy, as long as <type>.vequals is valid.
#
#----------------------------------------------------------------------------------------------
Class(Value, rec(
    isValue := true,
    __call__ := (self, t, v) >> t.value(v),

    new := (self,t,v) >> # HashedValue(GlobalConstantHash,  <-- this hashes the Value upon construction, disabled now
                         # due to slowness with large data() blocks, which aren't hashed, unless this option is used
	Cond(t.vbase=rec(),
            WithBases(self, rec(t:=t, v:=v, operations := ValueOps)),
            WithBases(self, Inherit(t.vbase, rec(t:=t, v:=v, operations:=ValueOps)))
	),
    #),

    ev := self >> self.v,
    eval := self >> self,
    free := self >> Set([]),

    from_rChildren := (self, rch) >> self,
#   print := self >> Print(self.__name__, "(", self.t, ",", self.v, ")"),
#   print := self >> Print(self.v),
    print := self >> Cond(IsString(self.v), Print("V(\"",self.v, "\")"), Print("V(", self.v, ")")),

    dims := self >> Cond(
	IsArrayT(self.t), self.t.dims(),
	Error("<self>.dims() is only valid when self.t is a TArray"))
));

IsValue := x -> IsRec(x) and IsBound(x.isValue) and x.isValue;

#----------------------------------------------------------------------------------------------
#----------------------------------------------------------------------------------------------

Declare(TComplex);

Class(TUnknown, AtomicTyp, rec( one := self >> 1, zero := self >> 0));
Class(TVoid,    AtomicTyp);
Class(TDummy,   AtomicTyp); # used in autolib for Lambda parameters that are ignored

Class(TReal, AtomicTyp, rec(
    cutoff := 1e-15,
    hash := (val, size) -> 1 + (DoubleRep64(Double(val)) mod size), #(IntDouble(1.0*val*size) mod size),

    check := (self,v) >> Cond(
        IsExp(v),     ReComplex(Complex(code.EvalScalar(v))),
        IsInt(v),     Double(v),
        IsRat(v),     v,
        IsDouble(v),  When(AbsFloat(v) < self.cutoff, 0.0, v),
        IsCyc(v),     ReComplex(Complex(v)),
        IsComplex(v), ReComplex(v),
        Error("<v> must be a double or an expression")),

    vequals := (self, v1,v2) >> When(
        (IsDouble(v1) or IsInt(v1) or IsRat(v1)) and (IsDouble(v2) or IsInt(v2) or IsRat(v2)),
        AbsFloat(Double(v1)-Double(v2)) < self.cutoff,
        false),

    zero := self >> self.value(0.0),
    one := self >> self.value(1.0),
    strId := self >> "f",

    complexType := self >> TComplex,
));

TDouble:=TReal;

#
# format:  | sign | integer bits | frac bits |
# # make sure we have space at least for the sign bit
#
_fpdouble := (val,b,fb) -> let(res := IntDouble(val * 2.0^fb),
    Cond(
	val = 1 and (fb = b-1), # we can represent 1 as 0.999999, if we use frac bits only
	    2^fb - 1,
	val = -1 and (fb = b-1), # we can represent 1 as 0.999999, if we use frac bits only
	    -(2^fb - 1),
	Log2Int(res)+2 > b, Error("Overflow, value=", val, ", signed width=",
                                   Log2Int(res)+2, ", max width=", b),
         res));

# format:  | integer bits | frac bits |
#
_ufpdouble := (val,b,fb) -> let(res := IntDouble(val * 2.0^fb),
    When(Log2Int(res)+1 > b, Error("Overflow, value=", val, ", unsigned width=",
                                   Log2Int(res)+1, ", max width=", b),
         res));

#F TFixedPt(<bits>, <fracbits>)   -- fixed point data type
#F
#F   <bits> -- total # of bits (including sign bit)
#F   <fracbits> -- number of fractional bits
#F
#F   Number of integer bits is assumed to be bits-1-fracbits (1 = sign bit)
#F
Class(TFixedPt, TReal, rec(
    operations := TypOpsNoPrint, # NOTE: do not inherit from TReal, and then this line won't be needed

    __call__ := (self, bits, fracbits) >> WithBases(self, rec(
            bits := bits,
            fracbits := fracbits,
            operations := TypOps)),

    rChildren := self >> [self.bits, self.fracbits],
    rSetChild := rSetChildFields("bits", "fracbits"),
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), rch),

    print := self >> Print(self.__name__, "(", self.bits, ", ", self.fracbits, ")"),

    check := (self, v) >> _fpdouble(v, self.bits, self.fracbits)
));

# TUFixedPt(<bits>, <fracbit>)  -- unsigned fixed point data type
#
Class(TUFixedPt, TFixedPt);

Class(TComplex, AtomicTyp, rec(
    hash := (val, size) -> let(
        cplx := Complex(val),
        #h := IntDouble(size * (ReComplex(cplx)+ImComplex(cplx))),
        h := DoubleRep64(ReComplex(cplx)) + DoubleRep64(ImComplex(cplx)),
        1 + (h mod size)),

    check := v -> Cond(
        BagType(v) in [T_CYC, T_RAT, T_INT], v, # exact representation
        IsDouble(v),  When(AbsFloat(v) < TReal.cutoff, 0, v),
        IsComplex(v), Complex(TReal.check(ReComplex(v)), TReal.check(ImComplex(v))),
        IsExp(v),     Complex(v.ev())),

    realType    := self >> TReal,
    complexType := self >> self,

    zero := self >> self.value(0.0),
    one := self >> self.value(1.0),
));

Class(TBool, AtomicTyp, rec(
    hash := (val, size) -> 1 + (InternalHash(val) mod size),
    check := v -> Cond(IsBool(v), v, Error("<v> must be a boolean")),
    one := self >> self.value(true),
    zero := self >> self.value(false),
));

Class(TInt_Base, AtomicTyp, rec(
    hash    := (val, size) -> 1 + (10047871*val mod size),
    bits    := 32,
    check   := v -> Cond(IsExp(v), Int(v.ev()),
                         IsInt(v), v,
                         IsDouble(v) and IsInt(IntDouble(v)), IntDouble(v),
                         Error("<v> must be an integer or an expression")),
    one  := self >> self.value(1),
    zero := self >> self.value(0),

    complexType := self >> TComplex,
));

Class(TInt, TInt_Base, rec(strId := self >> "i"));
Class(TUInt, TInt_Base, rec(isSigned := False, strId := self >> "ui"));
Class(TULongLong, TInt_Base);

IsChar := (x)->When(BagType(x)=T_CHAR, true, false);

Class(TChar, TInt_Base, rec(
    hash := (val, size) -> When(IsChar(val), 1 + (InternalHash(val) mod size), TInt_Base.hash(val, size)),
    bits := 8,
    check := v -> Cond(IsExp(v), Int(v.ev()),
                       IsInt(v), v,
                       IsChar(v), v,
                       Error("<v> must be an integer or an expression")),
));

Class(TUChar, TInt_Base, rec(
    bits := 8,
    isSigned := self >> false,
    check := v -> Cond(IsExp(v), Int(v.ev()),
                       IsInt(v), v,
                       Error("<v> must be an integer or an expression")),
));

Class(TString, AtomicTyp, rec(
    doHashValues := true,
    hash := (val, size) -> 1 + (InternalHash(val) mod size),
    check := v -> Cond(IsString(v), v, Error("<v> must be a string")),
    one := self >> Error("TString.one() is not allowed"),
    zero := self >> Error("TString.zero() is not allowed"),
));

Class(TList, CompositeTyp, rec(
    isListT := true,
    hash := (val, size) -> Error("Not implemented"),
    __call__ := (self, t) >>
        WithBases(self, rec(
        t    := Checked(IsType(t), t),
        operations := PrintOps)),
    print := self >> Print(self.__name__, "(", self.t, ")"),
    check := v -> Cond(IsList(v), v, Error("<v> must be a list")),

    one  := self >> Error("TList.one() is not allowed"),
    zero := self >> Error("TList.zero() is not allowed"),

    rChildren := self >> [self.t],
    rSetChild := rSetChildFields("t"),
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), rch),

));

Class(TSym, CompositeTyp, rec(
    hash := (val, size) -> Error("Not implemented"),
    check := v -> v,
    __call__ := (self, id) >>
        WithBases(self, rec(
        id    := Checked(IsString(id), id),
        operations := TypOps)),

    rChildren := self >> [self.id],
    rSetChild := rSetChildFields("id"),
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), rch),

    print := self >> Print(self.__name__, "(\"", self.id, "\")"),
    csize := self >> sizeof(self)
));

#F TArrayBase -- base class for array-like element collection types
#F
#F Subclasses: TPtr, TArray, TVect, BitVector
#F
#F Default constructor:
#F
#F  __call__(<element-type>, <size>) - array type of <size> elements of <element-type>
#F
Class(TArrayBase, CompositeTyp, rec(
     __call__ := (self, t, size) >>
        WithBases(self, rec(
        t    := Checked(IsType(t), t),
        size := Checked(IsPosInt0Sym(size), size),
        operations := TypOps)),

    hash := (self, val, size) >> (Sum(val, x -> x.t.hash(x.v, size)) mod size) + 1,

    rChildren := self >> [self.t, self.size],
    rSetChild := rSetChildFields("t", "size"),
    from_rChildren := (self, rch) >> ApplyFunc(ObjId(self), rch),

    isSigned := self >> self.t.isSigned(),

    print := self >> Print(self.__name__, "(", self.t, ", ", self.size, ")"),

    check := (self, v) >> Checked(IsList(v), Length(v) = self.size,
    ForAll(v, el -> el.t = self.t), v),

    # these fields go into values
    vbase := rec(
        free := self >> Union(List(self.v, e -> e.free())),
        rChildren := self >> self.v,
        rSetChild := meth(self, n, newC) self.v[n] := newC; end
    ),

    zero := self >> self.value(Replicate(_unwrap(self.size), self.t.zero())),
    one  := self >> self.value(Replicate(_unwrap(self.size), self.t.one())),

    value := (self, v) >> let(vv := When(IsValue(v), v.v, v),
        Cond(IsExp(vv), vv,
             Checked(IsList(vv),
                     Value.new(self, List(vv, e->self.t.value(e)))))),

    # array type can have free variables in .size field
    free := self >> Union(FreeVars(self.size), FreeVars(self.t)),

    csize := self >> self.t.csize() * self.size,

    realType := self >> ObjId(self)(self.t.realType(), self.size),

    base_t := self >> self.t,
    range  := self >> self.t.range(),
));

Declare(TPtr, TArray);

#F TArray(<element-type>, <size>) - array type of <size> elements of <element-type>
#F
Class(TArray, TArrayBase, rec(
    isArrayT := true,
    vtype := (self, v) >> TArray(self.t.vtype(v), self.size/v),
    toPtrType := self >> TPtr(self.t),
    doHashValues := true,

    dims := self >> Cond(
        ObjId(self.t)=TArray, [self.size] :: self.t.dims(),
        [self.size])
));

# [ptrAligned, ptrUnaligned] are TPtr.alignment values
ptrUnaligned := [1,0];
ptrAligned4  := [4,0];
ptrAligned8  := [8,0];
ptrAligned16 := [16,0];
ptrAligned := ptrAligned16;

# NOTE: this is a hack, esp because 16 byte boundary is hardcoded in ptrAligned
#        It should be in SpiralDefaults somehow
TArray.alignment := ptrAligned;
TArray.qualifiers := [];

TypeDomain := (dom, els) ->
    Cond(Same(dom, Rationals) or Same(dom, Scalars) or Same(dom, Doubles), TReal,
     Same(dom, Complexes), TComplex,
     Same(dom, Cyclotomics), When(ForAll(els, x->Im(x)=0), TReal, TComplex),
     Same(dom, Integers), TInt,
     Error("Unrecognized domain <dom>"));

# IsArrayT(<t>) - checks whether <t> is an array type object
IsArrayT := x -> IsType(x) and IsBound(x.isArrayT) and x.isArrayT;

# IsListT(<t>) - checks whether <t> is a list type object
IsListT := x -> IsType(x) and IsBound(x.isListT) and x.isListT;

# IsVecT(<t>) - checks whether <t> is an vector type object
IsVecT := x -> IsType(x) and IsBound(x.isVecT) and x.isVecT;

# IsPtrT(<t>) - checks whether <t> is a pointer type object
IsPtrT := x-> IsType(x) and IsBound(x.isPtrT) and x.isPtrT;

# IsUnalignedPtrT(<t>) - checks whether <t> is a unaligned pointer type object,
#       unaligned means aligned with smaller granularity than child type (t.t)
#       size.
IsUnalignedPtrT := x -> IsPtrT(x) and x.alignment<>ptrAligned;

# obsolete, use IsArrayT
IsArray := IsArrayT;

Class(TPtr, TArrayBase, rec(
     isPtrT := true,
     __call__ := arg >> let(self := arg[1],
         t          := arg[2],
         qualifiers := When(IsBound(arg[3]), arg[3], []),
         alignment  := When(IsBound(arg[4]), arg[4], ptrAligned16),
         WithBases(self, rec(
            t    := Checked(IsType(t), t),
            size := 0,
            qualifiers := qualifiers,
            _restrict  := false,
            alignment  := alignment,
            operations := TypOps)).normalizeAlignment()),

     # value := (self, v) >> Error("Values of TPtr type are not allowed"),
     value := Typ.value,

     check := (self, v) >> Cond(
        IsList(v), Checked(
           Length(v) = self.size,
           ForAll(v, el -> el.t = self.t),
           v
        ),
        IsInt(v), v,
        Error("TPtr needs to either point to an array or some value")
     ),

     # this looks crazy, but sometimes this happens (in LRB backend actually) : X - X
     # where X is a pointer. Internally this can become X + (-X), and then becomes 0
     isSigned := self >> true,

     rChildren := self >> [self.t, self.qualifiers, self.alignment],
     rSetChild := rSetChildFields("t", "qualifiers", "alignment"),

     zero := self >> TInt.zero(),
     one := self >> TInt.one(),

     print := self >> Print(self.__name__, "(", self.t,
         When(self.qualifiers<>[], Print(", ", self.qualifiers)), ")",
         When(self._restrict, ".restrict()", ""),
         ".aligned(", self.alignment, ")"
         ),

     restrict := (self) >> CopyFields(self, rec(_restrict := true)),
     unRestricted := (self) >> CopyFields(self, rec(_restrict := false)),

     csize := self >> sizeof(self),

     realType := self >> Cond(self._restrict,
         ObjId(self)(self.t.realType(), self.qualifiers).restrict(),
         ObjId(self)(self.t.realType(), self.qualifiers)
     ),

     aligned   := (self, a) >> CopyFields(self, rec( alignment := Checked(IsList(a) and Length(a)=2, a)  )).normalizeAlignment(),
     unaligned := (self) >> CopyFields(self, rec( alignment := [1,0] )),

     normalizeAlignment := meth(self)
         if IsValue(self.alignment[2]) then self.alignment[2] := self.alignment[2].v;
         elif IsSymbolic(self.alignment[2]) then self.alignment := ptrUnaligned; # NOTE: Conservative assumption
         fi;
         Constraint(IsInt(self.alignment[2]));
         self.alignment[2] := self.alignment[2] mod self.alignment[1];
         return self;
     end,

     withAlignment := (self, value) >> CopyFields(self, rec(
         alignment := When(IsPtrT(value), value.alignment, value))),

     # things get a little strange here because we allow pointers
     # to be set to some int based offset
     #
     vbase := rec(
         free := self >> Cond(
             IsList(self.v), Union(List(self.v, e -> e.free())),
             IsInt(self.v), [],
             Error("hmm.")
         ),
         rChildren := self >> Cond(
             IsList(self.v), self.v,
             IsInt(self.v), [],
             Error("hmm.")
         ),

         rSetChild := meth(arg)
             local _self;
             _self := arg[1];

             if Length(arg) = 3 then
                _self.v[arg[2]] := arg[3];
             elif Length(arg) = 2 then
                _self.v := arg[2];
             else
                Error("choke");
             fi;
         end,
     ),
));

# -- TVect ----------------------------------------------------------------------

Class(TVect, TArrayBase, rec(
    isVecT := true,
    doHashValues := true,
    __call__ := (self, t, size) >> Cond(t=T_UInt(1), BitVector(size), Inherited(t, size)),

    product := (v1, v2) -> Checked(IsList(v1) or IsList(v2), let(
        vv1 := When(not IsList(v1), Replicate(Length(v2), v1), v1),
        vv2 := When(not IsList(v2), Replicate(Length(v1), v2), v2),
        l := Length(vv1),
        Checked(l = Length(vv2),
            List([1..l], i -> vv1[i]*vv2[i])))),

    sum := (v1, v2) -> Checked(IsList(v1) or IsList(v2), let(
        vv1 := When(not IsList(v1), Replicate(Length(v2), v1), v1),
        vv2 := When(not IsList(v2), Replicate(Length(v1), v2), v2),
        l := Length(vv1),
        Checked(l = Length(vv2),
            List([1..l], i -> vv1[i]+vv2[i])))),

    value := (self, v) >> Cond( IsValue(v) and self=v.t, v, let(
        vv := When(IsValue(v), v.v, v),
        Cond(IsExp(vv), vv,
             IsList(vv), Value.new(self, List(vv,                       e -> self.t.value(e))),
             <#else#>
                         Value.new(self, List(Replicate(self.size, vv), e -> self.t.value(e)))))),

    saturate := (self, v) >> let( vv := _unwrap(v), Cond(not IsList(vv) or Length(vv)<>self.size, v,
        Value.new(self, List(vv, e -> self.t.saturate(e)))) ),

    toUnsigned := self >> TVect(self.t.toUnsigned(), self.size),
    toSigned   := self >> TVect(self.t.toSigned(),   self.size),
    double     := self >> TVect(self.t.double(),     self.size/2),

));

IsTVectDouble := x -> IsVecT(x) and x.t = TReal;

TVectDouble := vlen -> TVect(TReal, vlen);

#Class(T_Type, Typ, rec(
#     __call__ := (self, bits) >>
#        WithBases(self, rec(
#        bits := Checked(IsPosInt(bits), bits),
#        operations := TypOps)),
#
#     hash := (self, val, size) >>  1 + (10047871*val mod size),
#
#     rChildren := self >> [],
#     rSetChild := self >> Error("This function should not be called"),
#     print := self >> Print(self.__name__, "(", self.bits, ")"),
#     free := self >> Union(List(self.rChildren(), FreeVars)),
#     vtype := (self,v) >> TVect(self, v)
#));

Class(T_Type, RewritableObject, rec(
    isType := true,

    isSigned := self >> true,
    realType := self >> self,

    doHashValues := false,
    check := v -> v,
    vbase := rec(),

    value := meth(self, v)
        local ev;
        if IsExp(v) then
            ev := v.eval();
            if IsSymbolic(ev) and not IsValue(ev) then
                v.t := self;
                return v;
            fi;
        fi;
        if IsValue(v) then
            return Value.new(self, self.check(v.v));
        else
            return Value.new(self, self.check(v));
        fi;
    end,

    eval     := self >> self,
    product  := (v1, v2) -> v1 * v2,
    sum      := (v1, v2) -> v1 + v2,
    zero     := self >> self.value(0),
    one      := self >> self.value(1),
    csize    := self >> sizeof(self),
    base_t   := self >> self, # composite types should return base type (without recursion).
    saturate := abstract(), # (self, v) >> ...
    range    := abstract(), # (self) >> ...
));

Declare(T_Int, T_UInt, T_Complex);

Class(T_Ord, T_Type, rec(
    hash := (val, size) -> 1 + (10047871*val mod size),
    saturate := (self, v) >> let( b := self.range(),
        Cond( IsExp(v), v, self.value(Max2(b.min, Min2(b.max, _unwrap(v)))))),
));

Class(T_Int, T_Ord, rec(
    check := (self, v) >> let(
        i := Cond(IsDouble(v), IntDouble(v),
                  IsRat(v), Int(v),
                  Error("Can't convert <v> to an integer")),
        b := self.params[1],
        ((i + 2^(b-1)) mod 2^b) - 2^(b-1)),

    strId    := self >> "i"::StringInt(self.params[1]),
    range    := self >> RangeT(-2^(self.params[1]-1),  2^(self.params[1]-1)-1,  1),

    isSigned   := True,
    toUnsigned := self >> T_UInt(self.params[1]),
    toSigned   := self >> self,
    double     := self >> T_Int(2*self.params[1]),
));

Class(T_UInt, T_Ord, rec(
    check := (self, v) >> let(
        i := Cond(IsDouble(v), IntDouble(v),
                  IsRat(v), Int(v),
                  Error("Can't convert <v> to an integer")),
        b := self.params[1],
        i mod 2^b),

    strId    := self >> "ui"::StringInt(self.params[1]),
    range    := self >> RangeT(0, 2^self.params[1]-1, 1),

    isSigned   := False,
    toUnsigned := self >> self,
    toSigned   := self >> T_Int(self.params[1]),
    double     := self >> T_UInt(2*self.params[1]),
));

Class(BitVector, TArrayBase, rec(
    isVecT := true,
    __call__ := (self, size) >> Inherited(T_UInt(1), size),

    print := self >> Print(self.__name__, "(", self.size, ")"),
    vbase := rec(
        print := self >> let(n:=Length(self.v), Print("h'", HexStringInt(Sum([1..n], i->self.v[i] * 2^(n-i))))),
    ),

    isSigned := self >> false,

    rChildren := self >> [self.size],
    rSetChild := rSetChildFields("size"),

    one := self >> self.value(Replicate(self.size, 1)),
    zero := self >> self.value(Replicate(self.size, 0)),

    hash := (self, val, size) >> let(n:=Length(val),
        1 + (Sum([1..n], i -> val[i] * 2^(n-i)) mod size)),

    product := TVect.product,
    sum := TVect.sum,

    _uint1 := T_UInt(1),

    value := (self, v) >> When( IsValue(v) and v.t = self, v,
        let(vv := When(IsValue(v), v.v, v),
            Cond(IsExp(vv), vv,
                 Checked(IsList(vv),
                     Value.new(self, List(vv, e->self._uint1.check(e))))))),
));

Class(T_Real, T_Type, rec(
   #correct cutoffs are floor(log10(2^(mantissa bits + 1)))
   cutoff := self>>Cond(
       self.params[1] = 128, 1e-34,
       self.params[1] = 80, 1e-19,
       self.params[1] = 64, 1e-15,
       self.params[1] = 32, 1e-7,
       Error("cutoff not supported")
   ),

   hash := TReal.hash,

   check := (self,v) >> let( r := Cond(
            IsExp(v),     ReComplex(Complex(code.EvalScalar(v))),
            IsInt(v),     Double(v),
            IsRat(v),     v,
            IsDouble(v),  v,
            IsCyc(v),     ReComplex(Complex(v)),
            IsComplex(v), ReComplex(v),
            # else
                Error("<v> must be a double or an expression")),
        When(AbsFloat(r) < self.cutoff(), 0.0, r)),

   vequals := (self, v1,v2) >> When(
        (IsDouble(v1) or IsInt(v1)) and (IsDouble(v2) or IsInt(v2)),
        AbsFloat(Double(v1)-Double(v2)) < self.cutoff(),
        false),

   zero := self >> self.value(0.0),
   one := self >> self.value(1.0),
   isSigned := (self) >> true,
   strId := self >> "f"::StringInt(self.params[1]),
   range := self >> Cond(
       self.params[1] = 128, RangeT(
           -1.7976931348623157e+308 - 10e291, #INF
           1.7976931348623157e+308 + 10e291, #INF
           1e-34
       ),
       self.params[1] = 80, RangeT(
           -1.7976931348623157e+308 - 10e291, #INF
           1.7976931348623157e+308 + 10e291, #INF
           1e-19
       ),
       self.params[1] = 64, RangeT(
           -1.7976931348623157e+308,
            1.7976931348623157e+308,
            1.1102230246251565e-016
       ),
       self.params[1] = 32, RangeT(
           -3.4028234e+038,
            3.4028234e+038,
            5.96046448e-008
       )),

   complexType := self >> T_Complex(self),
));

Class(T_Complex, T_Type, rec(
    hash := TComplex.hash,
    realType    := self >> self.params[1],
    complexType := self >> self,

    isSigned := self >> self.params[1].isSigned(),
    strId    := self >> "c"::self.params[1].strId(),

    check := (self, v) >> let(
	realt := self.params[1],
	cpx := Complex(v),
	Complex(realt.check(ReComplex(cpx)),
	        realt.check(ImComplex(cpx))))
));

# # complex type is made up of TWO T_Real, T_Uint, or T_Int types.

# Class(T_Complex, TArrayBase, rec(
#     isComplex := true,
#     __call__ := (arg) >> let(
#         self := arg[1],
#         t := arg[2],
#         Checked(
#             ObjId(t) in [T_Real, T_UInt, T_Int],
#             WithBases(self, rec(
#                 t := t,
#                 qualifiers := When(Length(arg) > 2, arg[3], []),
#                 operations := TypOps,
#                 size := 0
#             ))
#         )
#     ),

#     rChildren := self >> [self.t, self.qualifiers],
#     rSetChild := rSetChildFields("t", "qualifiers"),

#     print := self >> Print(self.__name__, "(", self.t,
#         When(self.qualifiers <> [],
#             Print(", ", self.qualifiers)
#         ),
#         ")"
#     )
# ));

_IsVar := (e) -> code.IsVar(e);

#F T_Struct: structure type.
#F
#F T_Struct("structname", [<var1>, <var2>, ... , <varN>])
#F
Class(T_Struct, T_Type, rec(
    updateParams := meth(self)
        Constraint(IsString(self.params[1]));
        Constraint(IsList(self.params[2]));
        Constraint(ForAll(self.params[2], e -> _IsVar(e)));
    end,

    getName := self >> self.params[1],
    getVars := self >> self.params[2]
));

IsIntT := (t) -> IsType(t) and t in [TChar, TInt] or ObjId(t) = T_Int;
IsUIntT := (t) -> IsType(t) and t in [TUChar, TUInt] or ObjId(t) = T_UInt;
IsOrdT := (t) -> IsIntT(t) or IsUIntT(t);

IsFixedPtT := (t) -> IsType(t) and ObjId(t)=TFixedPt;

IsRealT := (t) -> IsType(t) and t=TReal or ObjId(t)=T_Real;

IsComplexT := (t) -> IsType(t) and t=TComplex or ObjId(t)=T_Complex;

IsOddInt :=  n -> When(IsValue(n), n.v mod 2 = 1, IsInt(n) and n mod 2 = 1);

IsEvenInt := n -> When(IsValue(n), n.v mod 2 =0, IsInt(n) and n mod 2 = 0);


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

prep_hex_perm_spu := function(p)
    local retval;
    retval :=  ConcatenationString(
                "0x", p{[  1 ..  8]}, ", ",
                "0x", p{[  9 .. 16]}, ", ",
                "0x", p{[ 17 .. 24]}, ", ",
                "0x", p{[ 25 .. 32]});
    return(retval);
end;

prep_perm_string_spu := function(p)
    local base_string, final_string, i_counter, zeroOutString, v;
    # This handles both 4x32f and 2x64f perm strings
    v := Length(p);
    base_string := [0..((16/v)-1)];
    zeroOutString := List([1..(16/v)], i->128);
    final_string := [];
    # A value of '128' in the permutation param list causes the spu_shuffle to zero out the corresponding bytes.
    for i_counter in (p-1) do # changed Reversed(p-1) to (p-1)
       if i_counter = 127 then
          Append(final_string, zeroOutString);
        else
           Append(final_string, (base_string + i_counter * (16/v)));
        fi;
    od;
    return final_string;
end;

# NOTE: The first section is Intel SSE/2/3 specific, and has to be ported
# over to SPU

unpacklo := (l1,l2,n,k) -> Flat(List([1..n/(2*k)], i-> [
        List([1..k], j->l1[(i-1)*k+j]),
        List([1..k], j->l2[(i-1)*k+j])
]));

unpackhi := (l1,l2,n,k) -> Flat(List([1..n/(2*k)], i -> [
        List([1..k], j->l1[n/2+(i-1)*k+j]),
        List([1..k], j->l2[n/2+(i-1)*k+j])
]));

sparams := (l,n) -> List([1..l], i->[1..n]);

shuffle := (in1, in2, p, n, k) -> Flat([
    List([1..n/(2*k)],     i->List([1..k], j->in1[(p[i]-1)*k+j])),
    List([n/(2*k)+1..n/k], i->List([1..k], j->in2[(p[i]-1)*k+j]))
]);

iperm4 := self >> Filtered(Cartesian(self.params()), i->i[1]<>i[2] and i[3] <> i[4]);

# Generic instructions
#
Class(vop_new_mixin, rec(
    params := self >> [],
    permparams := self >> Cartesian(self.params()),
    isBinop := self >> self.numargs = 2,
    isUnop := self >> self.numargs = 1,
    isLoadop := False,
    isStoreop := False,
));

Class(vop_new, vop_new_mixin, Exp, rec(
    __call__ := arg >> let(self:=arg[1],
    _computeExpType(WithBases(self, rec(
        p          := When(Length(arg) >= self.numargs+2, arg[self.numargs+2], []),
        args       := Concat(List(Sublist(arg, [2..self.numargs+1]), toExpArg),
                        When(Length(arg) >= self.numargs+2 and arg[self.numargs+2] <> [],
                        [vparam(arg[self.numargs+2])], [])),
        operations := ExpOps
    )))
    ),
));

Class(vbinop_new, vop_new, rec(numargs:=2));
Class(vunop_new, vop_new, rec(numargs:=1));

Class(vunbinop_new, vop_new, rec(
    numargs := 1,
    params := self >> self.binop.params(),
    v := self >> self.binop.v,
    semantic := (self, in1, p) >> self.binop.semantic(in1, in1, p)
));

Class(vloadop_new, vop_new, rec(
    numargs := 0,
    isLoadop := True,
    # in case of explicit type cast (YSV modification), we don't need getNoScalar,
    # and below returns [], compiler understands not to mess with typecasts
    getNoScalar := self >> When(IsBound(self.noscalar) and IsBound(self.args[self.noScalar].loc),
	self.args[self.noScalar].loc, [])
));

Class(vstoreop_new, assign, vop_new_mixin, rec(
    isStoreop := True,
    # in case of explicit type cast (YSV modification), we don't need getNoScalar,
    # and below returns [], compiler understands not to mess with typecasts
    getNoScalar := self >> let(s := self.loc,
    When(IsBound(s.loc), s.loc, []))
));

Class(vstoremsk, assign, vop_new_mixin, rec(
    isStoreop := True,
   __call__ := (self, loc, exp, p) >> WithBases(self,
       rec(operations := CmdOps,
       loc := toAssignTarget(loc),
       exp := toExpArg(exp),
       p := p)),

    print := (self,i,is) >> Print(Blanks(i), self.name, "(",
    self.loc.print(), ", ", self.exp.print(), ", ", self.p, ");\n"),

    # in case of explicit type cast (YSV modification), we don't need getNoScalar,
    # and below returns [], compiler understands not to mess with typecasts
    getNoScalar := self >> When(IsBound(self.noscalar) and IsBound(self.args[self.noScalar].loc),
	self.args[self.noScalar].loc, [])
));


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

#
# NEON 64-bit and 128-bit SIMD ISA Definitions
#
# legend: v = ok, ! = missing, u = added, untested
    # Required:
    #  v active
    #  v bin_shl1  bin_shl2  bin_shr1  bin_shr2  bin_shrev
    #  v bits, ctype, t, v
    #  v countrec
    #  v includes
    #  v info
    #  v instr
    #  v isFixedPoint, isFloat
    #  v loadCont (uses sv loads, ARM does not have masked loads)
    #  v mul_cx, mul_cx_conj
    #  u reverse
    #  v splopts
    #  v storeCont (uses sv stores, ARM does not have masked stores)
    #  v svload
    #  v svstore
    #  v RCVIxJ2
    #  v dupload, duploadn
    #  ! hadd
    #  v swap_cx
    #  v vzero

    # Fixed point:
    #  fracbits
    #  saturatedArithmetic

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

Class(SIMD_NEON, SIMD_ISA, rec(
    file := "neon",
    commonIncludes := self >> [],
    active       := true,
    isFixedPoint := false,
    isFloat      := true,
    ctype        := "float32_t",
    stype        := "__attribute__ ((aligned(16))) float32_t",
    bits         := 32,
    useDeref     := false,
    splopts      := rec(precision := "single"),
    arrayDataModifier := "__attribute__ ((aligned(16)))",
    arrayBufModifier  := "static __attribute__ ((aligned(16)))",
    declareConstants := true,
    threeOps         := false,
    fma		     := true,
    vzero := self >> self.t.zero(),
    unparser := NEONUnparser,
    compileStrategy := self >> BaseIndicesCS
      :: When(self.fma, [DoFMA], [])
      :: [ MarkDefUse, #
           (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))),
           MarkDefUse, #
           (c, opts) -> CopyPropagate(c, CopyFields(opts, rec(autoinline := true))) ]
      :: When(self.threeOps, [DoThreeOp], [])
      :: [ Compile.declareVars,
           (c, opts) -> opts.vector.isa.fixProblems(c, opts),
           HashConsts ],

    loadCont := (self, n, y, yofs, x, xofs, xofs_align, opts) >> let(
	nn := _unwrap(n),
	yy := vtref(self.t, y, yofs),
	assign(yy, deref(nth(x, xofs).toPtr(self.t)))),

    storeCont := (self, n, y, yofs, yofs_align, x, xofs, opts) >> let(
	nn := _unwrap(n),
	xx := vtref(self.t, x, xofs),
	assign(deref(nth(y, yofs).toPtr(self.t)), 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)))),
));

Declare(NEON_HALF);

Class(NEON_HALF, SIMD_NEON, rec(
    info := "NEON 2 x floating point",
    v     := 2,
    t     := TVect(T_Real(32), 2),
    freshU  := self >> var.fresh_t("u", TVect(self.t, 2)),
    freshT  := self >> var.fresh_t("t", self.t),
    instr   := [vunpacklo_half,  vunpackhi_half, vrev_half ],

    includes := self >> self.commonIncludes() ::
        ["<arm_neon.h>", "<include/omega32_neon.h>", "<include/mm_malloc.h>"],

    dupload := (y, x) -> assign(y, vdup(x, 2)),
    duploadn := (y, x, n) -> assign(y, vdup_lane_half(x, n)),

    reverse := (y,x) -> assign(vref(y,0,2), vrev_half(vref(x,0,2))),
    RCVIxJ2 := (y,x,opts) -> assign(y, vrev_half(x)),

    # this is shift "right" in Spiral notation (and ARM too)
    # [a b] -> [0 a]
    bin_shl1 := (self, y,x,opts) >> assign(y, vext_half(self.t.zero(), x, 1)),
    # this is shift "left" in Spiral notation (and ARM too)
    # [a b] -> [b 0]
    bin_shr1 := (self, y,x,opts) >> assign(y, vext_half(x, self.t.zero(), 1)),

    # this is shift "right" in Spiral notation (and ARM too)
    # [a b], [c,d] -> [b c]
    bin_shl2 := (self, y,x,opts) >> assign(y, vext_half(x[1], x[2], 1)),
    # this is shift "left" in Spiral notation (and ARM too)
    # [a b], [c,d] -> [b c]
    bin_shr2 := (self, y,x,opts) >> assign(y, vext_half(x[1], x[2], 1)),

    # [a b], [c,d] -> [b c]
    bin_shrev := (self, y,x,opts) >> assign(y, vext_half(x[1], x[2], 1)),

    countrec := rec(
        ops := [
           [ add, sub ],
	   [ mul, vmulcx_half ],
           [ fma, fms, nfma ],
           [ vunpacklo_half, vunpackhi_half, vrev_half, vswapcx_half, vtrnq_half, vextract_half,
	     vdup_lane_half ],
           [ vload1_half ],
           [ deref, nth, ],
           Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]","[mults]","[fmas]","[vperms]","[svldst]","[vldst]","[vval]"],
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

   loadCont := (self, n, y, yofs, x, xofs, xofs_align, opts) >> let(
	nn := _unwrap(n),
	yy := vtref(self.t, y, yofs),
	p0 := nth(x, xofs).toPtr(self.t.t),
	Cond(
	    nn=1, assign(yy, vload1_half(p0, self.vzero(), 1)),
	    nn=2, assign(yy, deref(nth(x, xofs).toPtr(self.t)))
	)
    ),

    storeCont := (self, n, y, yofs, yofs_align, x, xofs, opts) >> let(
	nn := _unwrap(n),
	xx := vtref(self.t, x, xofs),
	p0 := nth(y, yofs).toPtr(self.t.t),
	Cond(
	    nn=1, vstore1_half(p0, xx, 1),
	    nn=2, assign(deref(nth(y, yofs).toPtr(self.t)), xx)
	)
    ),

    svload := [
        # load using subvectors of length 1
	[   (y,x,opts) -> assign(y, vload1_half(x[1].toPtr(NEON_HALF.t.t),
		                                NEON_HALF.vzero(), 1)),

            (y,x,opts) -> let(u := var.fresh_t("T", NEON_HALF.t),
                decl([u], chain(
                        assign(u, vload1_half(x[1].toPtr(NEON_HALF.t.t),
				              NEON_HALF.vzero(), 1)),
                        assign(y, vload1_half(x[2].toPtr(NEON_HALF.t.t), u, 2))
                )))
	],
        # load using subvectors of length 2
        [(y,x,opts) -> assign(y, nth(x[1].toPtr(TVect(TReal, 2)), 0)) ]
    ],

    svstore := [
        [  (y,x,opts) -> vstore1_half(y[1].toPtr(NEON_HALF.t.t), x, 1),
           (y,x,opts) -> chain(
               vstore1_half(y[1].toPtr(NEON_HALF.t.t), x, 1),
               vstore1_half(y[2].toPtr(NEON_HALF.t.t), x, 2)
           )
        ],
        [  (y,x,opts) -> assign(nth(y[1].toPtr(NEON_HALF.t), 0), x) ]
    ],

    mul_cx := (self, opts) >> ((y,x,c) -> let(
	u := self.freshU(),  v1 := self.freshT(), v2 := self.freshT(), r1 := self.freshT(),
	decl([u, v1, v2, r1], chain(
		assign(r1, self.t.value([-1.0,1.0])),
         	assign(u, vtrnq_half(c, c)),
		assign(v1, x * vextract_half(u, [0])),
		assign(v2, vswapcx_half(x) * vextract_half(u, [1])),
		assign(y, fma(v1, v2, r1))))
    )),

    mul_cx_conj := (self, opts) >> ((y,x,c) -> let(
	u := self.freshU(),  v1 := self.freshT(), v2 := self.freshT(), r1 := self.freshT(),
	decl([u, v1, v2, r1], chain(
		assign(r1, self.t.value([1.0, -1.0])),
         	assign(u, vtrnq_half(c, c)),
		assign(v1, x * vextract_half(u, [0])),
		assign(v2, vswapcx_half(x) * vextract_half(u, [1])),
		assign(y, fma(v1, v2, r1))))
    )),

    swap_cx := (y, x, opts) -> assign(y, vswapcx_half(x)),
));

Class(NEON, SIMD_NEON, rec(
    info := "NEON 4 x floating point",

    countrec := rec(
        ops := [
            [ add, sub ],
	    [ mul, vmulcx_neon ],
            [ fma, fms, nfma ],
            [ vpacklo_neon,      vpackhi_neon,       vunpacklo_neon,    vunpackhi_neon,
              vunpacklolo2_neon, vunpacklohi2_neon,  vunpackhilo2_neon, vunpackhihi2_neon,
              vtransposelo_neon, vtransposehi_neon,  vrev_neon,         vswapcx_neon,
              vuzpq_32f,         vzipq_32f,          vtrnq_32f,         vextract_neon_4x32f,
	      vdup_lane_neon ],
            [ vload1_neon, vload_half_neon, vcombine_neon ],
            [ deref, nth, ],
            Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]","[mults]","[fmas]","[vperms]","[svldst]","[vldst]","[vval]"],
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

    v     := 4,
    t     := TVect(T_Real(32), 4),
    freshU := self >> var.fresh_t("u", TVect(self.t, 2)),
    freshT := self >> var.fresh_t("t", self.t),

    instr := [vpacklo_neon,    vpackhi_neon,
              vunpacklo_neon,  vunpackhi_neon,
              vunpacklolo2_neon,   vunpacklohi2_neon,
              vunpackhilo2_neon,   vunpackhihi2_neon,
              vtransposelo_neon, vtransposehi_neon,
	      vrev_neon ],

    includes := self >> self.commonIncludes() ::
        ["<arm_neon.h>", "<include/omega32_neon.h>", "<include/mm_malloc.h>"],

    dupload  := (y, x) -> assign(y, vdup(x, 4)),
    duploadn := (y, x, n) -> assign(y, vdup_lane_neon(x, n)),

    reverse := (y,x) -> assign(vref(y,0,4), vrev_neon(vext_neon(vref(x,0,4), vref(x,0,4), 2))),
    RCVIxJ2 := (y,x,opts) -> assign(y, vext_neon(x, x, 2)),

    # this is shift "right" in Spiral notation (and ARM too)
    # [a b c d] -> [0 a b c]
    bin_shl1 := (self, y,x,opts) >> assign(y, vext_neon(self.t.zero(), x, 1)),
    # this is shift "left" in Spiral notation (and ARM too)
    # [a b c d] -> [b c d 0]
    bin_shr1 := (self, y,x,opts) >> assign(y, vext_neon(x, self.t.zero(), 3)),

    # this is shift "right" in Spiral notation (and ARM too)
    # [a b c d], [e f g h] -> [d e f g]
    bin_shl2 := (self, y,x,opts) >> assign(y, vext_neon(x[1], x[2], 1)),
    # this is shift "left" in Spiral notation (and ARM too)
    # [a b c d], [e f g h] -> [b c d e]
    bin_shr2 := (self, y,x,opts) >> assign(y, vext_neon(x[1], x[2], 3)),

    # [a b c d] [e f g h] -> [e d c b]
    # support for VO1dsJ(n, v)
    bin_shrev := (self, y, x, opts) >> let(
	u := var.fresh_t("T", self.t),
	decl([u], chain(
		assign(u, vext_neon(x[1], x[2], 3)), # [ b c d e]
		assign(u, vext_neon(u, u, 2)),       # [ d e b c]
		assign(y, vrev_neon(u))))            # [ e d c b]
    ),

    loadCont := (self, n, y, yofs, x, xofs, xofs_align, opts) >> let(
	nn := _unwrap(n),
	yy := vtref(self.t, y, yofs),
	p0 := nth(x, xofs).toPtr(self.t.t),
	p2 := nth(x, xofs+2).toPtr(self.t.t),
	Cond(
	    nn=1,
	        assign(yy, vload1_neon(p0, self.vzero(), 1)),
	    nn=2,
                assign(yy, vcombine_neon(
			       vload_half_neon(p0),
			       NEON_HALF.vzero())),
	    nn=3,
                assign(yy, vcombine_neon(
			       vload_half_neon(p0),
			       vload1_half(p2, NEON_HALF.vzero(), 1))),
	    nn=4,
	        assign(yy, deref(nth(x, xofs).toPtr(self.t)))
	)
    ),

    storeCont := (self, n, y, yofs, yofs_align, x, xofs, opts) >> let(
	nn := _unwrap(n),
	xx := vtref(self.t, x, xofs),
	p0 := nth(y, yofs).toPtr(self.t.t),
	p2 := nth(y, yofs+2).toPtr(self.t.t),
	Cond(
	    nn=1,
		vstore1_neon(p0, xx, 1),
	    nn=2,
		vstore2lo_neon(p0, xx),
	    nn=3, chain(
		vstore2lo_neon(p0, xx),
		vstore1_neon(p2, xx, 3)),
	    nn=4,
	        assign(deref(nth(y, yofs).toPtr(self.t)), xx)
	)
    ),

    svload := [
        # load using subvectors of length 1
	[
            (y,x,opts) -> assign(y, vload1_neon(x[1].toPtr(NEON.t.t), NEON.vzero(), 1)),

            (y,x,opts) -> let(u := NEON.freshT(),
                    decl([u], chain(
                            assign(u, vload1_neon(x[1].toPtr(NEON.t.t), NEON.vzero(), 1)),
                            assign(y, vload1_neon(x[2].toPtr(NEON.t.t), u,            2))))),
            (y,x,opts) -> let(u := NEON.freshT(),
                    decl([u], chain(
                            assign(u, vload1_neon(x[1].toPtr(NEON.t.t), NEON.vzero(), 1)),
                            assign(u, vload1_neon(x[2].toPtr(NEON.t.t), u,            2)),
                            assign(y, vload1_neon(x[3].toPtr(NEON.t.t), u,            3))))),
            (y,x,opts) -> let(u := NEON.freshT(),
                    decl([u], chain(
                            assign(u, vload1_neon(x[1].toPtr(NEON.t.t), NEON.vzero(), 1)),
                            assign(u, vload1_neon(x[2].toPtr(NEON.t.t), u,            2)),
                            assign(u, vload1_neon(x[3].toPtr(NEON.t.t), u,            3)),
                            assign(y, vload1_neon(x[4].toPtr(NEON.t.t), u,            4)))))
	],
        # load using subvectors of length 2
        [
            (y,x,opts) -> let(
		u := var.fresh_t("T", NEON_HALF.t),
                decl([u], chain(
                        assign(u, vload_half_neon(x[1].toPtr(NEON_HALF.t.t))),
                        assign(y, vcombine_neon(u, NEON_HALF.vzero()))))),
            (y,x,opts) -> let(
		u1 := var.fresh_t("T", NEON_HALF.t),
		u2 := var.fresh_t("T", NEON_HALF.t),
                decl([u1, u2] , chain(
                        assign(u1, vload_half_neon(x[1].toPtr(NEON_HALF.t.t))),
                        assign(u2, vload_half_neon(x[2].toPtr(NEON_HALF.t.t))),
			assign(y, vcombine_neon(u1, u2)))))
        ]],

    svstore := [
        # store using subvectors of length 1
        [  (y,x,opts) -> vstore1_neon(y[1].toPtr(NEON.t.t), x, 1),
           (y,x,opts) -> chain(
               vstore1_neon(y[1].toPtr(NEON.t.t), x, 1),
               vstore1_neon(y[2].toPtr(NEON.t.t), x, 2)),
           (y,x,opts) -> chain(
               vstore1_neon(y[1].toPtr(NEON.t.t), x, 1),
               vstore1_neon(y[2].toPtr(NEON.t.t), x, 2),
               vstore1_neon(y[3].toPtr(NEON.t.t), x, 3)),
           (y,x,opts) -> chain(
               vstore1_neon(y[1].toPtr(NEON.t.t), x, 1),
               vstore1_neon(y[2].toPtr(NEON.t.t), x, 2),
               vstore1_neon(y[3].toPtr(NEON.t.t), x, 3),
               vstore1_neon(y[4].toPtr(NEON.t.t), x, 4))
        ],
        # store using subvectors of length 2
        [  (y,x,opts) -> vstore2lo_neon(y[1].toPtr(NEON.t.t), x),
           (y,x,opts) -> chain(vstore2lo_neon(y[1].toPtr(NEON.t.t), x),
                               vstore2hi_neon(y[2].toPtr(NEON.t.t), x))
        ]
    ],

    mul_cx := (self, opts) >> ((y,x,c) -> let(
	    u := self.freshU(),  v1 := self.freshT(), v2 := self.freshT(), r1 := self.freshT(),
	    decl([u, v1, v2, r1], chain(
		    assign(r1, self.t.value([-1.0,1.0,-1.0,1.0])),
         	    assign(u, vtrnq_32f(c, c)),
		    assign(v1, x * vextract_neon_4x32f(u, [0])),
		    assign(v2, vswapcx_neon(x) * vextract_neon_4x32f(u, [1])),
		    assign(y, fma(v1, v2, r1))))
    )),

    mul_cx_conj := (self, opts) >> ((y,x,c) -> let(
	    u := self.freshU(),  v1 := self.freshT(), v2 := self.freshT(), r1 := self.freshT(),
	    decl([u, v1, v2, r1], chain(
		    assign(r1, self.t.value([1.0, -1.0, 1.0, -1.0])),
         	    assign(u, vtrnq_32f(c, c)),
		    assign(v1, x * vextract_neon_4x32f(u, [0])),
		    assign(v2, vswapcx_neon(x) * vextract_neon_4x32f(u, [1])),
		    assign(y, fma(v1, v2, r1))))
    )),

    swap_cx := (y, x, opts) -> assign(y, vswapcx_neon(x)),
));

SIMD_ISA_DB.addISA(NEON);
SIMD_ISA_DB.addISA(NEON_HALF);


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

# Non-Terminals (Transforms)
# ==========================
# MP, from 03/24/01, GAPv3.4.4

#F Non-Terminaltable (Transform Table)
#F -----------------------------------
#F

#F The non-terminal table represents all symbols that can be used
#F to define an spl of type "nonTerminal". The table is meant to be
#F extended. In contrast to spls of type "symbol", an spl of type
#F "nonTerminal" has no equivalent in the SPL language. The non-terminals
#F usually represent discrete signal transforms and are used by the
#F formula generator to represent non-terminal expressions in SPL objects.
#F These are meant to be further expanded. Thus, an SPL object can be exported
#F to a valid SPL program iff it contains no non-terminals.
#F Every non-terminal is represented by a record contained in the
#F following list NonTerminalTableSPL.
#F The record has the form:
#F rec(
#F   isNonTerminal,   true, identifying a non-terminal
#F   NonTerminalOps,  operations record
#F   symbol,          a string for the symbol
#F   CheckParams,     a function for checking and canonifying
#F                    of the parameters
#F   Dim,             a function to return the dimension from
#F                    the parameters in canonical form
#F   Params,          a function that returns a shorter representation
#F                    of parameters used for printing in gap
#F   Terminate SPL,   a function to convert into an spl without non-terminals
#F   Transposed,      a function for transposing
#F   isRealTransform, = true if the transform is considered as real transform,
#F                    i.e., the matrix is real for all choices of parameters
#F )
#F
#F the following fields are optional:
#F for verification:
#F SmallRandom        a function that returns a parameter choice for a small
#F                    instantiation of the transform, i.e., one that can be verified
#F                    against the definition
#F LargeRandom        correspondingly, a function that returns a parameter choice
#F                    for a large instantiation of the transform, used for verifying
#F                    on a random vector
#F
#F HashIndexSPL       a function that returns index used for hashing. It is stored
#F                    in .hashIndex field. Overrides the standard
#F                    HashIndex function used in the search.
#F
#F hashIndex          overrides HashIndex function used in
#F                    search. Precomputed by optional HashIndexSPL
#F                    function and should not be specified manually.
#F
#F
#F Nonterminals are created in the directory formgen/transforms
#F
#F The Non-Terminal table can easily be extended by adding new non-terminals
#F (transforms).
#F

#F Non-Terminal Table
#F ------------------
#F

#F NonTerminalTableSPL
#F   is the set of all known non-terminals (transforms)
#F
NonTerminalTableSPL := Set ( [ ] );

#F NonTerminalListSPL
#F   is a set containing all known non-terminals.
#F   Note that NonTerminalListSPl is ordered exactly as NonTerminalTableSPL.
#F
NonTerminalListSPL := Set( [ ] );

#F Adding new Non-Terminals (Transforms)
#F -------------------------------------
#F

#F AddNonTerminal ( <non-terminal> )
#F   adds <non-terminal> to the non-terminal table. This function is
#F   meant to import new non-terminals into NonTerminalTableSPL,
#F   and NonTerminalListSPL, and update .index field.
#F
#F Note: Currently, this function is invoked automatically, when one
#F       for example, defines new breakdown rules for a non-terminal,
#F
AddNonTerminal := function ( S )
  local i;
  Constraint(IsNonTerminal(S));
  if not IsBound(S.__bases__) then
      Error("Old-style non-terminals are not supported anymore");
  fi;

  if S in NonTerminalTableSPL then RemoveSet(NonTerminalTableSPL, S); fi;
  AddSet(NonTerminalTableSPL, S);
  AddSet(NonTerminalListSPL, S.name);
  for i in [1..Length(NonTerminalTableSPL)] do
      NonTerminalTableSPL[i].index := i;
  od;
  #UpdateApplicableTable();
end;


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

Class(CudaUnparser, CUnparser, rec(

    simt_loop := meth(self, o, i, is)
        local v, lo, hi, l_cond, r_cond, simt_idx, simt_rng, body;

        v := o.var; lo := o.range[1]; hi := Last(o.range);
        simt_idx := o.simt_idx;
        simt_rng := o.simt_idx.get_rng();
        body := Copy(o.cmd);
        l_cond := simt_rng[1]>0; r_cond := simt_rng[2] < simt_idx.size()-1;

        if (hi-lo) < simt_rng[2]-simt_rng[1] then
            body := SubstVars(body, rec((v.id) := simt_idx + lo - simt_rng[1]));
            Print(Blanks(i), "if( ");
            if l_cond then
                Print(self(simt_idx, 0, 0), " >= ", self(simt_rng[1], 0, 0), " && ");
            fi;
            Print(self(simt_idx, 0, 0), " <= ", self(simt_rng[1]+hi-lo, 0, 0), " ) {\n",
                self(body, i+is, is),
                Blanks(i), "}\n");
        elif (hi-lo) = simt_rng[2]-simt_rng[1] then
            body := SubstVars(body, rec((v.id) := simt_idx + lo - simt_rng[1]));
            if l_cond or r_cond then
                Print(Blanks(i), "if( ");
                if l_cond then
                    Print(self(simt_idx, 0, 0), " >= ", self(simt_rng[1], 0, 0));
                fi;
                if l_cond and r_cond then
                    Print(" && ");
                fi;
                if r_cond then
                    Print(self(simt_idx, 0, 0), " <= ", self(simt_rng[2], 0, 0));
                fi;
                Print(" ) {\n");
            fi;
            self(body, When(l_cond or r_cond, i+is, i), is);
            if l_cond or r_cond then
                Print(Blanks(i), "}\n");
            fi;
        else
            body := SubstVars(body, rec((v.id) := simt_idx + v - simt_rng[1]));
            Print(When(IsBound(self.opts.looppragma), self.opts.looppragma(o,i,is)),
                Blanks(i), "for(int ", v, " = ", lo, "; ");
            if l_cond then
                Print(self(simt_idx + v-simt_rng[1], 0, 0), " >= ", lo, " && ");
            fi;
            Print(self(simt_idx + v-simt_rng[1], 0, 0), " <= ", hi, "; ",
                v, "+=", self(simt_rng[2]-simt_rng[1]+1, 0, 0), ") {\n",
                self(body, i+is, is),
                Blanks(i), "}\n");
        fi;

    end,

    Dim3 := meth(self,t,vars,i,is)
        local v, var_list;
        Print("dim3 ");
        var_list := List(vars, v-> v.id::"("::StringJoin(", ", List([v.x.value, v.y.value, v.z.value], v -> When(IsValue(v), v.v, v) ) )::")");
        self.infix(var_list, ", ");
    end,

    TArray := meth(self,t,vars,i,is)
        local dims, elt, v, ptype, vsize, var_specs;
        if Length(vars) > 1 then DoForAll(vars, v->Print(self.TArray(t, [v], i, is), "; "));
        elif Length(vars) = 0 then
            Print(self.declare(t.t, [], i, is), " *");
        else
            # at this point Length(vars)=1
            v := When(IsList(vars), vars[1], vars);
            var_specs := When(IsBound(v.decl_specs), v.decl_specs, []);
            dims := []; elt := t;
            while IsArray(elt) do Add(dims, elt.size); elt := elt.t; od;

            #FIXME/HACK: Ignoring twiddles by looking for "D" in .id
            # Better way: look for func.name="init" in parent/context
            if IsBound(self.opts.useMemoryArena) and self.opts.useMemoryArena and v.id[1] <> 'D' then
                #FIXME: Arena currently doesn't handle multiple dims.
                #FIXME: Slightly ugly hack to get this to be a pointer

                # To handle vectors. Arena is declared only for scalars. For
                # vectors, we must manually scale the allocation by vector length.
                vsize := 1; if ObjId(elt)=TVect then vsize := elt.size; fi;
                ptype := Concatenation(elt.name, "Pointer");
                self.(ptype)(elt, [v], i, is);
                Print(" =  &(ARENA[ (arenalevel-=",self(dims[1]*vsize,i,is),") ])");
            else
                if var_specs <> [] then
                    Print(self.infix(var_specs, " "), " ");
                fi;
                if IsBound(v.decl_as_ptr) and v.decl_as_ptr then
                    Print(self.declare(t.t, [], i, is), "*", v.id);
                else
                    self.(elt.name)(elt, [v], i, is);
                    DoForAll(dims, d->Print("[",self(d,i,is),"]"));
                fi;
            fi;

        fi;
    end,

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

    cu_event_create := (self, o, i, is) >> Print(Blanks(i), "cudaEventCreate(", self(o.args[1], 0, 0),
                            When(Length(o.args)>1 and not o.args[2].v, ")", ");\n")),

    cu_event_record := (self, o, i, is) >> Print(Blanks(i), "cudaEventRecord(", self(o.args[1], 0, 0),
                            When(Length(o.args)>1 and not o.args[2].v, ")", ");\n")),

    cu_event_destroy := (self, o, i, is) >> Print(Blanks(i), "cudaEventDestroy(", self(o.args[1], 0, 0),
                            When(Length(o.args)>1 and not o.args[2].v, ")", ");\n")),

    cu_event_elapsed_time := (self, o, i, is) >> Print(Blanks(i), "cudaEventElapsedTime",
                                self.pinfix(o.args{[1..3]}, ", "),
                                When(Length(o.args)>3 and not o.args[4].v, "", ";\n")),

    cu_event_synchronize := (self, o, i, is) >> Print(Blanks(i), "cudaEventSynchronize(", self(o.args[1], 0, 0),
                            When(Length(o.args)>1 and not o.args[2].v, ")", ");\n")),

    cu_device_synchronize := (self, o, i, is) >> Print(Blanks(i), "cudaDeviceSynchronize(",
                            When(Length(o.args)>0 and not o.args[1].v, ")", ");\n")),

    cprintf := (self, o, i, is) >> Print(Blanks(i), "printf(\"", o.args[1].v, "\", ",
                                            self.infix(o.args{[2..Length(o.args)]}, ", "), ");\n"),

    func := (self, o, i, is) >> let(
        parameters:=Flat(o.params),
        id := Cond(o.id="transform" and IsBound(self.opts.subName),
                     self.opts.subName,
                   o.id="init"      and IsBound(self.opts.subName),
                     Concat("init_",self.opts.subName),
                   o.id="destroy"      and IsBound(self.opts.subName),
                     Concat("destroy_",self.opts.subName),
                   o.id),
		When ((IsBound(self.opts.wrapCFuncs) and self.opts.wrapCFuncs), Print("\nextern \"C\" {")),
        Print("\n", Blanks(i),
            self.opts.funcModifier, self.declare(o.ret, var(id, o.ret), i, is), "(",
            DoForAllButLast(parameters, p->Print(self.declare(p.t, p,i,is), ", ")),
            When(Length(parameters)>0, self.declare(Last(parameters).t, Last(parameters),i,is), ""), ") ",
            "{\n",
            When(IsBound(self.opts.postalign), DoForAll(parameters, p->self.opts.postalign(p,i+is,is))),
            self(o.cmd, i+is, is),
            Blanks(i),
            "}\n"),
		When ((IsBound(self.opts.wrapCFuncs) and self.opts.wrapCFuncs), Print("}\n"))),

    specifiers_func := (self, o, i, is) >> let(
        parameters:=Flat(o.params),
        id := o.id,
        Print("\n", Blanks(i),
            self.opts.funcModifier, self.infix(o.decl_specs, " "), " ", self.declare(o.ret, var(id, o.ret), i, is), "(",
            DoForAllButLast(parameters, p->Print(self.declare(p.t, p,i,is), ", ")),
            When(Length(parameters)>0, self.declare(Last(parameters).t, Last(parameters),i,is), ""), ") ",
            "{\n",
            When(IsBound(self.opts.postalign), DoForAll(parameters, p->self.opts.postalign(p,i+is,is))),
            self(o.cmd, i+is, is),
            Blanks(i),
            "}\n")),

    cu_call := (self, o, i, is) >>
        Print(Blanks(i), o.func,
                "<<<",
                self.infix([o.dim_grid, o.dim_block], ", "),
                ">>>",
                self.pinfix(o.args, ", "), ";\n"),

    cu_allocate := (self, o, i, is) >> Print(Blanks(i),
        "cudaMalloc(&",
        self(o.loc, 0, 0), ", ", self(o.size, 0, 0), "*sizeof(", self.declare(o.type,[], 0, 0), "));\n"),

    cu_allocate_managed := (self, o, i, is) >> Print(Blanks(i),
        "cudaMallocManaged(&",
        self(o.loc, 0, 0), ", ", self(o.exp.size, 0, 0), "*sizeof(", self.declare(o.exp.t,[], 0, 0), "));\n"),

    cu_memcpy := (self, o, i, is) >> Print(Blanks(i),
        "cudaMemcpy(", self(o.loc, 0, 0), ", ", self(o.exp, 0, 0), ", ", self(o.size, 0, 0), "*sizeof(", self.declare(o.loc.t.t,[], 0, 0),
            "), ", o.kind, ");\n"),

    cu_memcpy_to_sym := (self, o, i, is) >> Print(Blanks(i),
        "cudaMemcpyToSymbol(", self(o.loc, 0, 0), ", ", self(o.exp, 0, 0), ", ", self(o.size, 0, 0), "*sizeof(", self.declare(o.loc.t.t,[],0,0), ") );\n"),

    cu_check_errors := (self, o, i, is) >> Print(Blanks(i), "checkCudaErrors( ", self(o.args[1], 0, 0), " );\n"),

    cu_free := (self, o, i, is) >> Print(Blanks(i), "cudaFree(", self(o.args[1], 0, 0), ");\n"),

#    cospi := (self,o,i,is) >> Print("_"::o.name, self.pinfix(o.args, ", ")),
#    sinpi := (self,o,i,is) >> Print("_"::o.name, self.pinfix(o.args, ", ")),

    simtThreadIdxX := (self, o, i, is) >> Print("threadIdx.x"),
    simtThreadIdxY := (self, o, i, is) >> Print("threadIdx.y"),
    simtThreadIdxZ := (self, o, i, is) >> Print("threadIdx.z"),

    simtBlockIdxX := (self, o, i, is) >> Print("blockIdx.x"),
    simtBlockIdxY := (self, o, i, is) >> Print("blockIdx.y"),
    simtBlockIdxZ := (self, o, i, is) >> Print("blockIdx.z"),

    simt_syncgrid := (self, o, i, is) >> Error("Grid-level sync not supported."),
    simt_syncblock := (self, o, i, is) >> Print(Blanks(i), "__syncthreads();\n"),
    simt_synccluster := (self, o, i, is) >> Print(Blanks(i), "__syncwarp();\n")

    ));

CudaDefaults := CopyFields(SpiralDefaults,
                                rec(
                                    globalUnrolling := 10,
                                    useDeref := false,
                                    generateInitFunc := false,
                                    includes := [],
                                    arrayBufModifier := "",
                                    arrayDataModifier := "",

                                    mempool := true,
                                    use_shmem := true,
                                    gpu_timing := true,
                                    sumsgen := SIMTSumsGen,
                                    codegen :=  CudaCodegen,
                                    unparser := CudaUnparser,
                                    cuda_version := "10.1",
                                )
                    );

TitanVDefaults := CopyFields(CudaDefaults,
                                rec(
                                    codename := "GV100-400-A1",
                                    max_l1_size := 128*1024,
                                    max_shmem_size := 96*1024,
                                    l2_size := 4.5*2^20,
                                    devmem_size := 12*2^30
                                )
                    );


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

## The hoister is a compiler pass that separates looped code in three
## parts:
##  - the hoisted code a.k.a before the loop
##  - the body of the loop, a.k.a the loop itself
##  - the epilogue of the loop, a.k.a what is run after the loop
##
## Essentially, it is done by separating statements that depend on the loop
## index, that have to be in the body from statements that do not which can
## be hoisted.
##
## On top of this, the following code performs two optimizations:
##  - if code is being stored to a memory location that is loop independent,
##    then this memory location is scalarized, i.e. the store is performed in
##    register and the memory write is delayed (put inside the epilogue)
##  - The code performs loop variable induction. This replaces statements that
##    depend of param*i, by accumulators that add plus param at each pass of
##    the loop. The code also attempts to discover the minimal accumulation
##    value to have a few number of window pointers

Hoister := function(c)
    local loop_idx, loop_range, idxpool, hoist, body, epilogue, lvlist, lvfifo, asns, asn, lv, freevars,
    candidate_memory_acc, confirmed_memory_acc, stmt, stmts, lv, freevars, equal_second_member, d, step,
    lvfamilies, fams, fam, s, cc, pointer, j, var_usage, members, member_usage, rootexpr, m, newloop_idx, newloop_range, newloop_start, newloop_inc ;
    #Hoister only applies to loopn(decl(chain(...)))
    if ((ObjId(c)=loopn) and (ObjId(c.cmd)=decl) and (ObjId(c.cmd.cmd)=chain)) then

         c := Compile.pullDataDeclsRefs(c);
         loop_idx := c.var;
         loop_range := c.range;
         c := c.cmd;
         MarkDefUse(c);
         #idxpool will contain the set of all variables that change with the loop index
         #(and therefore should not be hoisted)
         idxpool := Set([loop_idx]);

         #Loop variable induction
         #This gathers all expressions that are linear in the index variable
         #and stores them as (v, start, inc) in the loop-variable list (lvlist)
         lvlist := [];
         lvfifo := [];

         Add(lvfifo, rec(v := loop_idx,
                     start := loop_idx.t.zero(),
                     inc := V(1)));

         while(Length(lvfifo)>0) do

             lv := lvfifo[1];
             lvfifo := ListWithout(lvfifo, 1);
             Add(lvlist, lv);

             #If lv.start is a variable, add this variable to the lvfifo
             if IsVar(lv.start) then
                  #If lv.start is used twice, it cannot be used as an induction variable.
                  if (Length(Collect(c, [assign, lv.start, @(0)]))=1
                      and Length(Collect(c, lv.start))=1) then
                      SubstBottomUp(c, lv.v, e->lv.start);
                      SubstBottomUp(lvfifo, lv.v, e->lv.start);
                      SubstBottomUp(lvlist, lv.v, e->lv.start);

                      [asns, c] := Pull(c, [assign, lv.start, @(0)],e->skip(), e->e);

                      Add(lvfifo, rec(v := lv.start,
                              start := asns[1].exp,
                              inc := lv.inc));
                      AddSet(idxpool, lv.start);

                 fi;
             fi;

             #Gather all mults and adds that depend on lv and add them too
             [asns, c] := Pull(c, [assign, @(0), [add, @(1).cond(e->ObjId(e)<>Value), lv.v]],
                 e->skip(), e->e);
             for asn in asns do
                 Add(lvfifo, rec(v := asn.loc,
                         start := add(asn.exp.args[1], lv.start),
                         inc := lv.inc));
                 AddSet(idxpool, asn.loc);
                 od;

             [asns, c] := Pull(c, [assign, @(0), [add, lv.v, @(2).cond(e->ObjId(e)<>Value)]],
                 e->skip(), e->e);
             for asn in asns do
                 Add(lvfifo, rec(v := asn.loc,
                         start := add(asn.exp.args[2], lv.start),
                         inc := lv.inc));
                 AddSet(idxpool, asn.loc);
                 od;

             [asns, c] := Pull(c, [assign, @(0), [mul, @(1), lv.v]],
                 e->skip(), e->e);
             for asn in asns do
                 Add(lvfifo, rec(v := asn.loc,
                     start := mul(asn.exp.args[1], lv.start),
                     inc := mul(asn.exp.args[1],  lv.inc)));
                 AddSet(idxpool, asn.loc);
                 od;

             [asns, c] := Pull(c, [assign, @(0), [mul, lv.v, @(2)]],
                 e->skip(), e->e);
             for asn in asns do
                 Add(lvfifo, rec(v := asn.loc,
                     start := mul(asn.exp.args[2], lv.start),
                     inc := mul(asn.exp.args[2],  lv.inc)));
                 AddSet(idxpool, asn.loc);
                 od;

         od;

         #Some of the lvs were only used by other lvs, let's kick
         #them out
         freevars := Set(c.free());
         lvlist := Filtered(lvlist, lv -> lv.v in freevars);

         #Now we partition the lvs into bins that have the same increment
         #We call it an lvfamily
         lvfamilies:=[];
         for lv in lvlist do
             fams := Filtered(lvfamilies, fam->fam.inc=lv.inc);
             if (Length(fams)>0) then
                 Add(fams[1].list, lv);
             else
                 Add(lvfamilies, rec(inc:=lv.inc, list:=[lv]));
             fi;
         od;

         #If the increment is an operation, hoist this operation
         for lv in lvfamilies do
             if (ObjId(lv.inc) in [add, mul]) then
                d := var.fresh_t("d", lv.inc.t);
                Add(c.cmds, assign(d, lv.inc));
                lv.inc := d;
             fi;
         od;

         #OK, so this is the tricky part.
         #we want to fuse lvfamilies together if it is possible.
         #it is only possible if members of the family are used sequentially in the
         #loop AND if we know the pattern

         var_usage := [];
         DoForAll(c.rChildren(), function(x) if IsAssign(x) then Append(var_usage, x.op_in()); fi; end);

         for j in [1..Length(lvfamilies)] do
           fam:=lvfamilies[j];
           members:=List(fam.list, x->x.v);
           member_usage := RemoveAdjacentDuplicates(Filtered(var_usage, x -> x in members));

           if (Length(Set(member_usage))=Length(member_usage) and Length(members)>1) then
               #ok so this is a good candidate, but can we match it?
               #our target is that they all are add( add(param, i*param), param)
               rootexpr := fam.list[1].start;

               if (ObjId(rootexpr)=add) then
                   d := rootexpr.args[2];
                   equal_second_member:=true;
                   for m in fam.list do
                   if ((ObjId(m.start)<>add) or (m.start.args[2]<>d)) then
                       equal_second_member:=false;
                   fi;
                   od;

                   if ((equal_second_member) and (rootexpr.args[1] in fam.list[2].start.args[1].pred)) then
                           step := Difference(fam.list[2].start.args[1].pred, Set([rootexpr.args[1]]));
                           if (Length(step)=1) then
                               pointer := 2;
                               cc := [];
                               for d in c.rChildren() do
                               if ((Length(fam.list)>=pointer) and (members[pointer] in d.op_in())) then
                                   pointer:=pointer+1;
                              Add(cc, assign(members[1], add(members[1], step[1])));
                               fi;
                               Add(cc, d);
                               od;
                               c := chain(cc);

                               d := sub(fam.inc, mul(V(Length(members)-1), step[1]));
                               s := var.fresh_t("inc", d.t);
                               Add(cc, assign(s, d));

                               c := chain(cc);
                               lvfamilies[j] := rec(inc := s,
                                   list:=[rec(v:=members[1],
                                           start:=rootexpr,
                                           inc:=lvfamilies[j].inc)]);
                               for d in [2..Length(members)] do
                               SubstBottomUp(c, members[d], e->members[1]);
                               od;
                           fi;
                   fi;
               fi;
           fi;
        od;

         #This is the hoister logic
         candidate_memory_acc := Set([]);
         confirmed_memory_acc := Set([]);
         hoist := [];
         body := [];
         epilogue := [];

         for asn in c.cmds do
             if IsAssign(asn) then
                 s := asn.op_in();
                 IntersectSet(s, idxpool);
                 if (Length(s)=0) then
                     if (ObjId(asn.exp)=deref) then
                         #cannot assume SSA on derefs
                         #so we mark it and leave it
                         #we'll fix that later
                         AddSet(candidate_memory_acc, asn.exp);
                     fi;
                     Add(hoist, asn);
                 else
                     if ((ObjId(asn.loc)=deref) and (asn.loc in candidate_memory_acc)) then
                         AddSet(confirmed_memory_acc, asn.loc);
                     fi;
                     Add(body, asn);
                     UniteSet(idxpool, asn.op_out());
                 fi;
             else
                 if (ObjId(asn)<> skip) then
                     Error("Non assigns cannot be handled");
                 fi;
             fi;
         od;

         #Plug back the induced vars
         newloop_idx := [];
         for lv in lvfamilies do
            if ((newloop_idx=[])and(Length(lv.list)=1)) then
                newloop_idx := lv.list[1].v;
                d := lv.list[1].start;
                s := var.fresh_t("init", d.t);
                Add(hoist, assign(s, d));
                lv.list[1].start := s;
                d := add(lv.list[1].start, mul(loop_range, lv.list[1].inc));
                s := var.fresh_t("ubound", d.t);
                Add(hoist, assign(s, d));
                newloop_range := s;
                newloop_start := lv.list[1].start;
                newloop_inc := lv.inc;
            else
                for d in lv.list do
                   Add(hoist, assign(d.v, d.start));
                   Add(body, assign(d.v, add(d.v, lv.inc)));
                od;
            fi;
         od;
         if (newloop_idx=[]) then
             newloop_idx := loop_idx;
             newloop_range := loop_range;
             newloop_start := V(0);
             newloop_inc := V(1);
         fi;
         Add(hoist, assign(newloop_idx, newloop_start));
         Add(body, assign(newloop_idx, add(newloop_idx, newloop_inc)));

         #This is the memory accumulator logic
         for d in confirmed_memory_acc do
             asns := Collect(hoist, [assign, @(1), [deref, d.loc]]);
             if Length(asns)<>1 then
                 Error("Multi assign in hoist????");
             else
                 SubstBottomUp(body, [deref, d.loc], e->asns[1].loc);
                 Add(epilogue, assign(d, asns[1].loc));
             fi;
         od;

         c := Compile.declareVars(chain(
                 chain(hoist),
                 doloop(newloop_idx, newloop_range, chain(body)),
                 chain(epilogue)));
    fi;
    return c;
end;


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

# dummy to restart doc string
_dummy := 0;

#F ExhaustiveSearch(spl, opts)
#F

ExhaustiveSearch := function(spl, opts)
	local idxList, resList, bestRec;

	if not IsSPL(spl) then
		Error("invalid SPL");
	fi;

	idxList := [1 .. NofRuleTrees(spl, opts)];

	resList := TimeRuleTrees(spl, opts, idxList);

	bestRec := BestTimedRuleTree(resList);
	bestRec.ruletree := RuleTreeN(spl, bestRec.index, opts);

	return bestRec;
end;


f := function(n)
  return f(n+1);
end;

# Now loop until an error occurs
f(0);

# Error message :
#   Entering break read-eval-print loop ...
#   you can 'quit;' to quit to outer loop, or
#   you may 'return;' to continue

n;
# 4998

# quit "brk mode" and return to GAP
quit;


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

# Cell Unparser
# Author: schellap

@TInt := @.cond(x->x.t=TInt);
@TRealOrInt := @.cond(x->x.t=TInt or x.t=TReal);
@TReal := @.cond(x->x.t=TReal);
@TVect := @.cond(x->ObjId(x.t)=TVect);

Class(CellUnparser, CMacroUnparserProg, rec(

    func_ppe := (self,o,i,is) >> "",

    # str = format string with $n for arguments ($1 = first argument)
    # list of arguments , arguments are printed using this unparser
    printf := (self, str, args) >> ApplyFunc(PrintEvalF, Concatenation([str],
        List(args, a->(When(IsFunc(a), a, ()->self(a,0,0)))))),

    # -----------------------------
    # ISA independent constructs
    # -----------------------------
    nth :=  (self, o, i, is) >> self.printf("$1[$2]", [o.loc, o.idx]),
    fdiv := (self, o, i, is) >> self.printf("((($3)$1) / $2)", Concatenation(o.args, [self.opts.vector.isa.ctype])),
    fdiv := (self, o, i, is) >> self.printf("($1 / $2)", o.args),
    idiv := (self, o, i, is) >> self.printf("($1 / $2)", o.args),
    imod := (self, o, i, is) >> self.printf("($1 % $2)", o.args),

    # This is the type used for declarations of vector variables
    ctype := (t, isa) -> Cond(
        #t in [TDouble, TVect(TDouble, 1)], "float",
        t = TReal,              isa.ctype, #evals to "float" or "double"
        t = TVect(TReal, 2),    isa.vtype, #evals to "vector double"
        t = TVect(TReal, 4),    isa.vtype,
        t = TVect(TReal, 8),    isa.vtype,
        Error("Unparser doesn't know how to declare type: ", t)
    ),

    Value := (self, o, i, is) >> Cond(
        o.t = TReal,
           let(v := When(IsCyc(o.v), ReComplex(Complex(o.v)), Double(o.v)), When(v<0, Print("(", v, ")"), Print(v))),
        o.t = TInt,
           When(o.v < 0, Print("(", o.v, ")"), Print(o.v)),
        ObjId(o.t)=TVect,
           Print("((", self.ctype(o.t, self.opts.vector.isa), "){", self.infix(o.v, ", "), "})"),
        #NOTE: Adding this for parallel cell, but this shouldn't be needed, right?
       IsArray(o.t),
           Print("{", self.infix(o.v, ", "), "}"),
       o.t = TString,
           #Print("\"", o.v, "\"")
           Print(o.v),
       o.t = TBool, Print(When(o.v, "1", "0"))

    ),

    vdup         := (self, o, i, is) >> Print("spu_splats((", self.opts.vector.isa.ctype, ")", self(o.args[1], i, is), ")"),
    vsplat_8x16i := (self, o, i, is) >> Print("spu_splats((", self.opts.vector.isa.ctype, ")", self(o.args[1], i, is), ")"),
    vsplat_4x32f := (self, o, i, is) >> Print("spu_splats((", self.opts.vector.isa.ctype, ")", self(o.args[1], i, is), ")"),
    vsplat_2x64f := (self, o, i, is) >> Print("spu_splats((", self.opts.vector.isa.ctype, ")", self(o.args[1], i, is), ")"),

    # Declarations
    TVect := (self, t, vars, i, is) >> let(ctype := self.ctype(t, self.opts.vector.isa),
              Print(ctype, " ",
              self.infix(vars, ", "))),

    TVectPointer := (self, t, vars, i, is) >> let(ctype := self.ctype(t, self.opts.vector.isa),
              Print(ctype,
              When(IsBound(self.opts.useMemoryArena) and self.opts.useMemoryArena, "* ", " "),
              self.infix(vars, ", "))),

    TReal := ~.TVect,
    TRealPointer := ~.TVectPointer,

    TInt := (self, t, vars, i, is) >> Print("int ", self.infix(vars, ", ")),

    # Arithmetic
    # --------------------------------
    # -- mul --
    mul := (self, o, i, is) >> CondPat(o,
    [mul, @TReal, @TVect],
        Cond(self.opts.vector.isa.v = 2,
            self.printf("spu_mul(((vector $1){$2,$2}), $3)",       [self.ctype(o.args[1].t, self.opts.vector.isa), o.args[1], o.args[2]]),
        self.opts.vector.isa.v = 4,
            self.printf("spu_mul(((vector $1){$2,$2,$2,$2}), $3)", [self.ctype(o.args[1].t, self.opts.vector.isa), o.args[1], o.args[2]]),
        Error("Don't know how to unparse vector arch of length: ", self.opts.vector.isa.v)
        ),
    [mul, @TVect,   @TVect],
        self.printf("spu_mul($1, $2)",              [o.args[1], o.args[2]]),
    [mul, @TRealOrInt, @TRealOrInt],
        self.printf("($1 * $2)", o.args),
    [mul, @TRealOrInt, @TRealOrInt, @TRealOrInt],
        self.printf("($1 * $2 * $3)", o.args),
    Error("Don't know how to unparse <o>. Unrecognized type combination: ", o)),

    # -- add --
    add := (self, o, i, is) >> When(Length(o.args) > 2,
       self(_computeExpType(add(o.args[1], _computeExpType(ApplyFunc(add, Drop(o.args, 1))))), i, is),
       CondPat(o,
           [add, @TVect,   @TVect],
              self.printf("spu_add($1, $2)", [o.args[1], o.args[2]]),
           #[add, @TRealOrInt, @TRealOrInt],
           [add, @TVect,   @],
              self.printf("spu_add($1, $2)", [o.args[1], o.args[2]]),
           [add, @, @],
              self.printf("($1 + $2)", o.args),
           Error("Don't know how to unparse <o>. Unrecognized type combination")
       )
    ),

    # -- sub --
    sub := (self, o, i, is) >> When(Length(o.args) > 2,
       self(_computeExpType(sub(o.args[1], _computeExpType(ApplyFunc(sub, Drop(o.args, 1))))), i, is),
       CondPat(o,
           [sub, @TVect,   @TVect],
              self.printf("spu_sub($1, $2)", [o.args[1], o.args[2]]),
           [sub, @TVect,   @],
              self.printf("spu_sub($1, $2)", [o.args[1], o.args[2]]),
           [sub, @TRealOrInt, @TRealOrInt],
              self.printf("($1 - $2)", o.args),
           [sub, @, @],
              self.printf("($1 - $2)", o.args),
           Error("Don't know how to unparse <o>. Unrecognized type combination")
       )
    ),

    # -- neg --
    neg := (self, o, i, is) >> CondPat(o,
       #[neg, @TVect],
       #    self(o.t.value(Replicate(o.t.size,0)) - o.args[1], i, is),
       #self.printf("(-$1)", o.args)
       [neg, @TVect], Cond( self.opts.vector.isa.v = 2, self.printf("_negated2($1)", o.args),
                            self.opts.vector.isa.v = 4, self.printf("_negatef4($1)", o.args),
                            Error("What kind of vector length are you trying to pull on me?")
                ),
        self.printf("(-$1)", o.args)
    ),

    v_neg01 := (self, o, i, is) >> self.printf("_negated2($1)", o.args),

    vpack := (self, o, i, is) >> Print(self.opts.vector.isa.vconstv, "{", self.infix(o.args, ", "), "}"),

    # Spiral: fma(rt, x, y, z)    -> rt = x + (y*z)
    # SPU:    FMA(rt, ra, rb, rc) -> rt = (ra*rb) + rc
    # -- fma --
    fma  := (self, o, i, is) >> CondPat(o,
       [fma, @TVect, @TVect, @TVect],
          self.printf("spu_madd($2, $3, $1)",  [o.args[1], o.args[2], o.args[3]]),
       [fma, @TVect, @TReal, @TVect],
          self.printf("spu_madd(((vector $4){$2,$2,$2,$2}), $3, $1)",  [o.args[1], o.args[2], o.args[3], self.ctype(o.args[2].t, self.opts.vector.isa)]),
       [fma, @TReal, @TReal, @TReal],
          self.printf("$1 + ($2 * $3)", [o.args[1], o.args[2], o.args[3]]),
        Error("Don't know how to unparse fma instruction <o>. Unrecognized type combination:", o.args[1].t, o.args[2].t, o.args[3].t)
    ),

    # Spiral: fms(rt, x, y, z)    -> rt = x - (y*z)
    # SPU:    FNMS(rt, ra, rb, rc) -> rt = rc - (ra*rb)
    fms  := (self, o, i, is) >> CondPat(o,
       [fms, @TVect, @TVect, @TVect],
          self.printf("spu_nmsub($2, $3, $1)",  [o.args[1], o.args[2], o.args[3]]),
       [fms, @TVect, @TReal, @TVect],
          self.printf("spu_nmsub(((vector $4){$2,$2,$2,$2}), $3, $1)",  [o.args[1], o.args[2], o.args[3], self.ctype(o.args[2].t, self.opts.vector.isa)]),
       [fms, @TInt, @TVect, @TVect], #HACK
          self.printf("spu_nmsub($2, $3, (($4){$1,$1,$1,$1}))",  [o.args[1].v, o.args[2], o.args[3], self.ctype(o.args[2].t, self.opts.vector.isa)]),
       [fms, @TInt, @TReal, @TVect], #HACK
          self.printf("spu_nmsub(((vector $4){$2,$2,$2,$2}), $3, ((vector $4){$1,$1,$1,$1}))",  [o.args[1].v, o.args[2], o.args[3], self.ctype(o.args[2].t, self.opts.vector.isa)]),
       [fms, @TReal, @TReal, @TReal],
          self.printf("$1 + ($2 - $3)", [o.args[1], o.args[2], o.args[3]]),
        Error("Don't know how to unparse fms instruction <o>. Unrecognized type combination")
    ),

    # Spiral: nfma(rt, x, y, z)   -> rt = (y*z) - x
    # SPU:    FMS(rt, ra, rb, rc) -> rt = (ra*rb) - rc
    #NOTE: Change above and below vector constants to spu_splats so the v=2 or v=4 cases are both implicitly taken care of
    nfma := (self, o, i, is) >> CondPat(o,
       [nfma, @TVect, @TVect, @TVect],
          self.printf("spu_msub($2, $3, $1)",  [o.args[1], o.args[2], o.args[3]]),
       [nfma, @TVect, @TReal, @TVect],
          self.printf("spu_msub(((vector $4){$2,$2,$2,$2}), $3, $1)",  [o.args[1], o.args[2], o.args[3], self.ctype(o.args[2].t, self.opts.vector.isa)]),
        Error("Don't know how to unparse nfma instruction <o>. Unrecognized type combination")
    ),

    # logic
    # --------------------------------
    bin_and := (self, o, i, is) >> self.prefix("spu_and", o.args),

    bin_or := (self, o, i, is) >> self.prefix("spu_or", o.args),

    # comparison
    # --------------------------------
    eq := (self, o, i, is) >> CondPat(o,
        [eq, @TVect, @], self.printf("spu_cmpeq($1, spu_splat($2))", o.args),
        [eq, @, @TVect], self.printf("spu_cmpeq(spu_splat($1), $2)", o.args),
        [eq, @, @], self.printf("(($1) == ($2))", o.args)
        ),
    cmpg := (self, o, i, is) >> CondPat(o,
        [eq, @TVect, @], self.printf("spu_cmpgt($1, spu_splat($2))", o.args),
        [eq, @, @TVect], self.printf("spu_cmpgt(spu_splat($1), $2)", o.args),
        [eq, @, @], self.printf("(($1) > ($2))", o.args)
        ),
    cmpl := (self, o, i, is) >> CondPat(o,
        [eq, @TVect, @], self.printf("spu_LT_IS_EXPENSIVE($1, spu_splat($2))", o.args),
        [eq, @, @TVect], self.printf("spu_LT_IS_EXPENSIVE(spu_splat($1), $2)", o.args),
        [eq, @, @], self.printf("(($1) < ($2))", o.args)
        ),

    promote_spu8x16i := (self, o, i, is) >> self.prefix("spu_promote", o.args),
    promote_spu4x32f := (self, o, i, is) >> self.prefix("spu_promote", o.args),
    promote_spu2x64f := (self, o, i, is) >> self.prefix("spu_promote", o.args),

    extract_spu8x16i := (self, o, i, is) >> self.prefix("spu_extract", o.args),
    extract_spu4x32f := (self, o, i, is) >> self.prefix("spu_extract", o.args),
    extract_spu2x64f := (self, o, i, is) >> self.prefix("spu_extract", o.args),

    insert_spu8x16i  := (self, o, i, is) >> self.prefix("spu_insert",  o.args),
    insert_spu4x32f  := (self, o, i, is) >> self.prefix("spu_insert",  o.args),
    insert_spu2x64f  := (self, o, i, is) >> self.prefix("spu_insert",  o.args),

    vparam_spu := (self, o, i, is) >>
      Print("((vector unsigned char){", PrintCS(prep_perm_string_spu(o.p)), "})"),

   vzero_8x16i := (self, o, i, is) >> Print("((", self.opts.vector.isa.vtype ,"){", PrintCS(List([1..self.opts.vector.isa.v], i->0)), "})"),
   vzero_4x32f := (self, o, i, is) >> Print("((", self.opts.vector.isa.vtype ,"){", PrintCS(List([1..self.opts.vector.isa.v], i->0)), "})"),
   vzero_2x64f := (self, o, i, is) >> Print("((", self.opts.vector.isa.vtype ,"){", PrintCS(List([1..self.opts.vector.isa.v], i->0)), "})"),

   # ----------------------------------------------------------------------------------
   # ISA specific : spu_8x16i
   # ----------------------------------------------------------------------------------
   vperm_8x16i_spu := (self, o, i, is) >>
     self.printf("spu_shuffle($1, $2, $3)", o.args),

   vuperm_8x16i_spu := (self, o, i, is) >>
     self.printf("spu_shuffle($1, $1, $2)", o.args),

   vloadu8_spu8x16i := (self, o, i, is) >>
   Print("spu_or(spu_slqwbyte(*((vector signed short*) (&(",self(o.args[1],i,is),"))), (unsigned) ((vector signed short*) (&(",self(o.args[1],i,is),"))) & 15), spu_rlmaskqwbyte(*(((vector signed short*) (&(",self(o.args[1],i,is),")))+1), ((unsigned) ((vector signed short*) (&(",self(o.args[1],i,is),"))) & 15)-16))"),

   # ----------------------------------------------------------------------------------
   # ISA specific : spu_4x32f
   # ----------------------------------------------------------------------------------
   vperm_4x32f_spu := (self, o, i, is) >>
     self.printf("spu_shuffle($1, $2, $3)", o.args),

   vuperm_4x32f_spu := (self, o, i, is) >>
     self.printf("spu_shuffle($1, $1, $2)", o.args),

   #vloadu1,2,4 should produce:
   #spu_shuffle(spu_slqwbyte((*((vector float *)(&X[element]))), (unsigned int)(&X[element]) & 15), ((vector float){0,0,0,0}), ((vector unsigned char){0, 1, 2, 3, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128}));
   #spu_shuffle(spu_promote(X[element], 0), spu_promote(X[element+1], 0), ((vector unsigned char){0, 1, 2, 3, 16, 17, 18, 19, 128, 128, 128, 128, 128, 128, 128, 128}));
   #spu_or(spu_slqwbyte(*ptr,            (unsigned) ptr & 15), spu_rlmaskqwbyte(*(ptr+1), ((unsigned) ptr & 15)-16));

   #NOTE: Both these are HACKs!
   slqwbyte_spu4x32f := (self, o, i, is) >>
       self.printf("spu_slqwbyte(*$1, (unsigned) ($1) & 15)", o.args),

   rlmaskqwbyte_spu4x32f := (self, o, i, is) >>
       self.printf("spu_rlmaskqwbyte(*$1, ((unsigned) $1 & 15)-16)", o.args),

   vloadu_spu4x32f := (self, o, i, is) >>
       Print("spu_or(spu_slqwbyte(*",self(o.args[1],i,is),", (unsigned) ",self(o.args[1],i,is)," & 15), spu_rlmaskqwbyte(*(",self(o.args[1],i,is),"+1), ((unsigned) ",self(o.args[1],i,is)," & 15)-16))"),

   vloadu4_spu4x32f := (self, o, i, is) >>
   Print("spu_or(spu_slqwbyte(*((vector float*) (&(",self(o.args[1],i,is),"))), (unsigned) ((vector float*) (&(",self(o.args[1],i,is),"))) & 15), spu_rlmaskqwbyte(*(((vector float*) (&(",self(o.args[1],i,is),")))+1), ((unsigned) ((vector float*) (&(",self(o.args[1],i,is),"))) & 15)-16))"),

# spu_or(
#       spu_slqwbyte(*((vector float*) (&(",ptr,"))), (unsigned) ((vector float*) (&(",ptr,"))) & 15),
#       spu_rlmaskqwbyte(*(((vector float*) (&(",ptr,")))+1), ((unsigned) ((vector float*) (&(",ptr,"))) & 15)-16))

   #spu_or(spu_slqwbyte(*ptr,            (unsigned) ptr & 15), spu_rlmaskqwbyte(*(ptr+1), ((unsigned) ptr & 15)-16));

   # ----------------------------------------------------------------------------------
   # ISA specific : spu_2x64f
   # ----------------------------------------------------------------------------------

   vperm_2x64f_spu := (self, o, i, is) >>
     self.printf("spu_shuffle($1, $2, $3)", o.args),

   vuperm_2x64f_spu := (self, o, i, is) >>
     self.printf("spu_shuffle($1, $1, $2)", o.args),

   # ----------------------------------------------------------------------------------
   # Cell Parallel unparser
   # ----------------------------------------------------------------------------------
   extraHeader := "TODO",
#Concat("#include <spu_mfcio.h>\n\
#extern spe_infostruct spe_info;\n\
#//extern DATATYPE_NO_QUOTES* Xalt;\
#//extern DATATYPE_NO_QUOTES* Yalt;\
#extern DATATYPE_NO_QUOTES* XYalt;\
#extern mfc_list_element_t gathlist[2048], scatlist[2048];\
##define Xalt XYalt\
##define Yalt XYalt\
#/* extern volatile int writeSignal[4];\n\
#extern int check;\n\
#extern void sig_barrier();\n\
#n*/\n\
##include \"spumacros.h\"\n\
#// Declare memory arena\
##ifdef ARENA_SIZE\
#  __attribute__((aligned(16))) DATATYPE_NO_QUOTES ARENA[ARENA_SIZE];\
#  int arenalevel = ARENA_SIZE;\
##endif\
#\n\
#\n\n\n"),

   dist_loop := meth(self, o, i, is)
       Print(Blanks(i),    "{\n");
       #Print(Blanks(i+is), "unsigned int ", o.var, " = spe_info.spuid;\n"); # Not needed since spuid is now a param
       self(o.cmd,i+is,is);
       Print(Blanks(i),    "}\n");
       #Print(Blanks(i),    "//ALL_TO_ALL_BARRIER;\n");
       #Print(Blanks(i),    "BLOCK_ON_READ();\n");
    end,

    #NOTE: this should really be just a berrier (shouldn't have
    #BLOCK_ON_CPUDMA). But leaving this in there for legacy compatibility.
    #Should'nt affect performance significantly.

    dist_barrier := (self,o,i,is) >> Print(Blanks(i), "BLOCK_ON_CPUDMA; ALL_TO_ALL_BARRIER;\n"),

    dma_barrier := (self,o,i,is) >> Print(Blanks(i), "BLOCK_ON_CPUDMA;\n"),

    #NOTE: might be hacks. Need to possibly fix inside parent unparser's methods.
    call := (self, o, i, is) >> Print(Blanks(i), o.args[1].id, self.pinfix(Drop(o.args, 1), ", "), ";\n"),

    fcall := (self, o, i, is) >> Print(self(o.args[1],0,0), "(", self.infix(Drop(o.args, 1), ", "), ")"),

    #fcall_addr := (self, o, i, is) >> Print(self(o.args[1],0,0), "(", self.infix(Drop(o.args, 1), ", "), ")"),

   # ----------------------------------------------------------------------------------
   # Cell Multibuffering unparser
   # ----------------------------------------------------------------------------------
    multibuffer_loop := meth(self, o, i, is)
       local v, lo, hi, measSteadyState, swapx, swapy, swapxy, block_on_diag, swap_twiddles, n;

       n  := Length(o.range);
       v  := o.var;
       lo := o.range[1] + 1;
       hi := Last(o.range) - 1;
       measSteadyState := When(
            IsBound(self.opts.measSteadyState) and self.opts.measSteadyState = true,
            true,
            false);

       #swapx := Concat("SWAP(", o.x.id, ", ", o.x.id, "alt);\n");
       #swapy := Concat("SWAP(", o.y.id, ", ", o.y.id, "alt);\n");

       swapx  := Concat("SWAP(", o.x.id, ", XYalt);\n");
       swapy  := Concat("SWAP(", o.y.id, ", XYalt);\n");
       swapxy := Concat("SWAP(", o.y.id, ", ", o.x.id, ");\n");

       swap_twiddles := When(o.twiddles=[], "\n",
            Concat("SWAP(", o.bufs[1].id, ", ", o.bufs[2].id, ");\n")
       );

       block_on_diag := When(o.twiddles=[], "\n", "BLOCK_ON_DIAG;\n");

       Print(

       Blanks(i), When(measSteadyState, "/*", ""),
       Blanks(i), "\n{// ------------- Multibuffer header begin -----------\n",
       Blanks(i+is), "int ", v, " = ", lo-1, ";\n",
       # Begin skewiter:
       Blanks(i+is), v, "= (", v, "+(spuid/  ( SPUS>",n," ? (SPUS/",n,") : 1  )  ))%", n, ";\n",

       Blanks(i+is), "{\n",
       self(o.gathmem, i+is, is),
       When(o.twiddles=[], "", self(o.twiddles, i+is, is)),
       Blanks(i+is), "}\n",

       Blanks(i+is), "BLOCK_ON_MEMDMA;\n",
       Blanks(i+is), block_on_diag,
       Blanks(i+is), swapx,
       Blanks(i+is), swap_twiddles,

       Blanks(i+is), "{\n",
       # skewiter increment
       Blanks(i+is), v, "= (", v, "+1) % ", n, ";\n",
       #Blanks(i+is), v, "++;\n",
       self(o.gathmem, i+is, is),
       When(o.twiddles=[], "", self(o.twiddles, i+is, is)),
       # skewiter decrement
       Blanks(i+is), v, "= (", v, "+", n, "-1) % ", n, ";\n",
       #Blanks(i+is), v, "--;\n",
       Blanks(i+is), "}\n",

       Blanks(i+is), "{ // Loopbody begin\n",
       When(IsBound(self.opts.mbuf_nobody) and self.opts.mbuf_nobody = true, "", self(o.cmd,i+is,is)),
       Blanks(i+is), "} // Loopbody end\n",
       Blanks(i+is), "BLOCK_ON_MEMDMA;\n",
       Blanks(i+is), block_on_diag,
       Blanks(i), "}// ------------- Multibuffer header end -----------\n\n",
       Blanks(i), When(measSteadyState, "*/", ""),

       Blanks(i), "for(int ", v, " = ", lo, "; ", v, " <= ", hi, "; ", v, "++) {\n",
       # Begin skewiter:
       Blanks(i+is), v, "= (", v, "+(spuid/  ( SPUS>",n," ? (SPUS/",n,") : 1  )  ))%", n, ";\n",
       Blanks(i+is), swapy,
       Blanks(i+is), swapxy,
       # skewiter decrement
       Blanks(i+is), v, "= (", v, "+", n, "-1) % ", n, ";\n",
       #Blanks(i+is), v, "--;\n",
       self(o.scatmem, i+is, is),
       # skewiter increment
       Blanks(i+is), v, "= (", v, "+1) % ", n, ";\n",
       #Blanks(i+is), v, "++;\n",
       #Blanks(i+is), swapx,
       Blanks(i+is), swap_twiddles,
       # skewiter increment
       Blanks(i+is), v, "= (", v, "+1) % ", n, ";\n",
       #Blanks(i+is), v, "++;\n",
       self(o.gathmem, i+is, is),
       When(o.twiddles=[], "", self(o.twiddles, i+is, is)),
       # skewiter decrement
       Blanks(i+is), v, "= (", v, "+", n, "-1) % ", n, ";\n",
       #Blanks(i+is), v, "--;\n",
       Blanks(i+is), "{ // Loopbody begin\n",
       When(IsBound(self.opts.mbuf_nobody) and self.opts.mbuf_nobody = true, "", self(o.cmd,i+is,is)),
       Blanks(i+is), "} // Loopbody end\n",
       Blanks(i+is), "BLOCK_ON_MEMDMA;\n",
       Blanks(i+is), block_on_diag,
       # End skewiter:
       Blanks(i+is), v, "= (", v, "+", n, "-(spuid/  ( SPUS>",n," ? (SPUS/",n,") : 1  )  ))%", n, ";\n",
       Blanks(i), "}\n",

       Blanks(i), When(measSteadyState, "/*", ""),
       Blanks(i), "\n{// ------------- Multibuffer footer begin -----------\n",
       Blanks(i+is), "int ", v, "=", hi, "+1;\n",
       # Begin skewiter:
       Blanks(i+is), v, "= (", v, "+(spuid/  ( SPUS>",n," ? (SPUS/",n,") : 1  )  ))%", n, ";\n",
       Blanks(i+is), swapy,
       Blanks(i+is), swapxy,
       # skewiter decrement
       Blanks(i+is), v, "= (", v, "+", n, "-1) % ", n, ";\n",
       #Blanks(i+is), v, "--;\n",

       Blanks(i+is), "{\n",
       self(o.scatmem, i+is, is),
       Blanks(i+is), "}\n",

       # skewiter increment
       Blanks(i+is), v, "= (", v, "+1) % ", n, ";\n",
       #Blanks(i+is), v, "++;\n",
       #Blanks(i+is), swapx,
       Blanks(i+is), swap_twiddles,
       Blanks(i+is), "{ // Loopbody begin\n",
       When(IsBound(self.opts.mbuf_nobody) and self.opts.mbuf_nobody = true, "", self(o.cmd,i+is,is)),
       Blanks(i+is), "} // Loopbody end\n",
       Blanks(i+is), "BLOCK_ON_MEMDMA;\n",
       Blanks(i+is), swapy,
       #Blanks(i+is), v, "++;\n",

       Blanks(i+is), "{\n",
       self(o.scatmem, i+is, is),
       Blanks(i+is), "}\n",

       Blanks(i+is), "BLOCK_ON_MEMDMA;\n",
       Blanks(i), "}// ------------- Multibuffer footer end -----------\n\n",
       Blanks(i), When(measSteadyState, "*/", "")
       );

       #NOTE: Last wait can be avoided by DMA_putting Y directly (same for X?)

    end,

#F For debugging:
#F Multibuffer_loop that doesn't do multibuffering. Easier for humans to parse when looking at C code
    mem_loop := meth(self, o, i, is)
       local v, lo, hi, swapx, swapy, block_on_diag, swap_twiddles;

       swapx := Concat("SWAP(", o.x.id, ", ", o.x.id, "alt);\n");
       swapy := Concat("SWAP(", o.y.id, ", ", o.y.id, "alt);\n");
       swap_twiddles := When(o.twiddles=[], "\n",
            Concat("SWAP(", o.bufs[1].id, ", ", o.bufs[2].id, ");\n")
       );

       v  := o.var;
       lo := o.range[1];
       hi := Last(o.range);

       block_on_diag := When(o.twiddles=[], "\n", "BLOCK_ON_DIAG;\n");

       Print(

       Blanks(i), "for(int ", v, " = ", lo, "; ", v, " <= ", hi, "; ", v, "++) {\n",

       self(o.gathmem, i+is, is),
       When(o.twiddles=[], "", self(o.twiddles, i+is, is)),
       Blanks(i+is), "BLOCK_ON_MEMDMA;\n",
       Blanks(i+is), block_on_diag,
       Blanks(i+is), swapx,
       Blanks(i+is), swap_twiddles,

       Blanks(i+is), "{ // Loopbody begin\n",
       When(IsBound(self.opts.mbuf_nobody) and self.opts.mbuf_nobody = true, "", self(o.cmd,i+is,is)),
       Blanks(i+is), "} // Loopbody end\n",

       Blanks(i+is), swapy,
       self(o.scatmem, i+is, is),
       Blanks(i+is), "BLOCK_ON_MEMDMA;\n",

       Blanks(i), "}\n"

       );

    end,

    kern := (self, o, i, is) >> self(o.cmd, i, is)

));

Class(CellUnparser_parallel, CellUnparser);


#
#
#

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

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

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