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

#IsDigit := c -> c in "0123456789";
#var.prefix := meth( self )
#    local p;
#    p := FirstPosition(self.id,IsDigit);
#    p := SplitAt(self.id,When(p>0,p-1,0))[1];
#    Constraint(p <> "");
#    return p;
#end;

Class(VarGenBase, rec(
    __call__ := self >> WithBases(self, rec(counter:=rec(), pfx := "")),

    suffix := n -> StringInt(n),

    reset := meth(self) self.counter := rec(); end,

    nextu := (self, t) >> When(self.pfx="", self.next(t, "u"), self.next(t, "")),

    next := meth(self, t, pfx)
        local p;
	Constraint(IsString(pfx)); Constraint(IsType(t));
	pfx := Concat(self.pfx, pfx);
	if not IsBound(self.counter.(pfx)) then
            # first will be plain "pfx", then "pfx2"
	    p := self.var(t, pfx);
	    self.counter.(pfx) := 2;
	else
	    p := self.var(t, Concat(pfx,  self.suffix(self.counter.(pfx))));
	    self.counter.(pfx) := self.counter.(pfx) + 1;
	fi;
	return p;
    end,

    nextName := (self, pfx) >> self.next(TInt, pfx).id,

    var := (t, pfx) -> Error("must be implemented in subclasses"),

    # creates a cloned generator with aliased .counter, and an extra prefix
    # that will be added to all params created with .next()
    withPrefix := (self, pfx) >> When(
        pfx="i",
        Error("i prefix is RESERVED for loop variables!"),
        WithBases(self, rec(pfx := Concat(self.pfx, pfx))))
));

Class(VarGenNumeric, VarGenBase, rec(
    var := (t, name) -> var(name, t)
));

Class(VarGenSymbolic, VarGenBase, rec(
    var := (t, name) -> var(name, t),
    suffix := VarNameInt
));

Class(ParamGenNumeric, VarGenBase, rec(
    var := (t, name) -> param(t, name)
));

Class(ParamGenSymbolic, VarGenBase, rec(
    var := (t, name) -> param(t, name),
    suffix := VarNameInt
));

Class(NewInt,rec(
	__call__ := meth(self)
	    self.n := self.n + 1;
	    return self.n;
	end,

	n := 0,
));

Class(VarMapper, rec(
    __call__ := (self, vargen) >> WithBases(self, rec(
	    sn := NewInt(),
	    bindings := tab(),
	    vargen := vargen)),

    reset := meth(self)
	self.vargen.reset();
	self.bindings := tab();
	self.sn := NewInt();
    end,

    ignore := (self, var) >> false,

    alreadyMapped := (self,var) >> IsBound(var.sn) and var.sn = self.sn,

    map := meth(self, var)
        local newvar;
	if self.ignore(var) or (IsBound(var.NoReMap) and var.NoReMap) or self.alreadyMapped(var) then
	    return var;
	fi;
        if IsBound(self.bindings.(var.id)) then
	    return self.bindings.(var.id);
	else
	    newvar := self.vargen.next(var.t, var.id{[1]});
	    newvar.sn := self.sn;
	    self.bindings.(var.id):=newvar;
	    return newvar;
	fi;
    end
));

ProperName := function(o)
    o.properName := true;
    return o;
end;

_RemapVars := function(c, ignore_list, varGen )
    local vmapper;
    vmapper := CopyFields(VarMapper(varGen),
        rec(ignore := (self, var) >> IsBound(var.properName) and var.properName=true
                                     or var.id in ignore_list));
    return SubstTopDownRules(c, [
	    [var,v->vmapper.map(v)],
	    [decl,d->decl(List(d.vars,v->vmapper.map(v)),d.cmd)],
	    [data,d->data(vmapper.map(d.var),d.value,d.cmd)] ]);
end;

RemapVars := c -> _RemapVars(c, ["X", "Y"], VarGenNumeric);

RemapVarsIgnore := (c, ignore_list) -> _RemapVars(c, Concatenation(["X", "Y"], ignore_list));

RemapVarsSafe := function(c, ignore_list)
    local free, args, init;
    free := c.free();
    args := Set(ConcatList( Collect(c, @(1, [func])), x->x.params));
    init := Collect(c, @(1, [var,param], x->IsBound(x.value) or IsBound(x.init))); # .value and .init: this is a hack!
    return  _RemapVars(c, List(free::args, x->x.id) :: init :: ignore_list, ParamGenSymbolic());
end;

Class(RemoveUnusedVars,rec(__call__:=function(code)
    local usedvars,declvars,unusedvars;

    usedvars := Set(Flat([
		  	Collect(code,var)	# all used vars in operands of assign-cmds
		]));

    declvars := Set(Flat([
##  			 X, Y, # implicitly they are arguments of DFT kernel
			 List(Collect(code,decl),d->d.vars)
		]));

    unusedvars := declvars;
    SubtractSet(unusedvars,usedvars);

    # remove unused vars
	code := SubstTopDown(code,decl,d->decl(Difference(d.vars, unusedvars),d.cmd));

    return code;
end));

##  # Maybe, it would be usefull to add someting else to remove unused loops, data...
##  Class(RemoveUnused...,rec(__call__:=function(code)
##      local usedvars,declvars,unusedvars,v;
##      declvars := Set(Flat([
##  			 List(Collect(code,data),d->d.var),
##  			 List(Collect(code,loop),l->l.var)
##  		]));
##      unusedvars := declvars;
##      SubtractSet(unusedvars,usedvars);
##
##			# NOTE: prefixes for data and loop var-names?
##      for v in unusedvars do
##        if v.id[1] = 'D' then
##          code := SubstTopDown(code,data,d->Cond(d.var=v,d.cmd,d));
##        elif v.id[1] = 'i' then
##          code := SubstTopDown(code,loop,d->Cond(d.var=v,d.cmd,d));
##        else # decls
##          code := SubstTopDown(code,decl,d->decl(RemoveList(d.vars,v),d.cmd));
##        fi;
##      od;


#############################################################################
####
##
#A  anusp.gi                    ANUPQ package                  Eamonn O'Brien
#A                                                             Alice Niemeyer
##
#Y  Copyright 1993-2001,  Lehrstuhl D fuer Mathematik,  RWTH Aachen,  Germany
#Y  Copyright 1993-2001,  School of Mathematical Sciences, ANU,     Australia
##

#############################################################################
##
#F  ANUPQSPerror( <param> )  . . . . . . . . . . . . report illegal parameter
##
InstallGlobalFunction( ANUPQSPerror, function( param )
    Error(
    "Valid Options:\n",
    "    \"ClassBound\", <bound>\n",
    "    \"PcgsAutomorphisms\"\n",
    "    \"Exponent\", <exponent>\n",
    "    \"Metabelian\"\n",
    "    \"OutputLevel\", <level>\n",
    "    \"SetupFile\", <file>\n",
    "Illegal Parameter: \"", param, "\"" );
end );

#############################################################################
##
#F  ANUPQSPextractArgs( <args> )  . . . . . . . . . . . . parse argument list
##
InstallGlobalFunction( ANUPQSPextractArgs, function( args )
    local   CR,  i,  act,  G,  match;

    # allow to give only a prefix
    match := function( g, w )
    	return 1 < Length(g) and
            Length(g) <= Length(w) and
            w{[1..Length(g)]} = g;
    end;

    # extract arguments
    G  := args[2];
    CR := rec( group := G );
    i  := 3;
    while i <= Length(args)  do
        act := args[i];

        # "ClassBound", <class>
        if match( act, "ClassBound" )  then
            i := i + 1;
            CR.ClassBound := args[i];
            if CR.ClassBound <= PClassPGroup(G)  then
                Error( "\"ClassBound\" must be at least ", PClassPGroup(G)+1 );
            fi;

        # "PcgsAutomorphisms"
        elif match( act, "PcgsAutomorphisms" )  then
            CR.PcgsAutomorphisms := true;

        #this may be available later
        # "SpaceEfficient"
        #elif match( act, "SpaceEfficient" ) then
        #    CR.SpaceEfficient := true;

        # "Exponent", <exp>
        elif match( act, "Exponent" )  then
            i := i + 1;
            CR.Exponent := args[i];

        # "Metabelian"
        elif match( act, "Metabelian" ) then
            CR.Metabelian := true;

        # "Verbose"
        elif match( act, "Verbose" )  then
            CR.Verbose := true;

        # "SetupFile", <file>
        elif match( act, "SetupFile" )  then
            i := i + 1;
            CR.SetupFile := args[i];

    	# "TmpDir", <dir>
    	elif match( act, "TmpDir" )  then
    	    i := i + 1;
    	    CR.TmpDir := args[i];

        # "Output", <level>
        elif match( act, "OutputLevel" )  then
            i := i + 1;
            CR.OutputLevel := args[i];
            CR.Verbose     := true;

        # signal an error
        else
            ANUPQSPerror(act);
        fi;
        i := i + 1;
    od;
    return CR;

end );

#############################################################################
##
#F  PqFpGroupPcGroup( <G> ) . . . . . .  corresponding fp group of a pc group
##
InstallGlobalFunction( PqFpGroupPcGroup,
    G -> Image( IsomorphismFpGroup( G ) )
);

#############################################################################
##
#M  FpGroupPcGroup( <G> ) . . . . . . .  corresponding fp group of a pc group
##
InstallMethod( FpGroupPcGroup, "pc group", [IsPcGroup], 0, PqFpGroupPcGroup );

#############################################################################
##
#F  PQ_EPIMORPHISM_STANDARD_PRESENTATION( <args> ) . (epi. onto) SP for group
##
InstallGlobalFunction( PQ_EPIMORPHISM_STANDARD_PRESENTATION,
function( args )
    local   datarec, rank, Q, Qclass, automorphisms, generators, x,
            images, i, r, j, aut, result, desc, k;

    datarec := ANUPQ_ARG_CHK("StandardPresentation", args);

    if datarec.calltype = "interactive" and IsBound(datarec.SPepi) then
       # Note: the `pq' binary seg-faults if called twice to
       # calculate the standard presentation of a group
      return datarec.SPepi;
    fi;

    if VALUE_PQ_OPTION("pQuotient") = fail and
       VALUE_PQ_OPTION("Prime", datarec) <> fail then
       # Ensure a saved value of `Prime' has precedence
       # over a saved value of `pQuotient'.
        Unbind(datarec.pQuotient);
    fi;

    if VALUE_PQ_OPTION("pQuotient", datarec) <> fail then
        PQ_AUT_GROUP( datarec.pQuotient );
        datarec.Prime := PrimePGroup( datarec.pQuotient );
    elif VALUE_PQ_OPTION("Prime", datarec) <> fail then
        rank := Number( List( AbelianInvariants(datarec.group),
                              x -> Gcd(x, datarec.Prime) ),
                        y -> y = datarec.Prime );

        # construct free group with <rank> generators
        Q := FreeGroup( IsSyllableWordsFamily, rank, "q" );

        # construct power-relation
        Q := Q / List( GeneratorsOfGroup(Q), x -> x^datarec.Prime );

        # construct pc group
        Q := PcGroupFpGroup(Q);

        # construct automorphism
        automorphisms := [];
        generators := GeneratorsOfGroup(Q);
        for x in GeneratorsOfGroup( GL(rank, datarec.Prime) ) do
            images := [];
            for i  in [ 1 .. rank ]  do
                r := One(Q);
                for j  in [ 1 .. rank ]  do
                    r := r * generators[j]^Int(x[i][j]);
                od;
                images[i] := r;
            od;
            aut := GroupHomomorphismByImages( Q, Q, generators, images );
            SetIsBijective( aut, true );
            Add( automorphisms, aut );
        od;
        SetAutomorphismGroup( Q, GroupByGenerators( automorphisms ) );
        datarec.pQuotient := Q;
    fi;

    #PushOptions(rec(nonuser := true));
    Qclass := PClassPGroup( datarec.pQuotient );
    if VALUE_PQ_OPTION("ClassBound", 63) <= Qclass then
        Error( "option `ClassBound' must be greater than `pQuotient' class (",
               Qclass, ")\n" );
    fi;
    PQ_PC_PRESENTATION(datarec, "SP" : ClassBound := Qclass);

    PQ_SP_STANDARD_PRESENTATION(datarec);

    PQ_SP_ISOMORPHISM(datarec);

    if datarec.calltype = "non-interactive" then
        PQ_COMPLETE_NONINTERACTIVE_FUNC_CALL(datarec);
        if IsBound( datarec.setupfile ) then
            #PopOptions();
            return true;
        fi;
    fi;

    # try to read output
    result := ANUPQReadOutput( ANUPQData.SPimages );

    if not IsBound(result.ANUPQmagic)  then
        Error("something wrong with `pq' binary. Please check installation\n");
    fi;

    desc := rec();
    result.ANUPQgroups[Length(result.ANUPQgroups)](desc);
#    if result.ANUPQautos <> fail and
#       Length( result.ANUPQautos ) = Length( result.ANUPQgroups ) then
#    	result.ANUPQautos[ Length(result.ANUPQgroups) ]( desc.group );
#    fi;

    # revise images to correspond to images of user-supplied generators
    datarec.SP := desc.group;
    x  := Length( desc.map );
    k  := Length( GeneratorsOfGroup( datarec.group ) );
    # images of user supplied generators are last k entries in .pqImages

    datarec.SPepi := GroupHomomorphismByImagesNC(
                         datarec.group,
                         datarec.SP,
                         GeneratorsOfGroup(datarec.group),
                         desc.map{[x - k + 1..x]} );
    #PopOptions();
    return datarec.SPepi;
end );

#############################################################################
##
#F  EpimorphismPqStandardPresentation( <arg> ) . . . epi. onto SP for p-group
##
InstallGlobalFunction( EpimorphismPqStandardPresentation, function( arg )
    return PQ_EPIMORPHISM_STANDARD_PRESENTATION( arg );
end );

#############################################################################
##
#F  PqStandardPresentation( <arg> : <options> ) . . . . . . .  SP for p-group
##
InstallGlobalFunction( PqStandardPresentation, function( arg )
    local SPepi;

    SPepi := PQ_EPIMORPHISM_STANDARD_PRESENTATION( arg );
    if SPepi = true then
      return true; # the SetupFile case
    fi;
    return Range( SPepi );
end );

#############################################################################
##
#M  EpimorphismStandardPresentation( <F> ) . . . . . epi. onto SP for p-group
#M  EpimorphismStandardPresentation( [<i>] )
##
InstallMethod( EpimorphismStandardPresentation,
               "fp group", [IsFpGroup], 0,
               EpimorphismPqStandardPresentation );

InstallMethod( EpimorphismStandardPresentation,
               "pc group", [IsPcGroup], 0,
               EpimorphismPqStandardPresentation );

InstallMethod( EpimorphismStandardPresentation,
               "positive integer", [IsPosInt], 0,
               EpimorphismPqStandardPresentation );

InstallOtherMethod( EpimorphismStandardPresentation,
                    "", [], 0,
                    EpimorphismPqStandardPresentation );

#############################################################################
##
#M  StandardPresentation( <F> ) . . . . . . . . . . . . . . .  SP for p-group
#M  StandardPresentation( [<i>] )
##
InstallMethod( StandardPresentation,
               "fp group", [IsFpGroup], 0,
               PqStandardPresentation );

InstallMethod( StandardPresentation,
               "pc group", [IsPcGroup], 0,
               PqStandardPresentation );

InstallMethod( StandardPresentation,
               "positive integer", [IsPosInt], 0,
               PqStandardPresentation );

InstallOtherMethod( StandardPresentation,
                    "", [], 0,
                    PqStandardPresentation );

#############################################################################
##
#F  IsPqIsomorphicPGroup( <G>, <H> )  . . . . . . . . . . .  isomorphism test
##
InstallGlobalFunction( IsPqIsomorphicPGroup, function( G, H )
    local   p,  class,  SG,  SH,  Ggens,  Hgens;

    # <G> and <H> must both be pc groups and p-groups
    if not IsPcGroup(G)  then
        Error( "<G> must be a pc group" );
    fi;
    if not IsPcGroup(H)  then
        Error( "<H> must be a pc group" );
    fi;
    if Size(G) <> Size(H)  then
        return false;
    fi;
    p := SmallestRootInt(Size(G));
    if not IsPrimeInt(p)  then
        Error( "<G> must be a p-group" );
    fi;

    # check the Frattini factor
    if RankPGroup(G) <> RankPGroup(H)  then
        return false;
    fi;

    # check the exponent-p length and the sizes of the groups in the
    # p-central series of both groups
    if List(PCentralSeries(G,p), Size) <> List(PCentralSeries(H,p), Size) then
        return false;
    fi;

    # if the groups are elementary abelian they are isomorphic
    class := PClassPGroup(G);
    if class = 1  then
        return true;
    fi;

    # compute a standard presentation for both
    SG := PqStandardPresentation(PqFpGroupPcGroup(G)
                                 : Prime := p, ClassBound := class);
    SH := PqStandardPresentation(PqFpGroupPcGroup(H)
                                 : Prime := p, ClassBound := class);

    # the groups are equal if the presentation are equal
    Ggens := GeneratorsOfGroup( FreeGroupOfFpGroup( SG ) );
    Hgens := GeneratorsOfGroup( FreeGroupOfFpGroup( SH ) );
    return RelatorsOfFpGroup(SG)
           = List( RelatorsOfFpGroup(SH),
                   x -> MappedWord( x, Hgens, Ggens ) );

end );

#############################################################################
##
#M  IsIsomorphicPGroup( <F>, <G> )
##
InstallMethod( IsIsomorphicPGroup, "pc group, pc group",
               [IsPcGroup, IsPcGroup], 0,
               IsPqIsomorphicPGroup );

#E  anusp.gi  . . . . . . . . . . . . . . . . . . . . . . . . . . . ends here


# 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

Class(SKLR_Vx1i, SIMD_ISA, rec(

    includes     := () -> ["<stdlib.h>"] :: _MM_MALLOC(),
    active       := true,
    ctype        := "char",
    instr        := [],
    bits         := 1,
    isFloat      := false,
    isFixedPoint := false,
    splopts      := rec(),
    alignment    := 8,

    autolib := rec(
        includes := () -> ["<include/sp_bits.h>"],
        includesTimer := () -> [],
    ),

    _op_load_u  := abstract(),
    _op_bcast   := abstract(),
    _op_store_u := abstract(),

    dupload  := (self, y, x) >> Checked(ObjId(x)=nth, # NOTE: is there a better way?
	let(base := x.loc,
	    ofs  := x.idx,
	    v    := self.v,
	    xvec := Cond(IsUnalignedPtrT(base.t), self._op_load_u(base, idiv(ofs,v)*v, v),
		         vtref(self.t, base, idiv(ofs, v))),
	    assign(y, self._op_bcast(xvec, imod(ofs, v))))),

    svload := [ [ ], # load using subvecs of len 1
                [ ], # load using subvecs of len 2
                [ ], # load using subvecs of len 4
    ],

    svstore := [ [ ], # store using subvecs of len 1
                 [ ], # store using subvecs of len 2
                 [ ], # store using subvecs of len 4
    ],

    # keep the n lower scalars and zero the other ones
    mask_l := (self, c, n) >> Cond( n = self.v, c,
        bin_and(c, self.val(Replicate(n, 1) :: Replicate(self.v - n, 0)))),
    mask_h := (self, c, n) >> Cond( n = self.v, c,
        bin_and(c, self.val(Replicate(n, 0) :: Replicate(self.v - n, 1)))),

    loadCont := (self, n, y, yofs, x, xofs, xofs_align, opts) >> let(
	a  := _unwrap(xofs_align),
	nn := _unwrap(n),
	yy := vtref(self.t, y, yofs),
	m  := x -> self.mask_l(x, nn),
	Cond(a = 0 and not IsUnalignedPtrT(x.t),
	         assign(yy, m(vtref(self.t, x, xofs/self.v))),

	     # known alignment, sv is small, so that we only need 1 aligned load + 1 shift + mask
	     ((IsInt(a) and (nn <= self.v - a)) or nn=1) and not IsUnalignedPtrT(x.t),
		 let(v1 := vtref(self.t, x, idiv(xofs, self.v)),
		     assign(yy, m(bin_shr(v1, a)))),

	     # known alignment, sv covers 2 vectors, 2 aligned loads + 2 shifts + mask
	     # NB: no masking is needed because shifts will do the job
	     IsInt(a) and not IsUnalignedPtrT(x.t),
		 let(v1 := vtref(self.t, x, (xofs - a)/self.v),
		     v2 := vtref(self.t, x, (xofs - a)/self.v + 1),
		     assign(yy, m(bin_or(bin_shr(v1, a), bin_shl(v2, self.v - a))))),
             # else, unknown alignment, use unaligned load
             assign(yy, self._op_load_u(x, xofs, nn))
        )
    ),

    storeCont := (self, n, y, yofs, yofs_align, x, xofs, opts) >> let(
	a  := _unwrap(yofs_align),
	nn := _unwrap(n),
	xx := vtref(self.t, x, xofs),
	yy := vtref(self.t, y, yofs/self.v),
	Cond(nn = self.v and a = 0 and not IsUnalignedPtrT(y.t),
	        assign(yy, xx),
	     #else
	        self._op_store_u(y, yofs, xx, nn))),

    rotate_left := (self, shift) >> ((y, x) -> assign(vtref(self.t, y, 0), rCyclicShift(vtref(self.t, x, 0), shift, self.v))),

    kswap := (self, y, x, k, mask) >> let( u := var.fresh_t("U", self.t),
                                         chain(assign( u, bin_and(bin_xor(x, bin_shr(x, 2^(k-1))), self.t.value(mask))),
                                               assign( y, bin_xor(bin_xor(x, u), bin_shl(u, 2^(k-1)))))),

    kexch := (self, y1, y2, x1, x2, mask) >> let( u := var.fresh_t("U", self.t),
                                         chain(assign(  u, bin_and(bin_xor(x1, x2), self.t.value(mask))),
                                               assign( y1, bin_xor(x1, u)),
                                               assign( y2, bin_xor(x2, u)))),
));

Class(SKLR_16x1i, SKLR_Vx1i, rec(
    # countrec below is invalid
    countrec := rec(
        ops := [
            [add, sub],
	    [mul],
            [sklr_bcast_16x1i], # shuffles
            [sklr_loadu_16x1i, sklr_storeu_16x1i],
            [deref],
            Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]", "[mults]", "[vperms]", "[svldst]", "[vldst]", "[vval]"],
        type := "TVect",
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

    info     := "Scalar 16 x 1-bit",
    v        := 16,
    t        := BitVector(16),

    v_ones   := BitVector(16).one(),
    v_zeros  := BitVector(16).zero(),
    val      := bits -> BitVector(16).value(bits),

    _op_load_u  := (self, ptr, offs, elts)      >> sklr_loadu_16x1i(ptr, offs, elts),
    _op_bcast   := (self, loc, elt_num)         >> sklr_bcast_16x1i(loc, elt_num),
    _op_store_u := (self, ptr, offs, src, elts) >> sklr_storeu_16x1i(ptr, offs, src, elts),
));

Class(SKLR_32x1i, SKLR_Vx1i, rec(
    # countrec below is invalid
    countrec := rec(
        ops := [
            [add, sub],
	    [mul],
            [sklr_bcast_32x1i], # shuffles
            [sklr_loadu_32x1i, sklr_storeu_32x1i],
            [deref],
            Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]", "[mults]", "[vperms]", "[svldst]", "[vldst]", "[vval]"],
        type := "TVect",
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

    info     := "Scalar 32 x 1-bit",
    v        := 32,
    t        := BitVector(32),

    v_ones   := BitVector(32).one(),
    v_zeros  := BitVector(32).zero(),
    val      := bits -> BitVector(32).value(bits),

    _op_load_u  := (self, ptr, offs, elts)      >> sklr_loadu_32x1i(ptr, offs, elts),
    _op_bcast   := (self, loc, elt_num)         >> sklr_bcast_32x1i(loc, elt_num),
    _op_store_u := (self, ptr, offs, src, elts) >> sklr_storeu_32x1i(ptr, offs, src, elts),

));

Class(SKLR_64x1i, SKLR_Vx1i, rec(
    # countrec below is invalid
    countrec := rec(
        ops := [
            [add, sub],
	    [mul],
            [sklr_bcast_64x1i], # shuffles
            [sklr_loadu_64x1i, sklr_storeu_64x1i],
            [deref],
            Value      # Value without [] is a keyword in countOps !!
        ],
        printstrings := ["[adds]", "[mults]", "[vperms]", "[svldst]", "[vldst]", "[vval]"],
        type := "TVect",
        arithcost := (self, opcount) >> opcount[1]+opcount[2]
    ),

    info     := "Scalar 64 x 1-bit",
    v        := 64,
    t        := BitVector(64),

    v_ones   := BitVector(64).one(),
    v_zeros  := BitVector(64).zero(),
    val      := bits -> BitVector(64).value(bits),

    _op_load_u  := (self, ptr, offs, elts)      >> sklr_loadu_64x1i(ptr, offs, elts),
    _op_bcast   := (self, loc, elt_num)         >> sklr_bcast_64x1i(loc, elt_num),
    _op_store_u := (self, ptr, offs, src, elts) >> sklr_storeu_64x1i(ptr, offs, src, elts),
));

RewriteRules(RulesStrengthReduce, rec(
    aligned_loadu_32x1 := Rule( @(1, sklr_loadu_32x1i, x -> IsInt(_unwrap(x.args[2] mod 32)) and not IsUnalignedPtrT(x.args[1])),
                             e -> let(
                                     offs := imod(e.args[2], 32),
                                     xx0  := vtref(e.t, e.args[1], idiv(e.args[2], 32)),
                                     xx1  := vtref(e.t, e.args[1], idiv(e.args[2], 32)+1),
                                     nn   := _unwrap(e.args[3]),
                                     bin_and( When(offs + nn <= 32,
                                             bin_shr(xx0, offs),
                                             bin_or(bin_shr(xx0, offs), bin_shl(xx1, 32 - offs))),
                                         e.t.value(Replicate(nn, 1) :: Replicate(32 - nn, 0)))
                                     )),
    aligned_loadu_64x1 := Rule( @(1, sklr_loadu_64x1i, x -> IsInt(_unwrap(x.args[2] mod 64)) and not IsUnalignedPtrT(x.args[1])),
                             e -> let(
                                     offs := imod(e.args[2], 64),
                                     xx0  := vtref(e.t, e.args[1], idiv(e.args[2], 64)),
                                     xx1  := vtref(e.t, e.args[1], idiv(e.args[2], 64)+1),
                                     nn   := _unwrap(e.args[3]),
                                     bin_and( When(offs + nn <= 64,
                                             bin_shr(xx0, offs),
                                             bin_or(bin_shr(xx0, offs), bin_shl(xx1, 64 - offs))),
                                         e.t.value(Replicate(nn, 1) :: Replicate(64 - nn, 0)))
                                     )),

));

Class(SKLR_32x1i_to_SSE_16x8i, ISA_Bridge, rec(
    isa_from    := SKLR_32x1i,
    isa_to      := SSE_16x8i(T_Int(8)),

    code        := (self, y, x, opts) >> let(
                    xx := (offs) -> vtref(self.isa_from.t, x, offs),
                    yy := (offs) -> vtref(self.isa_to.t,   y, offs),
                    a  := var.fresh_t("U", T_UInt(32)),
                    b0 := var.fresh_t("U", T_UInt(32)),
                    b1 := var.fresh_t("U", T_UInt(32)),
                    b2 := var.fresh_t("U", T_UInt(32)),
                    b3 := var.fresh_t("U", T_UInt(32)),
                    mask := T_UInt(32).value(1 + 256 + 65536 + 16777216),
                    decl([a,b0,b1,b2,b3], chain(
                        assign( a, tcast(a.t, xx(0)) ),
                        assign( b0, bin_and(            a, mask)),
                        assign( b1, bin_and(bin_shr(a, 1), mask)),
                        assign( b2, bin_and(bin_shr(a, 2), mask)),
                        assign( b3, bin_and(bin_shr(a, 3), mask)),
                        assign( yy(0), tcast(self.isa_to.t, vpack(b0, b1, b2, b3))),
                        assign( b0, bin_and(bin_shr(a, 4), mask)),
                        assign( b1, bin_and(bin_shr(a, 5), mask)),
                        assign( b2, bin_and(bin_shr(a, 6), mask)),
                        assign( b3, bin_and(bin_shr(a, 7), mask)),
                        assign( yy(1), tcast(self.isa_to.t, vpack(b0, b1, b2, b3)))
                    ))),

    toAMat := self >> L(self.isa_from.v, 8).toAMat(),
    toSpl  := self >> Cvt(self)*TL(self.isa_from.v, div(self.isa_from.v, 8)).withTags([AVecReg(self.isa_from)])
));

Class(SKLR_64x1i_to_SSE_16x8i, SKLR_32x1i_to_SSE_16x8i, rec(
    isa_from := SKLR_64x1i,
    isa_to   := SSE_16x8i(T_Int(8)),

    code     := (self, y, x, opts) >> let(
                    xx := (offs) -> vtref(self.isa_from.t, x, offs),
                    yy := (offs) -> vtref(self.isa_to.t,   y, offs),
                    a  := var.fresh_t("U", T_UInt(64)),
                    b0 := var.fresh_t("U", T_UInt(64)),
                    b1 := var.fresh_t("U", T_UInt(64)),
                    mask := T_UInt(64).value(1 + 2^8 + 2^16 + 2^24 + 2^32 + 2^40 + 2^48 + 2^56),
                    decl([a,b0,b1], chain(
                        assign( a, tcast(a.t, xx(0)) ),
                        assign( b0, bin_and(            a, mask)),
                        assign( b1, bin_and(bin_shr(a, 1), mask)),
                        assign( yy(0), tcast(self.isa_to.t, vpack(b0, b1))),
                        assign( b0, bin_and(bin_shr(a, 2), mask)),
                        assign( b1, bin_and(bin_shr(a, 3), mask)),
                        assign( yy(1), tcast(self.isa_to.t, vpack(b0, b1))),
                        assign( b0, bin_and(bin_shr(a, 4), mask)),
                        assign( b1, bin_and(bin_shr(a, 5), mask)),
                        assign( yy(2), tcast(self.isa_to.t, vpack(b0, b1))),
                        assign( b0, bin_and(bin_shr(a, 6), mask)),
                        assign( b1, bin_and(bin_shr(a, 7), mask)),
                        assign( yy(3), tcast(self.isa_to.t, vpack(b0, b1)))
                    )))
));

Class(SKLR_32x1i_to_SSE_4x32f_f32, SKLR_32x1i_to_SSE_16x8i, rec(
    isa_from := SKLR_32x1i,
    isa_to   := SSE_4x32f(T_Real(32)),

    code     := (self, y, x, opts) >> let(
                    xx := (offs) -> vtref(self.isa_from.t, x, offs),
                    yy := (offs) -> vtref(self.isa_to.t,   y, offs),
                    ti := TVect(T_Int(32), 4),
                    tf := TVect(T_Real(32), 4),
                    a  := var.fresh_t("U", T_UInt(32)),
                    b  := var.fresh_t("U", ti),
                    decl( [a, b], chain(
                        assign( a, tcast(a.t, xx(0)) ),
                        assign( b, vpack(a, bin_shr(a, 8), bin_shr(a, 16), bin_shr(a, 24)) ),
                        assign(yy(0), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b, 31), 31) ))),
                        assign(yy(1), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b, 30), 31) ))),
                        assign(yy(2), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b, 29), 31) ))),
                        assign(yy(3), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b, 28), 31) ))),
                        assign(yy(4), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b, 27), 31) ))),
                        assign(yy(5), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b, 26), 31) ))),
                        assign(yy(6), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b, 25), 31) ))),
                        assign(yy(7), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b, 24), 31) )))
                    ))
                )
));

Class(SKLR_64x1i_to_SSE_4x32f_f32, SKLR_32x1i_to_SSE_16x8i, rec(
    isa_from := SKLR_64x1i,
    isa_to   := SSE_4x32f(T_Real(32)),

    code     := (self, y, x, opts) >> let(
                    xx := (offs) -> vtref(self.isa_from.t, x, offs),
                    yy := (offs) -> vtref(self.isa_to.t,   y, offs),
                    ti := TVect(T_Int(32), 4),
                    tf := TVect(T_Real(32), 4),
                    a  := var.fresh_t("U", T_UInt(64)),
                    b0 := var.fresh_t("U", ti),
                    b1 := var.fresh_t("U", ti),
                    shift := (t, n) -> tcast(T_Int(32), bin_shr(t, n)),
                    decl( [a, b0, b1], chain(
                        assign( a, tcast(a.t, xx(0)) ),
                        assign( b0, vpack(shift(a,  0), shift(a,  8), shift(a, 16), shift(a, 24)) ),
                        assign( b1, vpack(shift(a, 32), shift(a, 40), shift(a, 48), shift(a, 56)) ),
                        assign(yy( 0), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b0, 31), 31) ))),
                        assign(yy( 1), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b1, 31), 31) ))),
                        assign(yy( 2), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b0, 30), 31) ))),
                        assign(yy( 3), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b1, 30), 31) ))),
                        assign(yy( 4), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b0, 29), 31) ))),
                        assign(yy( 5), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b1, 29), 31) ))),
                        assign(yy( 6), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b0, 28), 31) ))),
                        assign(yy( 7), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b1, 28), 31) ))),
                        assign(yy( 8), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b0, 27), 31) ))),
                        assign(yy( 9), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b1, 27), 31) ))),
                        assign(yy(10), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b0, 26), 31) ))),
                        assign(yy(11), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b1, 26), 31) ))),
                        assign(yy(12), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b0, 25), 31) ))),
                        assign(yy(13), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b1, 25), 31) ))),
                        assign(yy(14), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b0, 24), 31) ))),
                        assign(yy(15), tcast(tf, bin_and( tf.one(), arith_shr(bin_shl(b1, 24), 31) )))
                    ))
                )
));

# NOTE: assumption that most significant bit is set for non zero numbers:
#
# SSE_16x8i_i8_to_SKLR_32x1i can be implemented as
#    neg(vmovemask_16x8i(eq(self.isa_from.t.zero(), xx(2*i))))
# and later simplified if xx(2*i) comes from comparision, yet we cannot match this situation
# because it's unlikely that we will have comparision propagated into this expression.
# Another way is to make T_Bool and simplify expression above by looking at data type.

ISA_Bridge.add(Class(CVT_SKLR_16x1i_SSE_16x8i, ISA_Bridge_I, rec(
    isa_from    := SSE_16x8i(T_Int(8)),
    isa_to      := SKLR_16x1i,
    props       := ["saturation"],
    code := (self, y, x, opts) >> assign(self._y(y,0), vmovemask_16x8i(self._x(x,0))),
)));

ISA_Bridge.add(Class(CVT_SKLR_16x1i_SSE_16x8ui, CVT_SKLR_16x1i_SSE_16x8i, rec(
    isa_from := SSE_16x8i(T_UInt(8)),
)));

Class(SKLR_32f_to_SKLR_32x1i, ISA_Bridge_I, rec(
    isa_from    := SKLR(T_Real(32)),
    isa_to      := SKLR_32x1i,
    granularity := self >> self.isa_to.v,

    code := (self, y, x, opts) >> let(
                j  := Ind(self.isa_to.v),
                xt := self.isa_from.t,
                yt := T_UInt(self.isa_to.v),
                yy := (offs) -> vtref(self.isa_to.t, y, offs),
                a  := var.fresh_t("U", yt),
                decl([a], chain(
                    assign(a, a.t.zero()),
                    loop(j, j.range,
                        assign(a, bin_or(a, cond(eq(nth(x, j), xt.zero()), yt.zero(), bin_shl(yt.one(), j))))),
                    assign(yy(0), a)
                ))
            ),
));

Class(SKLR_32f_to_SKLR_64x1i, SKLR_32f_to_SKLR_32x1i, rec(
    isa_from := SKLR(T_Real(32)),
    isa_to   := SKLR_64x1i
));

Class(SKLR_64x1i_to_SKLR_32f, ISA_Bridge_I, rec(
    isa_from    := SKLR_64x1i,
    isa_to      := SKLR(T_Real(32)),
    granularity := self >> self.isa_from.v,

    code := (self, y, x, opts) >> let(
                j  := Ind(self.isa_from.v),
                xx := (offs) -> vtref(self.isa_from.t, x, offs),
                ti := T_UInt(self.isa_from.v),
                tf := self.isa_to.t,
                a  := var.fresh_t("U", ti),
                decl( [a], chain(
                    assign( a, tcast(a.t, xx(0)) ),
                    loop(j, j.range,
                        assign( nth(y, j), tcvt( tf, bin_and(bin_shr(a, j), a.t.one())))
                    ).unroll()
                ))
            ),
));

Class(SKLR_32x1i_to_SKLR_32f, SKLR_64x1i_to_SKLR_32f, rec(
    isa_from := SKLR_32x1i,
    isa_to   := SKLR(T_Real(32))
));

Class(SKLR_64x1i_to_SKLR_8i, SKLR_64x1i_to_SKLR_32f, rec(
    isa_from := SKLR_64x1i,
    isa_to   := SKLR(T_Int(8))
));

Class(SKLR_32x1i_to_SKLR_8i, SKLR_64x1i_to_SKLR_32f, rec(
    isa_from := SKLR_32x1i,
    isa_to   := SKLR(T_Int(8))
));

ISA_Bridge.add(Class(CVT_SKLR_32x1i_SKLR_16x1i, ISA_Bridge_I, rec(
    isa_from    := SKLR_16x1i,
    isa_to      := SKLR_32x1i,
    code := (self, y, x, opts) >>
        assign(self._y(y,0), bin_or(tcvt(T_UInt(32), self._x(x,0)), bin_shl(tcvt(T_UInt(32), self._x(x,1)), 16)) ),
)));

ISA_Bridge.add(Class(CVT_SKLR_64x1i_SKLR_16x1i, ISA_Bridge_I, rec(
    isa_from    := SKLR_16x1i,
    isa_to      := SKLR_64x1i,
    code := (self, y, x, opts) >>
        assign(self._y(y,0), bin_or(
                     tcvt(T_UInt(64), self._x(x,0)),
             bin_shl(tcvt(T_UInt(64), self._x(x,1)), 16),
             bin_shl(tcvt(T_UInt(64), self._x(x,2)), 32),
             bin_shl(tcvt(T_UInt(64), self._x(x,3)), 48)
        )),
)));


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

Class(ScratchpadGlobals, rec(
    getOpts := meth(arg)
        local lssize, opts, swp, brules, nrules, br, nrsgmts, vlen, globalUnrolling,size, ttype;

	lssize := When (Length(arg) >= 2, arg[2], 2);
	nrsgmts := When (Length(arg) >= 3, arg[3], 1);
	vlen := When (Length(arg) >= 4, arg[4], 1);
	size := When (Length(arg) >= 5, arg[5], 2);
    ttype := When (Length(arg) >= 6, arg[6], 'R');
	swp := When (Length(arg) >= 7, arg[7], false);
	globalUnrolling := When(Length(arg) >=8, arg[8], 1);

    brules := When(IsRec(SpiralDefaults.breakdownRules),
        UserRecFields(SpiralDefaults.breakdownRules),
        Filtered(Dir(SpiralDefaults.breakdownRules), i->not i in SystemRecFields));
    nrules := rec();
    for br in brules do
        nrules.(br) := List(SpiralDefaults.breakdownRules.(br), i->CopyFields(i));
    od;
    opts := CopyFields(SpiralDefaults);
    opts.breakdownRules := nrules;

    opts.breakdownRules.TCompose := [ TCompose_tag];
    opts.breakdownRules.DFT := [DFT_Base, DFT_CT, DFT_tSPL_CT, DFT_PD, DFT_Rader];
    opts.breakdownRules.TTwiddle := [ TTwiddle_Tw1];

	opts.breakdownRules.WHT := [WHT_tSPL_BinSplit, WHT_Base, WHT_BinSplit];

	opts.breakdownRules.TTensor := [AxI_IxB,IxB_AxI];
	opts.breakdownRules.TTensorI := Concat([IxA_scratch_push, IxA_base, AxI_base, IxA_L_base, L_IxA_base], [IxA_scratch, AxI_scratch, IxAL_scratch]);

	opts.tags := [Cond( ttype = 'R', ALStore(lssize,nrsgmts,vlen), ALStoreCx(lssize,nrsgmts,vlen)) ];

	opts.formulaStrategies.sigmaSpl := [ MergedRuleSet(RulesSumsScratch, RulesFuncSimpScratch, RulesDiag, RulesDiagStandalone, RulesStrengthReduce, RulesRCScratch,RulesII,OLRules) ];
    opts.formulaStrategies.preRC := [ MergedRuleSet(RulesSumsScratch, RulesFuncSimpScratch, RulesDiag, RulesDiagStandalone, RulesStrengthReduce, RulesRCScratch, RulesII, OLRules), (s,o) -> ScratchModel.updateInfo(s) ];
	opts.formulaStrategies.rc := [ MergedRuleSet(RulesSums, RulesFuncSimp, RulesDiag, RulesDiagStandalone, RulesStrengthReduce, RulesRCScratch, RulesII, OLRules) ];
    #opts.formulaStrategies.postProcess := [(s, opts) -> compiler.BlockSums(opts.globalUnrolling, s)];
    opts.size := size;
	opts.swp := swp;
    opts.globalUnrolling := globalUnrolling;

    opts.sumsgen := ScratchSumsGen;
	opts.codegen := ScratchCodegen;
    opts.unparser := CScratchUnparserProg;

    opts.memModifier := "__memory";
    opts.scratchModifier := "__scratch";
    opts.arrayDataModifier := "__rom";
    opts.romModifier := "__rom";

	opts.includes := [];
    Add(opts.includes, "\"scratch.h\"");

    opts.dmaSignal := (self, opts) >> "DMA_signal";
    opts.dmaWait := (self, opts) >> "DMA_wait";
    opts.cpuSignal := (self, opts) >> "CPU_signal";
    opts.cpuWait := (self, opts) >> "CPU_wait";
    opts.dmaFence := (self, opts) >> "DMA_fence";
    opts.dmaLoad := (self, opts) >> "DMA_load";
    opts.dmaStore := (self, opts) >> "DMA_store";
    opts.model := ScratchModel;

    return opts;
    end
));