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

UnifyPair := (aa,bb) -> let(
    # NOTE: this converts 2->TInt, this is a hack
    a := Cond(IsInt(aa) or IsSymbolic(aa), TInt, ObjId(aa)=TFunc, _convType(aa), aa),
    b := Cond(IsInt(bb) or IsSymbolic(bb), TInt, ObjId(bb)=TFunc, _convType(bb), bb),
    aid := ObjId(a),    bid := ObjId(b),  avec := IsVecT(aa), bvec := IsVecT(bb),
    Cond(

     # YSV: this handles subtraction of pointers (=integer index)
     #      addition of pointers is not valid
#     aid=TPtr and bid=TPtr, TInt,

     a=b, a,
     a = TUnknown and b <> TUnknown, b,
     b = TUnknown and a <> TUnknown, a,

#     aid in [TPtr, TArray] and (b=TInt or bid in [T_Int, T_UInt]), TPtr(a.t, When(aid=TArray, [], a.qualifiers), When(aid=TArray, ptrAligned, a.alignment)),
#     bid in [TPtr, TArray] and (a=TInt or aid in [T_Int, T_UInt]), TPtr(b.t, When(bid=TArray, [], b.qualifiers), When(bid=TArray, ptrAligned, b.alignment)),
     #NOTE: this looks like an horrible hack but how else can it be done?
     #isn't this a fundamental weakness of C/C++ ?

#     aid=TPtr and bid=TArray, a,
#     aid=TArray and bid=TPtr, b,

     avec and bvec and a.size<>b.size,     TVect(UnifyPair(a.t, b.t), Maximum(a.size, b.size)),
     avec and bvec, Checked(a.size=b.size, TVect(UnifyPair(a.t, b.t), a.size)),
     avec, TVect(UnifyPair(a.t, b), a.size),
     bvec, TVect(UnifyPair(a, b.t), b.size),

     aid=TFixedPt and bid=TFixedPt,
        When(a = b, a, Error("Can't unify fixed point types of different bit width")),

     a=TComplex and b in [TReal, TComplex, TInt, TUInt], TComplex,
     b=TComplex and a in [TReal, TComplex, TInt, TUInt], TComplex,

     (aid=T_Real or a=TReal) and (bid in [T_UInt, T_Int] or b in [TReal, TInt, TUInt]), a,
     (bid=T_Real or b=TReal) and (aid in [T_UInt, T_Int] or a in [TReal, TInt, TUInt]), b,

     a=TInt and b in  [TInt,TBool,TUInt], TInt,
     a in [TInt,TUInt,TBool] and b=TInt,  TInt,

     aid=T_Complex and b in [TComplex, TReal, TInt], a,
     bid=T_Complex and a in [TComplex, TReal, TInt], b,

     aid in [T_Real, T_Int] and b=TComplex, T_Complex(a),
     bid in [T_Real, T_Int] and a=TComplex, T_Complex(b),

     aid=T_Complex and bid=T_Complex, T_Complex(UnifyPair(a.params[1], b.params[1])),
     aid=T_Complex and bid in [TFixedPt, T_Real, T_UInt, T_Int],
         T_Complex(UnifyPair(a.params[1], b)),
     bid=T_Complex and aid in [TFixedPt, T_Real, T_UInt, T_Int],
         T_Complex(UnifyPair(a, b.params[1])),

     a=TComplex and bid = TFixedPt, T_Complex(b),
     b=TComplex and aid = TFixedPt, T_Complex(a),

     aid=T_Real and bid=T_Real, T_Real(Maximum(a.params[1], b.params[1])),

     aid in [T_Int, T_UInt] and b in [TInt, TUInt], a,
     a in [TInt, TUInt] and bid in [T_Int, T_UInt], b,

     aid=T_UInt and bid=T_UInt, T_UInt(Maximum(a.params[1], b.params[1])),
     aid in [T_Int, T_UInt] and bid in [T_Int, T_UInt], T_Int(Maximum(a.params[1], b.params[1])),

     aid=TFixedPt, a,
     bid=TFixedPt, b,

     a=TDummy or b=TDummy, TDummy,

     IsArrayT(a) and IsArrayT(b), Checked(a.size=b.size, TArray(UnifyPair(a.t, b.t), a.size)),
     IsArrayT(a), TArray(UnifyPair(a.t, b), a.size),
     IsArrayT(b), TArray(UnifyPair(a, b.t), b.size),

     IsBound(a.unifyWith), a.unifyWith(b),
     IsBound(b.unifyWith), b.unifyWith(a),

     #MRT handles addition/subtraction of pointers and ints.
     aid = TPtr and b = TInt, b,
     a = TInt and bid = TPtr, a,
     #MRT END

     Error("Can't unify ", a, " and ", b)));

#F UnifyTypes(<types>) - given a list of types returns a
#F   most general type
#F   NOTE: complete this.
#F
UnifyTypes := function(types)
    local t, l, i, a, b;
    l := Length(types);

    if   l=0 then return TUnknown;
    elif l=1 then return types[1];
    fi;

    [i, t] := [2, types[1]];
    while i <= l do
        t := UnifyPair(t, types[i]);
	i := i+1;
    od;
    return t;
end;

UnifyTypesL := function(args)
    local t, l, i, a, b;
    l := Length(args);

    if   l=0 then return TUnknown;
    elif l=1 then return args[1].t;
    fi;

    [i, t] := [2, args[1].t];
    while i <= l do
        t := UnifyPair(t, args[i].t);
	i := i+1;
    od;
    return t;
end;

Declare(InferType);

UnifyTypesV := values -> UnifyTypes(List(values, InferType));

InferType := v -> let(gt := BagType(v),
   Cond(
       IsInt(v),
           TInt,
       gt = T_RAT or gt = T_DOUBLE,
           TReal,
       gt = T_CPLX,
           TComplex,
       gt = T_CYC,
           When(Im(v)=0, TReal, TComplex),
       gt = T_CHAR,
           TChar,
       gt = T_BOOL,
           TBool,
       IsString(v),
           TString,
       IsExp(v) or IsValue(v),
           When(IsBound(v.t), v.t, TUnknown),

       gt = T_RANGE, Checked(Length(v) >= 0,
       TArray(TInt, Length(v))),

       IsList(v), Checked(Length(v) >= 0,
       TArray(UnifyTypes(List(v, InferType)), Length(v))),

       Error("Can't infer the type of ", v)));

#NOTE: when 'v' is a list with expressions inside it will create TArray value which is wrong
V := v -> Cond(IsValue(v), v,
               IsExp(v), v.eval(),
               InferType(v).value(v));


# Copyright 2018-2019, Carnegie Mellon University
# See LICENSE for details

Class(cos, AutoFoldExp, rec(
  computeType := (self) >> TReal,

));

Class(sin, AutoFoldExp, rec(
  computeType := (self) >> TReal,
));

Class(tan, AutoFoldExp, rec(
  computeType := (self) >> TReal,
));

Class(TIVReal, AtomicTyp, rec(
    hash := (_val, size) -> let(val := When(IsList(_val), _val, [_val, _val]),
        h := DoubleRep64(val[1]) + DoubleRep64(val[2]),
        1 + (h mod size)),
    check := v -> Cond(IsDouble(v) or IsList(v) and Length(v) = 2 and v[1] <= v[2], v, Error("<v> must be a Double or an ordered list of length 2")),
    realType    := self >> self,
    print := self >> Print(self.__name__, "(", self.t, ")"),
    base_t := self >> self.t,
    zero := self >> self.value(0.0),
    one := self >> self.value(1.0),
    __call__ := (self, t) >>
        WithBases(self, rec(
        t    := Checked(IsType(t), t),
        operations := TypOps))
));

Class(TIVBool, AtomicTyp, rec(
    hash := (val, size) -> 1 + (InternalHash(val) mod size),
    check := v -> Cond(IsInt(v) and v in [0, 1, -1], v, Error("<v> must be in [-1, 0, 1]")),
    zero := self >> self.value(0),
    one := self >> self.value(1)
));

Class(ivenv, chain);

Class(RulesHCOLIVArithType, RuleSet, rec(inType := "iCode", outType := "iCode"));
RewriteRules(RulesHCOLIVArithType, rec(
	ivenv_prop_types_bools := ARule(ivenv, [[@(1, assign), @(2,var).cond(e->e.t<>TInt),
		@(3).cond(e-> e.t<>@(2).val.t and e.t=TBool)], @(4)],
        e->let(
		nvar := var.fresh_t("n", TInt),
		#Print("New Type 2: ", @(1).val,"(",@(2).val, ",",@(3).val,")\n", @(2).val.t, " <> ", @(3).val.t, "\n New Var Type: ", nvar, ",",nvar.t," \n"),
	  [decl([nvar], SubstVars(chain(assign(nvar, @(3).val), @(4).val), rec((@(2).val.id) := nvar )))] ) ),

    ivenv_prop_types := ARule(ivenv, [[@(1, assign), @(2,var),
		@(3).cond(e-> e.t<>@(2).val.t and not (e.t in [TVect(T_Real(64), 2), TIVReal, TBool]))], @(4,...)],
        e->let(
		nvar := var.fresh_t("n", @(3).val.t),
		#Print("New Type: ", @(1).val,"(",@(2).val, ",",@(3).val,")", @(2).val.t, " <> ", @(3).val.t, " ", nvar, "\n", @(4).val, "\n"),
	  [decl([nvar], SubstVars(chain(assign(nvar, @(3).val), @(4).val), rec((@(2).val.id) := nvar )))] ) ),

));

Class(RulesHCOLIVArith, RuleSet, rec(inType := "iCode", outType := "iCode"));
RewriteRules(RulesHCOLIVArith, rec(
    ivenv_chain := Rule(@(1, ivenv, e->ForAny(e.cmds, f->ObjId(f) = chain)),
        e->ApplyFunc(ivenv, Flat(List(@(1).val.cmds, j->When(ObjId(j)=chain, j.cmds, j))))),
    ivenv_xyz_decl := ARule(ivenv, [@(1), @(2, decl)],
        e->[decl(@(2).val.vars, chain(@(1).val, @(2).val.cmd))]),
    ivenv_decl := Rule(@(1, ivenv, e->ObjId(e.cmds[1])=decl),
        e -> decl(@(1).val.cmds[1].vars, ivenv(Concat([@(1).val.cmds[1].cmd], Drop(@(1).val.cmds, 1))))),
    ivenv_drop_selfassign := ARule(ivenv, [@(1, assign, e->IsVar(e.loc) and IsVar(e.exp) and e.exp=e.loc), @(2)],
        e->[@(2).val]),
    ivenv_creturn := Rule(@(1, ivenv, e->ObjId(Last(e.cmds))=creturn),
        e->chain(ivenv(DropLast(@(1).val.cmds, 1)), Last(@(1).val.cmds))),
    loop_pull_const_array_tcast := Rule([@(1, loop), @(2),  @(3), [@(4, chain), [@(5, assign), @(6,var), [@(7, tcast), @(8), @(9,nth, e->IsValue(e.idx))]],...]],
        e->let(#Print(e, "\n"),
		chain(@(5).val, loop(@(1).val.var, @(1).val.range, chain(Drop(@(4).val.cmds,1)))))),
    ivenv_state := Rule([@(1, tcast), @(2), @@(3, nth, (e, cx)->IsBound(cx.opts.state) and e.loc.id=cx.opts.state.id)],
        (e, cx)->nth(cx.opts.ivstate, e.args[2].idx) )
));

RulesCodeUnrollHACMIVArth := CopyFields(MergedRuleSet( RulesHCOLIVArith, RulesUnrollHCOL, RulesStrengthReduce,  RulesCodeHCOL),
    rec(inType:="iCode", outType := "iCode"));

RulesCodeHACMIVArth := CopyFields(MergedRuleSet(RulesStrengthReduce, RulesCodeHCOL, RulesHCOLIVArith),
    rec(inType:="iCode", outType := "iCode"));

RulesCodeUnrollHACMIVArthType := CopyFields(MergedRuleSet(RulesCodeUnrollHACMIVArth, RulesHCOLIVArithType),
	rec(inType :="iCode", outType:="iCode"));

Class(IVArithMixin, rec(
    precision := "double",
    TRealCtype := "double",
    IVRealType := TIVReal(T_Real(64)),
    IVBoolType := TInt,
    IVValueType := TIVReal(T_Real(64)),
    codeRuleSet := RulesCodeUnrollHACMIVArth
));

Class(ivExp, errExp);

Class(RealEPS, errExp);

Class(RealMPS, errExp);

Class(IVArith, SumsBase, BaseContainer, rec(
    rng := meth(self) return self._children[1].rng(); end,
    dmn := meth(self) return self._children[1].dmn(); end,
    toOperator := self >> (vec -> self.child(1).toOperator()(vec))
));

Class(TIVArith, Tagged_tSPL_Container, rec(
    abbrevs :=  [ s -> Checked(IsSPL(s), [s]) ],
    transpose := self >> CopyFields(self, rec(transposed := not self.transposed)),
    dims := self >> [self.params[1].dims()[1], self.params[1].dims()[2]],
    isReal := True,
    toOperator := self >> (vec -> let(r := self.params[1].toOperator()(vec), List(r, i->ivExp(i))))
));

NewRulesFor(TIVArith, rec(
    TIVArith_Base := rec(
        applicable := True,
        forTransposition := false,
        children := nt -> [[nt.params[1]]],
        apply := (t, C, Nonterms) -> IVArith(C[1])
    )
));

HCOLSumsGen.IVArith := (self, o, opts) >> IVArith(self(o.child(1), opts));

Class(TypeUpdate, HierarchicalVisitor, rec(
	__call__ := meth(arg)
        local res;
        res := ApplyFunc(arg[1].visit, arg{[2..Length(arg)]});
        return res;
    end,
	func := (self, o, opts) >> let(func(o.ret, o.id, o.params, self(o.cmd, opts))),
	decl := (self, o, opts) >>  decl(o.vars, self(o.cmd, opts)),
	chain := (self, o, opts) >> chain(List(o.cmds, i-> self(i, opts))),
    ivenv := (self, o, opts) >> ivenv(List(o.cmds, i-> self(i, opts))),
    loop := (self, o, opts) >>  loop(o.var, o.range, self(o.cmd, opts)),

	assign := (self, o, opts) >> assign(o.loc, self(o.exp, opts)),
	add := (self, o, opts) >> add(self(o.args[1], opts), self(o.args[2], opts)),
	sub := (self, o, opts) >> sub(self(o.args[1], opts), self(o.args[2], opts)),
	mul := (self, o, opts) >> mul(self(o.args[1], opts), self(o.args[2], opts)),
	div := (self, o, opts) >> div(self(o.args[1], opts), self(o.args[2], opts)),
	cond := (self, o, opts) >> cond(o.args[1], self(o.args[2], opts), self(o.args[3], opts)),
	abs := (self, o, opts) >> abs(self(o.args[1], opts)),
	logic_and := (self, o, opts) >> logic_and(self(o.args[1], opts), self(o.args[2], opts)),
	logic_or := (self, o, opts) >> logic_or(self(o.args[1], opts), self(o.args[2], opts)),
	gt  := (self, o, opts) >> gt(self(o.args[1], opts), self(o.args[2], opts)),
	lt  := (self, o, opts) >> lt(self(o.args[1], opts), self(o.args[2], opts)),
	geq  := (self, o, opts) >> geq(self(o.args[1], opts), self(o.args[2], opts)),
	leq  := (self, o, opts) >> leq(self(o.args[1], opts), self(o.args[2], opts)),
	min := (self, o, opts) >> min(self(o.args[1], opts), self(o.args[2], opts)),
	max := (self, o, opts) >> max(self(o.args[1], opts), self(o.args[2], opts)),

	nth := (self, o, opts) >> o,
	Value := (self, o, opts) >> o,
	var := (self, o, opts) >> o,

	skip := (self, o, opts) >> o,
	tcast := (self, o, opts) >> o,

	creturn := (self, o, opts) >> o,
	creturnCond := (self, o, opts) >> o,

	eq := (self, o, opts) >> eq(self(o.args[1], opts), self(o.args[2], opts)),
));

HCOLCodegen.IVArith := (self, o, y, x, opts) >> let(
	#Generate non interval code
    cc := opts.codegen.OLCompose(o.child(1), y, x, opts),

	#Collect array of bools and make them into arrays of ints
	ba_vars := Filtered(FoldL(Collect(cc, var), (a,b)->When(not b.id in List(a, i->i.id), Concat([b],a), a), []),
    	    e->ObjId(e.t) = TArray and e.t.base_t()=TBool),
    ba_nvars := List(ba_vars, c-> [c, var.fresh_t("U", TArray(TInt, c.t.size))] ),
    ba_svars := FoldL(ba_nvars, (b, a)->CopyFields(rec((a[1].id):= a[2]), b), rec()),
    ba_cc1 := decl(List(ba_nvars, c->c[2]), SubstVars(cc, ba_svars)),

	#Cast bools variables into ints but make sure that they are not part of the input parameters
	b_vars :=Unique(Collect(ba_cc1, @(1,var).cond(e->e.t = TBool))),
	b_local_vars := Difference(b_vars, opts.params),
	b_global_vars := Difference(b_vars, b_local_vars),
	b_no_global := SubstBottomUp(ba_cc1, @(1,var).cond(e->e.t=TBool), f->let(Print(@(1).val.id, "\n"),
							tcast(TInt, @(1).val))),

	#Cast pointers to bools into pointers to ints
	b_cc2 := b_no_global,

	#Convert boolean constants into ints
	b_cc3 := SubstBottomUp(b_cc2, @(1,Value).cond(e->e.t=TBool),
        f->let(#Print(@(1).val, "\n"),
			Cond(@(1).val=V_true,  V(1),
			     @(1).val=V_false, V(0),
						   @(1).val))),

	#Collect array variables make them into arrays of intervals
    vars := Filtered(FoldL(Collect(b_cc3, var), (a,b)->When(not b.id in List(a, i->i.id), Concat([b],a), a), []),
    	    e->ObjId(e.t) = TArray and e.t.base_t() in [TReal, TDouble, T_Real(32), T_Real(64)]),
    nvars := List(vars, c-> [c, var.fresh_t("U", TArray(TIVReal(c.t.base_t()), c.t.size))] ),
    svars := FoldL(nvars, (b, a)->CopyFields(rec((a[1].id):= a[2]), b), rec()),
    cc1 := decl(List(nvars, c->c[2]), SubstVars( cc, svars)),

	#Cast doubles and reals variables into intervals
    cc11 := SubstBottomUp(cc1, @(1,var).cond(e->e.t in [TReal, TDouble, T_Real(32), T_Real(64)]), f->tcast(opts.IVRealType, @(1).val)),

	#Cast pointers to doubles and reals into intervals
    cc2 := SubstBottomUp(cc11, [@(1,nth), @(2, var, e->ObjId(e.t) = TPtr and e.t.base_t() in [TReal, TDouble, T_Real(32), T_Real(64)]), @(3)], f->tcast(opts.IVRealType, @(1).val)),

	#Cast constants into intervals
    cc3 := SubstBottomUp(cc2, @(1,Value).cond(e->e.t in [TReal, TDouble, T_Real(32), T_Real(64)]),
        f->let(tcast(opts.IVValueType, @(1).val))),

	result := ivenv(cc3),

	result2 := TypeUpdate(result, opts),

	result2
);

HCOLUnparser.RealEPS := (self,o,i,is) >> Print(Cond(o.args[1] = T_Real(64), "DBL_MIN", o.args[1] = T_Real(32), "FLT_MIN", "UNKNOWN_MIN"));
HCOLUnparser.RealMPS := (self,o,i,is) >> Print(Cond(o.args[1] = T_Real(64), "DBL_MAX", o.args[1] = T_Real(32), "FLT_MAX", "UNKNOWN_MAX"));
HCOLUnparser.ivenv := (self,o,i,is) >> self(chain(o.cmds), i, is);

HCOLUnparser.TIVReal := (self, t, vars, i, is) >>
		Print("interval_t ", self.infix(vars, ", ", i + is));
HCOLUnparser.TVect := (self, t, vars, i, is) >> Print("abc_interval_t ", self.infix(vars, ", ", i + is));

HCOLSSEUnparser.ivenv := (self,o,i,is) >> Print(Blanks(i), "{\n",
    Blanks(i+is), "unsigned _xm = _mm_getcsr();\n",
    Blanks(i+is), "_mm_setcsr(_xm & 0xffff0000 | 0x0000dfc0);\n",
    self(chain(o.cmds),i+is,is),
    Blanks(i+is),
    Cond(IsBound(self.opts.compiler) and self.opts.compiler = "IntelC", "__asm nop;\n",
        IsBound(self.opts.compiler) and self.opts.compiler = "GnuC", "asm volatile(\"\":::\"memory\");\n",
        "// BASIC BLOCK BARRIER\n"
    ),
    Blanks(i+is), "if (_mm_getcsr() & 0x0d) {\n",
    Blanks(i+2*is), "_mm_setcsr(_xm);\n",
	Blanks(i+2*is),
    Cond(self.opts.useCReturn, "return -1;\n", "Y[0] = -1;\n"),
    Blanks(i+is), "}\n",
    Blanks(i+is), "_mm_setcsr(_xm);\n",
    Blanks(i), "}\n");

HCOLSSEUnparser.RealEPS := HCOLUnparser.RealEPS;
HCOLSSEUnparser.RealMPS := HCOLUnparser.RealMPS;

Declare(ToIVArithBasic);

Class(ToIVArithBasic_Base, HierarchicalVisitor, rec(
    __call__ := meth(arg)
        local res;
        res := ApplyFunc(arg[1].visit, arg{[2..Length(arg)]});
        trace_log.addConversion(ObjId(arg[2]), arg[2], res, var);
       return res;
    end,
    func := (self, o, opts) >> let(Print("CONVERSION\n\n"),
		func(o.ret, o.id, FoldL(o.params, (a, b)->Concat(a, When(IsBound(opts.state) and b=opts.state, [opts.ivstate], [b])), []), self(o.cmd, opts))),
    creturn := (self, o, opts) >> o,
	creturnCond := (self, o, opts) >> o,
    decl := (self, o, opts) >> let(
        ovars := Filtered(o.vars, e->ObjId(e.t) = TIVReal),
        svars := FoldR(ovars, (b,a)->CopyFields(rec((a.id):=var.fresh_t("u", TVect(T_Real(64), 2))), b), rec()),
        vars := Filtered(o.vars, e->ObjId(e.t) <> TIVReal)::List(Filtered(RecFields(svars), i->not i in SystemRecFields), i->svars.(i)),
        cmd := SubstVars(o.cmd, svars),
        decl(vars, self(cmd, opts))),
    chain := (self, o, opts) >> chain(List(o.cmds, i-> self(i, opts))),
    ivenv := (self, o, opts) >> ivenv(List(o.cmds, i-> ToIVArithBasic(i, opts))),
    loop := (self, o, opts) >> loop(o.var, o.range, self(o.cmd, opts)),
    nth := (self, o, opts) >> o,

    Value := (self, o, opts) >> let(
        When(ObjId(o.t) = TIVReal,
          TVect(T_Real(64), 2).value([-o.v, o.v]),
          o)),

	skip := (self, o, opts) >> o,

	tcast := (self, o, opts) >> o,
	logic_and := (self, o, opts) >> o,
	logic_or := (self, o, opts) >> o,
	assign := (self, o, opts) >> o,
));

Class(ToIVArithBasic, HierarchicalVisitor, rec(
    __call__ := meth(arg)
        local res;
        res := ApplyFunc(arg[1].visit, arg{[2..Length(arg)]});
        trace_log.addConversion(ObjId(arg[2]), arg[2], res, var);
       return res;
    end,
    func := (self, o, opts) >> let(Print("CONVERSION\n\n"),
		func(o.ret, o.id, FoldL(o.params, (a, b)->Concat(a, When(IsBound(opts.state) and b=opts.state, [opts.ivstate], [b])), []), self(o.cmd, opts))),
    creturn := (self, o, opts) >> o,
	creturnCond := (self, o, opts) >> o,
    decl := (self, o, opts) >> let(
        ovars := Filtered(o.vars, e->ObjId(e.t) = TIVReal),
        svars := FoldR(ovars, (b,a)->CopyFields(rec((a.id):=var.fresh_t("u", TVect(T_Real(64), 2))), b), rec()),
        vars := Filtered(o.vars, e->ObjId(e.t) <> TIVReal)::List(Filtered(RecFields(svars), i->not i in SystemRecFields), i->svars.(i)),
        cmd := SubstVars(o.cmd, svars),
        decl(vars, self(cmd, opts))),
    chain := (self, o, opts) >> chain(List(o.cmds, i-> self(i, opts))),
    ivenv := (self, o, opts) >> ivenv(List(o.cmds, i-> self(i, opts))),
    loop := (self, o, opts) >> loop(o.var, o.range, self(o.cmd, opts)),
    nth := (self, o, opts) >> o,

    Value := (self, o, opts) >> let(
        When(ObjId(o.t) = TIVReal,
          TVect(T_Real(64), 2).value([-o.v, o.v]),
          o)),

	skip := (self, o, opts) >> o,

	tcast := (self, o, opts) >> o,

	logic_and := (self, o, opts) >> cond(eq(o.args[1],o.args[2]), o.args[1], mul(o.args[1], o.args[2])),
	logic_or := (self, o, opts) >> cond(logic_or(eq(o.args[1], 1), eq(o.args[2],1)), 1, min(o.args[1], o.args[2])),

    assign := (self, o, opts) >> let(
	  Cond(
	    ObjId(o.exp) = var,
			let(
				chain(
					assign(o.loc[1], tcast(o.loc[1].t,o.exp)),
					assign(o.loc[2], tcast(o.loc[2].t,o.exp))
				)
			),
	    ObjId(o.exp) = tcast and (o.exp.args[1] = TIVReal or o.exp.args[1] = TVect(T_Real(64), 2)),
				Cond(
					o.exp.args[2].t = T_Real(32),
						let(
							x0 := var.fresh_t("x", T_Real(64)),
							x1 := var.fresh_t("x", T_Real(64)),
							decl([x0, x1], chain(
								self(assign([x0,x1], o.exp.args[2]), opts),
								assign(o.loc[1], x0+RealEPS(T_Real(32))),
								assign(o.loc[2], x1-RealEPS(T_Real(32)))
							))
						),
					IsDouble(o.exp.args[2]),
						let(
							x0 := var.fresh_t("x", T_Real(64)),
							x1 := var.fresh_t("x", T_Real(64)),
							decl([x0, x1], chain(
								self(assign([x0,x1], o.exp.args[2]), opts),
								assign(o.loc[1], x0),
								assign(o.loc[2], -x1)
							))
						),
					Error("Don't know how to convert <o.args[2]> to an interval.")),
		ObjId(o.exp) = lt and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(
				x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x20 := var.fresh_t("x", T_Real(64)),
				x21 := var.fresh_t("x", T_Real(64)),
                decl([x10, x11, x20, x21], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					self(assign([x20, x21], o.exp.args[2]), opts),
					assign(o.loc, tcast(TInt, cond(lt(x10, x21), V(1), cond(lt(x20,x11), V(0),V(-1)))))
                ))
            ),
		ObjId(o.exp) = gt and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x20 := var.fresh_t("x", T_Real(64)),
				x21 := var.fresh_t("x", T_Real(64)),
                decl([x10, x11, x20, x21], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					self(assign([x20, x21], o.exp.args[2]), opts),
					assign(o.loc, tcast(TInt, cond(gt(x11, x20), V(1), cond(gt(x21, x10), V(0), V(-1)))))
                ))
            ),

		ObjId(o.exp) = add  and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
			let(x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x20 := var.fresh_t("x", T_Real(64)),
				x21 := var.fresh_t("x", T_Real(64)),
				decl([x10, x11, x20, x21], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					self(assign([x20, x21], o.exp.args[2]), opts),
					assign(o.loc[1], add(x11, x21)),
					assign(o.loc[2], add(x10, x20))
				))
		  ),
		ObjId(o.exp) = sub  and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
			let(x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x20 := var.fresh_t("x", T_Real(64)),
				x21 := var.fresh_t("x", T_Real(64)),
				decl([x10, x11, x20, x21], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					self(assign([x20, x21], o.exp.args[2]), opts),
					assign(o.loc[1], sub(x11, x20)),
					assign(o.loc[2], sub(x10, x21))
				))
			),
		ObjId(o.exp) = abs  and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(
				x10 := var.fresh_t("x", T_Real(64)),
				x11 := var.fresh_t("x", T_Real(64)),
				x2 := var.fresh_t("x", T_Real(64)),
				x3 := var.fresh_t("x", T_Real(64)),
                decl([x10, x11, x2, x3], chain(
					self(assign([x10, x11], o.exp.args[1]), opts),
					assign(x2, abs(x10)),
					assign(x3, abs(x11)),
					assign(o.loc[1], max(x2, x3)),
					assign(o.loc[2], min(x2, x3))
                ))
            ),
		assign(o.loc, self(o.exp, opts))
    )),
));

Class(ToIVArithSSE, HierarchicalVisitor, rec(
    __call__ := meth(arg)
        local res;
        res := ApplyFunc(arg[1].visit, arg{[2..Length(arg)]});
        trace_log.addConversion(ObjId(arg[2]), arg[2], res, var);
       return res;
    end,
    func := (self, o, opts) >> let(#Print("CONVERSION\n\n"),
		func(o.ret, o.id, FoldL(o.params, (a, b)->Concat(a, When(IsBound(opts.state) and b=opts.state, [opts.ivstate], [b])), []), self(o.cmd, opts))),
    creturn := (self, o, opts) >> o,
	creturnCond := (self, o, opts) >> o,
    decl := (self, o, opts) >> let(
        ovars := Filtered(o.vars, e->ObjId(e.t) = TIVReal),
        svars := FoldR(ovars, (b,a)->CopyFields(rec((a.id):=var.fresh_t("u", TVect(T_Real(64), 2))), b), rec()),
        vars := Filtered(o.vars, e->ObjId(e.t) <> TIVReal)::List(Filtered(RecFields(svars), i->not i in SystemRecFields), i->svars.(i)),
        cmd := SubstVars(o.cmd, svars),
        decl(vars, self(cmd, opts))),
    chain := (self, o, opts) >> chain(List(o.cmds, i-> self(i, opts))),
    ivenv := (self, o, opts) >> ivenv(List(o.cmds, i-> self(i, opts))),
    loop := (self, o, opts) >> loop(o.var, o.range, self(o.cmd, opts)),
    nth := (self, o, opts) >> o,
    Value := (self, o, opts) >> let(
        When(ObjId(o.t) = TIVReal,
          TVect(T_Real(64), 2).value([-o.v, o.v]),
          o)),
    var := (self, o, opts) >> o,
	skip := (self, o, opts) >> o,
    tcast := (self, o, opts) >> Cond(
        IsDouble(o.args[2]), vpack(-o.args[2], o.args[2]),
	IsValue(o.args[2]), TVect(T_Real(64), 2).value([-o.args[2].v, o.args[2].v]),
        o.args[2].t = T_Real(32), addsub_2x64f(vcvt_64f32f(vdup(RealEPS(T_Real(32)), 4)), vcvt_64f32f(vdup(o.args[2], 4))),
        o.args[2].t = T_Real(64), addsub_2x64f(vdup(RealEPS(T_Real(64))+RealEPS(T_Real(64)), 2), vdup(o.args[2], 2)),
	o.args[2].t = TIVReal(T_Real(64)),  TVect(T_Real(64),2).value(o.args[2]),
        Error("Don't know how to convert <o.args[2]> to an interval.")),

	logic_and := (self, o, opts) >> cond(eq(o.args[1],o.args[2]), o.args[1], mul(o.args[1], o.args[2])),
	logic_or := (self, o, opts) >> cond(logic_or(eq(o.args[1], 1), eq(o.args[2],1)), 1, min(o.args[1], o.args[2])),

    testc_4x32i:= (self, o, opts) >> o,

    assign := (self, o, opts) >> Cond(
	    ObjId(o.exp) = add and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
		  let(
			x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
			decl([x1, x2], chain(
				comment("addition"),
				self(assign(x1, o.exp.args[1]), opts),
				self(assign(x2, o.exp.args[2]), opts),
				assign(o.loc, add(x1, x2)) ) )
		  ),
		ObjId(o.exp) = sub and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
		  let(
			x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
			decl([x1, x2], chain(
				comment("sub"),
				self(assign(x1, o.exp.args[1]), opts),
				self(assign(x2, o.exp.args[2]), opts),
				assign(o.loc, add(x1, vushuffle_2x64f(x2, vparam([2,1])))) ) )
		  ),
        ObjId(o.exp) = mul and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
            let(u := var.fresh_t("x", TVect(T_Real(64), 2)), a := var.fresh_t("x", TVect(T_Real(64), 2)),
                b := var.fresh_t("x", TVect(T_Real(64), 2)), c := var.fresh_t("x", TVect(T_Real(64), 2)),
                x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([u, a, b, c, x1, x2], chain(
					comment("mul"),
					self(assign(x1, o.exp.args[1]), opts),
					self(assign(x2, o.exp.args[2]), opts),
                    assign(u, addsub_2x64f(TVect(T_Real(64), 2).zero(), x1)),
                    assign(a, mul(u, x2)),
                    assign(b, mul(vushuffle_2x64f(u, vparam([2,1])), x2)),
                    assign(c, neg(min(a, b))),
                    assign(o.loc, add(max(max(a, b), vushuffle_2x64f(c, vparam([2,1]))), vdup(RealEPS(T_Real(64)), 2)))
                ))
            ),
        ObjId(o.exp) = abs and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
            let(u := var.fresh_t("x", TVect(T_Real(64), 2)), x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([u, x1], chain(
					comment("abs"),
                    self(assign(x1, o.exp.args[1]), opts),
                    assign(u, vushuffle_2x64f(x1, vparam([2,1]))),
                    assign(o.loc, vshuffle_2x64f(min(x1, u), max(x1, u), vparam([1,2])))
                ))
            ),
		ObjId(o.exp) = pow and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)) and IsValue(o.exp.args[2]) and o.exp.args[2].v = 2,
			let( x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
				decl([x1], chain(
					self(assign(x1, mul(o.exp.args[1],o.exp.args[1])), opts),
					assign(o.loc, x1)
				))
			),
		ObjId(o.exp) = pow and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)) and IsValue(o.exp.args[2]) and IsInt(o.exp.args[2].v) and o.exp.args[2] > 2,
			let( x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
				decl([x1], chain(
					self(assign(x1, mul(o.exp.args[1], pow(o.exp.args[1],o.exp.args[2].v-1))), opts),
					assign(o.loc, x1)
				))
			),

		ObjId(o.exp) = div and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)) and not IsValue(o.exp.args[1]),
			let(
			Print("DIVISION - PART 1\n"),
			  x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
			  decl([x1], chain(
				self(assign(x1, mul(o.exp.args[1], 1.0/o.exp.args[2])), opts),
				assign(o.loc, x1)
			  ))
			),

		ObjId(o.exp) = div and <#o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)) and #>IsValue(o.exp.args[1]),
			let(
				x1 := var.fresh_t("x", TVect(T_Real(64), 2)),
				decl([x1], chain(
					assign(x1, vdiv_2x64f(TVect(T_Real(64), 2).value([-o.exp.args[1].v,o.exp.args[1].v]), o.exp.args[2])),
					assign(o.loc, vshuffle_2x64f(x1, x1, vparam([2,1])))
				))
			),
		ObjId(o.exp) = neg and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
		    let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)),x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
				decl([x1], chain(
					self(assign(x1, TVect(T_Real(64), 2).value([0,0]), opts)),
					self(assign(x2, sub(x1, o.exp.args[1])), opts),
					assign(o.loc, x2)
				))
			),
        ObjId(o.exp) = max and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2], chain(
                    self(assign(x1, o.exp.args[1]), opts),
                    self(assign(x2, o.exp.args[2]), opts),
                    assign(o.loc, vshuffle_2x64f(min(x1, x2), max(x1, x2), vparam([1, 2])))
                ))
            ),
         ObjId(o.exp) = min and (o.loc.t = TIVReal or o.loc.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2], chain(
                    self(assign(x1, o.exp.args[1]), opts),
                    self(assign(x2, o.exp.args[2]), opts),
                    assign(o.loc, vshuffle_2x64f(max(x1, x2), min(x1, x2), vparam([1, 2])))
                ))
            ),
        ObjId(o.exp) = geq <#and (o.loc.t = TBool or o.loc.t = TInt)#> and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)), u := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2, u], chain(
					comment("geq"),
                    assign(x1, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[1], opts))),
                    assign(x2, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[2], opts))),
                    assign(u, cmpge_2x64f(x1, vushuffle_2x64f(x2, vparam([2, 1])))),
                    assign(o.loc, sub(
                        testc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff"))),
                        testnzc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff")))))
                ))
            ),
        ObjId(o.exp) = leq <#and o.loc.t = TBool#> and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)), u := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2, u], chain(
                    assign(x1, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[1], opts))),
                    assign(x2, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[2], opts))),
                    assign(u, cmple_2x64f(x1, vushuffle_2x64f(x2, vparam([2, 1])))),
                    assign(o.loc, sub(
                        testc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff"))),
                        testnzc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff")))))
                ))
            ),
		ObjId(o.exp) = gt <#and (o.loc.t = TBool or o.loc.t = TInt)#> and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)), u := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2, u], chain(
                    assign(x1, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[1], opts))),
                    assign(x2, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[2], opts))),
                    assign(u, cmpgt_2x64f(x1, vushuffle_2x64f(x2, vparam([2, 1])))),
                    assign(o.loc, sub(
                        testc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff"))),
                        testnzc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff")))))
                ))
            ),
        ObjId(o.exp) = lt <#and o.loc.t = TBool#> and ForAll(o.exp.args, i->i.t = TIVReal or i.t = TVect(T_Real(64), 2)),
            let(x1 := var.fresh_t("x", TVect(T_Real(64), 2)), x2 := var.fresh_t("x", TVect(T_Real(64), 2)), u := var.fresh_t("x", TVect(T_Real(64), 2)),
                decl([x1, x2, u], chain(
                    assign(x1, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[1], opts))),
                    assign(x2, addsub_2x64f(TVect(T_Real(64), 2).zero(), self(o.exp.args[2], opts))),
                    assign(u, cmplt_2x64f(x1, vushuffle_2x64f(x2, vparam([2, 1])))),
                    assign(o.loc, sub(
                        testc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff"))),
                        testnzc_4x32i(tcast(TVect(T_Int(32), 4), u), vhex(Replicate(4, "0xffffffff")))))
                ))
            ),
		ObjId(o.exp) = eq,
			let(Error("Eq not implemented for interval arithmetic.")),
		ObjId(o.exp) = neq,
			let(Error("Neq not implemented for interval arithmetic.")),

        assign(o.loc, self(o.exp, opts))
    ),
    mul := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    abs := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    max := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	add := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	sub := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    geq := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    leq := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	gt  := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
    lt  := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	pow := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
	div := (self, o, opts) >> ApplyFunc(ObjId(o), List(o.args, i-> self(i, opts))),
));

HCOLSSEUnparser.TIVReal := (self, t, vars, i, is) >> Print("__m128d ", self.infix(vars, ", ", i + is));

CoSynthesizeStrategies.IVArithSSE := [ (t, opts) -> RandomRuleTree(t, opts),
        (rt, opts) -> SPLRuleTree(rt),
        (s, opts) -> SumsSPL(s, opts),
        (s, opts) -> Rewrite(s, [RulesSumsHCOLv2a, RulesTerminateReductionHCOL, RulesSumsHCOLv2b], opts),
        (s, opts) -> HCOLProof_Codegen(s, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHACMIVArth, opts),
        (c, opts) -> HCOLProof_CodeConversion(c, ToIVArithSSE, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHACMIVArthType, opts) ];

CoSynthesizeStrategies.IVArithBasic := [ (t, opts) -> RandomRuleTree(t, opts),
        (rt, opts) -> SPLRuleTree(rt),
        (s, opts) -> SumsSPL(s, opts),
        (s, opts) -> Rewrite(s, [RulesSumsHCOLv2a, RulesTerminateReductionHCOL, RulesSumsHCOLv2b], opts),
        (s, opts) -> HCOLProof_Codegen(s, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHACMIVArth, opts),
        (c, opts) -> HCOLProof_CodeConversion(c, ToIVArithBasic_Base, opts),
        (c, opts) -> Rewrite(c, RulesCodeUnrollHACMIVArth, opts) ];

HCOLSSEUnparser.addsub_2x64f := (self, o, i, is) >> Checked(Length(o.args) = 2,
        CondPat(o,
           [addsub_2x64f, @TReal, @TVect], self(addsub_2x64f(vdup(o.args[1],o.t.size), o.args[2]), i, is),
           [addsub_2x64f, @TVect, @TReal], self(addsub_2x64f(o.args[1], vdup(o.args[2], o.t.size)), i, is),
           [addsub_2x64f, @TInt,  @TVect], self(addsub_2x64f(vdup(_toReal(o.args[1]), o.t.size), o.args[2]), i, is),
           [addsub_2x64f, @TVect, @TInt],  self(addsub_2x64f(o.args[1], vdup(_toReal(o.args[2]), o.t.size)), i, is),
           [addsub_2x64f, @TVect, @TVect], self.printf("_mm_addsub_pd($1, $2)", o.args),
           [addsub_2x64f, @TVect, @(1).cond(e->e.t=TIVReal)], self.printf("_mm_addsub_pd($1, $2)", o.args),
           [addsub_2x64f, @(1).cond(e->e.t=TIVReal), @TVect], self.printf("_mm_addsub_pd($1, $2)", o.args),
           Error("Don't know how to unparse <o>. Unrecognized type combination")
    ));

HCOLSSEUnparser.max := (self, o, i, is) >> let(n := Length(o.args), When(
	IsVecT(o.t) and n >2, self.printf("_mm_max_$1($2, $3)", [self.ctype_suffix(o.t, _isa(self)), o.args[1],
		ApplyFunc(max, Drop(o.args, 1))]),
	CondPat(o,
		[max, @TVect, @TVect], self.prefix("_mm_max_" :: self.ctype_suffix(o.t, _isa(self)),o.args),
	    [max, @TVect, @(1).cond(e->e.t=TIVReal)], self.printf("_mm_max_pd($1, $2)", o.args),
        [max, @(1).cond(e->e.t=TIVReal), @TVect], self.printf("_mm_max_pd($1, $2)", o.args),
		Inherited(o, i, is))
));

HCOLSSEUnparser.min := (self, o, i, is) >> let(
	CondPat(o,
		[min, @TVect, @TVect], self.prefix("_mm_min_" :: self.ctype_suffix(o.t, _isa(self)),o.args),
	    [min, @TVect, @(1).cond(e->e.t=TIVReal)], self.printf("_mm_min_pd($1, $2)", o.args),
        [min, @(1).cond(e->e.t=TIVReal), @TVect], self.printf("_mm_min_pd($1, $2)", o.args),
		Inherited(o, i, is))
);

geq.computeType := self >> TBool;
leq.computeType := self >> TBool;
eq.computeType := self >> TBool;
pow.computeType := (self) >> self.args[1].t;
neq.computeType := (self) >> TBool;

min.computeType := self >> let(
		types := List(self.args, e->e.t),
		When(Length(Collect(types, TIVReal))>0,
			TIVReal(UnifyTypes(List(Collect(types, TIVReal), i->i.t))),   <# TVect(T_Real(64),2),  #>
			UnifyTypes(List(self.args, x->x.t)))
	);
max.computeType := self >> let(
		types := List(self.args, e->e.t),
		When(Length(Collect(types, TIVReal))>0,
			TIVReal(UnifyTypes(List(Collect(types, TIVReal), i->i.t))),   <# TVect(T_Real(64),2),  #>
			UnifyTypes(List(self.args, x->x.t)))
	);

VecExp.computeType := self >> let(
        t       := self.args[1].t,
        deref_t := When(IsPtrT(t), t.t, t),
        el_t    := Cond(IsVecT(deref_t), deref_t.t, deref_t),
	test    := Cond(el_t = TIVReal, el_t.t, el_t),
        TVect(test, self.v));

add.computeType := meth(self)
	    local len, t, ptr_args, other_args, sum;
	    len := Length(self.args);
	    if   len=0  then return TInt;
	    elif len=1  then return self.args[1].t;
	    else
            [ptr_args, other_args] := SplitBy(self.args, x->IsPtrT(x.t) or IsArrayT(x.t));
            if Length(ptr_args)=0 then
	       if (self.args[1].t = TIVReal or self.args[2].t = TIVReal) then
	         return TIVReal(T_Real(64));
	       else
	         return UnifyTypesL(self.args);
               fi;
            elif Length(ptr_args)=1 then
                sum := Sum(other_args);
                if other_args<>[] and not IsIntT(sum.t) then Error("Can't add non-integer to a pointer"); fi;
		   return self._ptrPlusOfs(ptr_args[1].t, sum);
            elif Length(other_args)=0 then
                return self._addPtrT(ptr_args);
            else
                return Error("Addition of more than one pointer and integers is not defined");
            fi;
	    fi;
    end;

sub.computeType := meth(self)
	    local len, t, ptr_args, other_args, sum;
	    len := Length(self.args);
	    if   len=0  then return TInt;
	    elif len=1  then return self.args[1].t;
	    else
            [ptr_args, other_args] := SplitBy(self.args, x->IsPtrT(x.t) or IsArrayT(x.t));
            if Length(ptr_args)=0 then
	       if (self.args[1].t = TIVReal or self.args[2].t = TIVReal) then
	         return TIVReal(T_Real(64));
	       else
	         return UnifyTypesL(self.args);
               fi;
            elif Length(ptr_args)=1 then
                sum := Sum(other_args);
                if other_args<>[] and not IsIntT(sum.t) then Error("Can't add non-integer to a pointer"); fi;
		   return self._ptrPlusOfs(ptr_args[1].t, sum);
            elif Length(other_args)=0 then
                return self._addPtrT(ptr_args);
            else
                return Error("Addition of more than one pointer and integers is not defined");
            fi;
	    fi;
    end;

mul.computeType := meth(self)
        local len, t, ptr_t, ptr_args, other_args, prod, args;
	args := self.args;

	len := Length(args);
	if   len=0  then return TInt;
	elif len=1  then return args[1].t;
	else
	    [ptr_args, other_args] := SplitBy(args, x->IsPtrT(x.t));
	    if Length(ptr_args)=0 then
	        if (self.args[1].t = TIVReal or self.args[2].t = TIVReal) then
  		  return TIVReal(T_Real(64));
		else
		  return UnifyTypesL(args);
		fi;
	    elif Length(ptr_args) > 1 then Error("Can't multiply pointers");
	    else
		prod := Product(other_args);
		if other_args<>[] and not IsIntT(prod.t) then Error("Can't multiply a pointer by a non-integer"); fi;
		return  self._ptrMul(ptr_args[1].t, prod);
	    fi;
	fi;
    end;

div.computeType := self >> When(self.args[1].t = TIVReal or self.args[2].t = TIVReal, TIVReal(T_Real(64)), UnifyTypes(List(self.args, x->x.t)));

TempArrayType := (child, y, x, index) ->
let(When(IsBound(child.a.t_in), child.a.t_in[index],
		let( X := Flat([x])[1], Y:= Flat([y])[1],
			Cond(
				IsBound(X.t.t) and X.t.t = TComplex, 		TComplex,
				IsBound(Y.t.t) and Y.t.t = TComplex, 		TComplex,
				ObjId(child) = ISumReduction, 				When(IsRec(child.idval), child.idval.t, TReal),
				ObjId(child) = PointWise, 					child.op.expr.t,
				X.t.t))));
TempArray := (y, x, child) -> let(cols := Flat([ Cols(child) ]),
	newType := TempArrayType(child, y, x, 1),
      StripList(
        List([ 1 .. Length(cols) ], (i) -> TempVec(TArray(TempArrayType(child,
               y, x, i), cols[i])))) );


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

Declare(param);

#Special ordering for parameters
#Allows control on the interfaces of autolib
Class(ParamOps, ExpOps, rec(
   \<   := (e1,e2) -> Cond(
       IsRec(e1) and IsRec(e2)
       and IsBound(e1.sort_weight)
       and IsBound(e2.sort_weight)
       and e1.sort_weight<>e2.sort_weight,
       e1.sort_weight < e2.sort_weight,
       ObjId(e1) <> ObjId(e2), ObjId(e1) < ObjId(e2),
       e1.rChildren() < e2.rChildren())
));

# Autolib coldness constants.
# pcCold -  parameter must be available at plan time;
# pcHot - parameter available at compute time only;
# pcAny - parameter can be hot or cold (default);
#

Class(pcCold, ConstClass);
Class(pcHot,  ConstClass);
Class(pcAny,  ConstClass);

#F param(<type>, <id>, [sort weight, [coldness]])  - symbolic representation of a parameter
#F                        (eg. of a codelet)
#F Fields: .t  - types
#F         .id - name of the parameter
#F         .sort_weight - weight during sorting
#F         .coldness - parameter coldness in autolib interfaces
Class(param, Loc, rec(
    isParam := true,
    __call__ := (arg) >> let(
                    self := arg[1],
                    type := arg[2],
                    id   := arg[3],
                    WithBases(self, Checked(IsString(id), IsType(type), rec(
                            operations := ParamOps,
                            id := id,
                            t := type,
                            sort_weight := When(Length(arg)>=4, arg[4], 0),
                            coldness := When(Length(arg)>=5, arg[5], pcAny))))),

    print := self >> Print(self.name, "(", self.t, ", \"", self.id, "\")"),
    rChildren := self >> [self.t, self.id],
    rSetChild := rSetChildFields("t", "id"),
    from_rChildren := (self, rch) >> CopyFields(ObjId(self)(rch[1], rch[2]),
            rec(sort_weight := self.sort_weight, coldness := self.coldness)),
    eval := self >> self,
    can_fold := False,

    computeType := self >> self.t,
    setRange := meth(self, r) # when used as a loop counter variable
       self.range := r;
       return self;
    end,
));

IsParam := x -> IsRec(x) and IsBound(x.isParam) and x.isParam;

Class(in_param, param);

Class(Unk, Exp, rec(
    __call__ := (self, t) >> Checked(IsType(t),
	WithBases(self, rec(operations := ExpOps, args:=[t], t := t))),
    computeType := self >> self.args[1],

   isUnk := true,
));

IsUnk := x -> IsRec(x) and IsBound(x.isUnk) and x.isUnk;

Class(UnkInt, Unk(TInt), rec(print := self >> Print(self.__name__)));

#F allocate(<var>, <type>) -- equivalent of malloc()
#F See also: deallocate(), zallocate()
Class(allocate, assign);

#F zero-allocate(<var>, <type>) -- equivalent of calloc()
#F See also: deallocate(), allocate()
Class(zallocate, assign);

#F deallocate(<var>, <type>) -- equivalent of free()
#F See also: allocate(), zallocate()
Class(deallocate, assign);

# fld(<type>, <loc>, <field_id>)
Class(fld, Loc, rec(
    __call__ := (self, type, loc, id) >> Checked(IsString(id), IsType(type), IsLoc(loc),
	WithBases(self, rec(
		operations := ExpOps,
		loc := loc,
		id := id,
		t := type))),
    computeType := self >> self.t,
    print := self >> Print(self.name, "(", self.t, ", ", self.loc, ", \"", self.id, "\")"),
    rChildren := self >> [self.t, self.loc, self.id],
    rSetChild := rSetChildFields("t", "loc", "id"),
    eval := self >> self,
    can_fold := False,
));

# ufld(<loc>, <field_id>)
Class(ufld, fld, rec(
    __call__ := (self, loc, id) >> Checked(IsString(id), IsLoc(loc),
	WithBases(self, rec(
		operations := ExpOps,
		loc := loc,
		id := id,
		t := TUnknown))),
    print := self >> Print(self.name, "(", self.loc, ", \"", self.id, "\")"),
    rChildren := self >> [self.loc, self.id],
    rSetChild := rSetChildFields("loc", "id"),
    eval := self >> self
));

# struct(<id>, <fields>)
#   <id> is the string that gives the name of the structure
#   <fields> is a list of <param>s or <var>s that go into the structure
#
Class(struct, Command, rec(
    __call__ := (self, id, fields) >> Checked(IsString(id), IsList(fields), WithBases(self,
	rec(operations := CmdOps,
	    id := id,
	    fields := fields))),
    rChildren := self >> [self.id, self.fields],
    rSetChild := rSetChildFields("id", "fields"),
    print := (self, i, si) >> Print(self.name, "(\"", self.id, "\", ", self.fields, ")"),
));

Class(ret, ExpCommand);

SubstParams := (s,bindings) ->
    SubstTopDownNR(s, @(1, param, e -> IsBound(bindings.(e.id))), e -> bindings.(e.id));

SubstParamsCustom := (s,bindings,objid_list) ->
    SubstTopDownNR(s, @(1, objid_list, e -> IsBound(bindings.(e.id))), e -> bindings.(e.id));

SubstVarsSafe := (s,bindings) ->
    SubstTopDownNR(s, @(1, var, e -> IsBound(bindings.(e.id))), e -> bindings.(e.id));


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

SetLinuxX86Profile:=function(opts, p, use_openmp)
   opts.profile:=Copy(default_profiles.linux_x86_icc);
   SetMakeOptsAffinity(opts);
   SetMakeOptsLibgen(opts);
   SetMakeOptsAssembly(opts);
   opts.includes := Concat(opts.includes, [ "<string.h>"]);

   if IsBound(opts.vector) then
       SetMakeOptsSSE(opts);
   fi;

   if p>1 then
       if (use_openmp) then
           SetMakeOptsOpenMP(opts);
       else
           SetMakeOptsPThreads(opts);
       fi;
   fi;
end;

SetOLOptions:=function(isa, p, use_openmp)
   local opts, MixedUnparser;

   if (isa = false) or (IsString(isa)) then
       opts := Copy(SpiralDefaults);
       if (IsString(isa)) then opts := InitDataType(opts,isa); fi;
   else
       if (isa = true) then isa:=SIMD_ISA_DB.active()[1]; fi;
       opts := SIMDGlobals.getOpts(isa);
   fi;

   if not IsBound(opts.profile) then
       SetLinuxX86Profile(opts, p, use_openmp);
   else
#Hack Cell profile
       opts.profile := default_profiles.linux_cellSPU_gcc_MMM;
       opts.profile.stubopts.ROWS := 1;
       opts.profile.stubopts.COLUMNS := 1;
       opts.includes := Concat(opts.includes, [ "<mm_malloc.h>", "<string.h>"]);
       opts.includes := Filtered(opts.includes, x->x<>"<omega32.h>");
   fi;

   if p>1 then
       if (use_openmp) then
           opts.unparser:=Class(MixedUnparser,OpenMP_UnparseMixin,opts.unparser);
       else
           opts.unparser:=Class(MixedUnparser,SMP_UnparseMixin,opts.unparser);
           opts.subParams := [var("num_threads", TInt), var("tid", TInt)];
       fi;
   fi;

   #OL specific
   opts.codegen := OLCodegen;

   opts.formulaStrategies.sigmaSpl := OLDefaultStrategy;

   opts.formulaStrategies.postProcess := [
              OLVectorPropagateRuleset,
              OLPushScatQuestionMarkInRuleset,
              OLAlreadyInitializedScatQuestionMarkRuleset,
              OLCrossPullInRuleset,
              OLAfterCrossPullInRuleset,
              OLVectorPropagateRuleset,
              OLSingleComposeRuleset,
              (s, opts) -> compiler.BlockSumsOpts(s, opts)
          ];

   #Unrolling
   opts.markBlock := MarkBlocksOps;
   opts.globalUnrolling := 300;

   #compile options
   opts.compileStrategy :=IndicesCS2;
   opts.useDeref := true;
   opts.propagateNth := false;
   opts.doScalarReplacement := true;

   #Libgen requires full spec of perms
   VPerm.print:=VPerm.printl;

   #ScatAcc requires full zero allocation for now
   opts.zeroallocate := true;

   #final code options
   opts.subName:="multi";
   opts.subInitName:="init_multi";

   return opts;
end;

SetSAROptions:=function(isa, p, use_openmp)
  local tags,opts;
  opts := SetOLOptions(isa, p, use_openmp);
  opts.codegen := OLCodegen;
  opts.zeroallocate := false;
  opts.InputTypes := [TDouble,TDouble,TDouble,TDouble];
  opts.OutputTypes := [TDouble,TDouble];
  opts.generateComplexCode := true;
  opts.TRealCType := "complex";
  opts.TRealCtype := "complex";
  opts := InitDataType(opts,"f64c");
  opts.formulaStrategies.sigmaSpl := OLDefaultStrategy;
  opts.breakdownRules.TTensorI_OL := [TTensorI_OL_Base, TTensorI_OL_Parrallelize_AParFirst, TTensorI_OL_Vectorize_AVecLast];
  opts.breakdownRules.SAR := [SAR_Base,SAR_MatchFilter,SAR_MatchFilterSMP,SAR_MatchFilterVec,SAR_MatchFilterVecBase];
  opts.breakdownRules.ExpandMult := [ExpandMult_Base, ExpandMult_One];
  opts.breakdownRules.SAR_Interpolation := [SAR_Interpolation_One];
  tags:=[];

  if (p>1) then
      Add(tags,AParSMP(p));
  fi;
  opts.formulaStrategies.postProcess := [
        OLVectorPropagateRuleset,
 	      OLScatQuestionMarkToScat,
              OLPushScatAccRuleset,
              OLCrossPullInRuleset,
              OLAfterCrossPullInRuleset,

              OLVectorPropagateRuleset,

              OLSingleComposeRuleset,
	      (s, opts) -> compiler.BlockSumsOpts(s, opts)];

  #Unrolling
  opts.markBlock := MarkBlocksOps;
  opts.globalUnrolling := 300;

  #compile options
  opts.compileStrategy :=IndicesCS2;
  opts.useDeref := true;
  opts.doNotScalarize := false;
  opts.propagateNth := false;
  opts.doScalarReplacement := true;

  #ScatAcc requires full zero allocation for now
  opts.zeroallocate := false;

  #final code options
  opts.subName:="multi";
  opts.subInitName:="init_multi";

  return opts;

end;


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

# ==========================================================================
# BaseOperation
# ==========================================================================
Class(BaseOperation, ClassSPL, rec(
    #-----------------------------------------------------------------------
    child       := (self, n) >> self._children[n],
    children    := self >> self._children,
    numChildren := self >> Length(self.children()),
    isReal      := self >> ForAll(self.children(), IsRealSPL),
    setChild := meth(self, n, what) self._children[n] := what; end,
    # -------- Transformation rules support ---------------------------------
    rChildren := ~.children,
    rSetChild := ~.setChild,
));


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

TempArrayOL:=function(y,x,child)
  local y1,x1,mytype,accu;
  return StripList(List(child.dmn(),l->TempVec(l)));
end;

#Class(OLCodegen,VectorCodegen, rec(
Class(OLCodegen, RecCodegenMixin, SMPCodegenMixin, VectorCodegen, rec(

    LinkIO := (self, o, y, x, opts) >> self(o.params[1], y, x, opts),

    Compose := meth(self, o,y,x,opts)
        local ch, numch, vecs, allow, i, j, indices, output, chcomp, veclen, rveclen, dmnoffset, rngoffset, outvar, cc;

    ch := Filtered(o.children(), i-> not i.name in ["DMPGath","DMPScat"]);
    numch := Length(ch);
    vecs := [y];
    allow := (x<>y);

    for i in [1..numch-1] do
        if allow and ObjId(ch[i])=Inplace then
            vecs[i+1] := vecs[i];
        else
            vecs[i+1] := TempArrayOL(y,x,ch[i]);
        fi;
    od;
    vecs[numch+1] := x;
    for i in Reversed([1..numch]) do
            if allow and ObjId(ch[i])=Inplace
        then vecs[i] := vecs[i+1]; fi;
    od;

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

    #Error("Codegn-Compose -> Wire");
    #Start from the most right and handle Wire

    i := numch;
    cc:=ch[i];
    if ObjId(cc)=BB then
	cc:=cc._children[1];
    fi;

    while (i>1 and (ObjId(cc)=Wire or ObjId(cc) = Cross)) do

        if ObjId(cc) = Wire then
        #Variable length
            if not IsList(vecs[i+1]) then
                veclen := 1;
                indices := Compacted([1..veclen]);
                output := List(cc.params[2]*indices,x->vecs[i+1]);
            else
                veclen := Length(vecs[i+1]);
                indices := Compacted([1..veclen]);
                output := List(cc.params[2]*indices,x->vecs[i+1][x]);
	fi;
	vecs[i] := output;
    else
    # Wires in Cross
        dmnoffset := 0;
        rngoffset := 0;
        for chcomp in cc._children do
	    if ObjId(cc)=BB then
	       cc:=cc._children[1];
	    fi;

        if (ObjId(chcomp) = Wire) then
            if not IsList(chcomp.dims()[2]) then
                veclen := 1;
            else
                veclen := Length(chcomp.dims()[2]);
            fi;
            if not IsList(chcomp.dims()[1]) then
                rveclen := 1;
            else
                rveclen := Length(chcomp.dims()[1]);
            fi;
            indices := Compacted([1..veclen]);
            output := List(chcomp.params[2]*indices,x->vecs[i+1][x+dmnoffset]);
            j:=1;
            for outvar in output do
                vecs[i][rngoffset + j] := outvar;
                j:=j+1;
            od;
            dmnoffset:=dmnoffset+veclen;
            rngoffset:=rngoffset+rveclen;
        fi;
    od;

    fi;
        i:=i-1;
        cc:=ch[i];
        if ObjId(cc)=BB then
	  cc:=cc._children[1];
	fi;
    od;

#    for i in [1..numch-1] do
#      if ObjId(ch[i])=Wire then
#          indices := Compacted([1..Length(vecs[i])]);
#          output := List(ch[i].params[2]*indices,x->vecs[i][x]);
#          vecs[i+1]:=output;
#      fi;
#    od;

    ## HACK For the A Cross I that were I is not computed
    ## Could be (advantageously) replaced by a Wire(Id) when
    ## the new system will be around

    # Disabled by Hao because it is broken in some way#
    for i in [2..numch] do
       if ObjId(ch[i])=Cross then
           for j in [1..Length(ch[i]._children)] do
              if (ObjId(ch[i]._children[j])=Prm and
                          ObjId(ch[i]._children[j].func)=fId and
                          ObjId(vecs[i][j].t)=TArray)or
                      (ObjId(ch[i]._children[j])=BB and
                          ObjId(ch[i]._children[j]._children[1])=Prm and
                          ObjId(ch[i]._children[j]._children[1].func)=fId and
                          ObjId(vecs[i][j].t)=TArray) or
                      (ObjId(ch[i]._children[j])=BB and
                          ObjId(ch[i]._children[j]._children[1])=I and
                          ObjId(vecs[i][j].t)=TArray) then
	      skip();
              #vecs[i][j]:=vecs[i+1][j];
          fi;
           od;
       fi;
    od;

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

    ScatInit := (self, o, y, x, opts) >> let(ii := Ind(),
    condition := When(Length(o.cond)=0,V(1),FoldL1(List(o.cond,x->eq(x,V(0))),logic_and)),
    chain(
    IF(condition,
        loop(ii, o.func.domain(), assign(nth(y,o.func.at(ii)), When(ObjId(y)=var,y.t.t.zero(),y.t.zero()))),
        skip()),
    self(o._children,y,x,opts))
    ),

    Wire := (self, o, y, x, opts) >> skip(),

    ScatAcc := (self, o, y, x, opts) >>
        self._acc(self(Scat(o.func),y,x,opts),y),

    ICScatAcc := meth(self,o,y,x,opts)
      local i, func;
      i := Ind(); func := o.func.lambda();
      return loop(i, o.func.domain(), chain(assign_acc(nth(y,func.at(i)), nth(x, i)),
                        assign_acc(nth(y,add(func.at(i),1)),nth(x,add(i,1)))));
    end,

     # NOTE: handle unaligned case - IsUnalignedPtrT(y)
     VScatAcc := (self, o, y, x, opts) >>
         self._acc(self(VScat(o.func,o.v),y,x,opts),y),

     VTensor_OL := meth(self, o, y, x, opts)
       local CastToVect;
       CastToVect:=x->tcast(TPtr(TVect(x.t.t, o.vlen)), x);
       return self(o.child(1), StripList(List(Flat([y]),t->CastToVect(t))), StripList(List(Flat([x]),t->CastToVect(t))), opts);
     end,

##########
### to FIX: this Multiplication implicitely takes 2 inputs
    Multiplication:= meth(self, o, y, x, opts)
    local iterator;

    iterator:=Ind();
    return loop(iterator, [ 0 .. o.element[2]-1 ],
            assign(nth(StripList(y), iterator), mul(nth(x[1], iterator),nth(x[2], iterator))));
    end,

    ICMultiplication:=meth(self,o,y,x,opts)
    local len,iterator;

    iterator:=Ind();
    len := When(o.element[2] = 2,1,o.element[2]);
    len := When(Length(o.element) = 3,len/2,len);
    return loop(iterator, [ 0 .. (len)- 1 ],
            chain(
                assign(nth(StripList(y),mul(iterator,2)),sub(mul(nth(x[1], mul(iterator,2)),nth(x[2],mul(iterator,2))),
                                 mul(nth(x[1], add(mul(iterator,2),1)),nth(x[2], add(mul(iterator,2),1))))),
                assign(nth(StripList(y), add(mul(iterator,2),1)),add(mul(nth(x[1], mul(iterator,2)),nth(x[2], add(mul(iterator,2),1))),
                                             mul(nth(x[1], add(mul(iterator,2),1)),nth(x[2],mul(iterator,2)))))));

    end,

    Addition:= meth(self, o, y, x, opts)
        local iterator;
        iterator:=Ind();
        return loop(iterator, [ 0 .. _unwrap(o.element[2])-1 ],
            assign(nth(StripList(y), iterator), add(nth(x[1], iterator),nth(x[2], iterator))));
    end,

   Subtraction:= meth(self, o, y, x, opts)
        local iterator;
        iterator:=Ind();
        return loop(iterator, [ 0 .. o.element[2]-1 ],
            assign(nth(StripList(y), iterator), sub(nth(x[1], iterator),nth(x[2], iterator))));
    end,

    Codelet := meth(self, o, y, x, opts)
        local code,inputlist,outputlist,outputTypes,inputTypes;

    o := o.child(1);
        o := SubstBottomUp(o,BB,e->e.rChildren()[1]);
        o := OLQuickAndDirtyHackForCodelet(o);

        o:=ApplyStrategy(o,opts.formulaStrategies.postProcess, UntilDone, opts);
        o := OLRulesBufferFinalize(o);
        outputTypes := opts.OutputTypes;
        inputTypes := opts.InputTypes;
        inputlist :=[];
        outputlist :=[];
        for i in [1..DimLength(o.dims()[1])] do
          Append(outputlist, [TPtr(outputTypes[i])]);
        od;
        for i in [1..DimLength(o.dims()[2])] do
          Append(inputlist, [TPtr(inputTypes[i])]);
        od;
        Unification(o, StripList(inputlist));

        if List(outputlist,l->l.t)<>List(o.rng(),l->l.t) then
          Error("Unification of final output failed");
        fi;

    ## Generating code : main body
#        o := BlockSums(opts.libgen.basesUnrolling, o);
    code := SReduce(self(o, y, x, opts), opts);
        code := ESReduce(code, opts);
    code := RemoveAssignAcc(code);
    code := BlockUnroll(code, opts);
    code := DeclareHidden(code);
    return code;
    end,

   Formula := meth(self, o, y, x, opts)
        local code, init_code, datas, prog, params, sub, inputTypes, outputTypes,
    initsub,i,inputlist,outputlist,smp, num_threads, buffers, bufalloc, dalloc, dvars, map,ignore, codelet_codes, codelet_recs, chash;

    o := o.child(1);
        o := OLRulesBufferFinalize(o);
    o := OLRulesCode(o);

    params := Set(Collect(o, param));
    smp := Collect(o, SMPSum);
    num_threads := When(smp=[], 1, smp[1].p);
    if not ForAll(smp, x->x.p=num_threads) then Error("Non-uniform num_threads in SMPSum's"); fi;
        outputTypes := opts.OutputTypes;
        inputTypes :=  opts.InputTypes;
    inputlist :=[];
    outputlist :=[];
    for i in [1..DimLength(o.dims()[1])] do
      Append(outputlist, [TPtr(outputTypes[i])]);
    od;
    for i in [1..DimLength(o.dims()[2])] do
      Append(inputlist, [TPtr(inputTypes[i])]);
    od;
    Unification(o, StripList(inputlist));

    if List(outputlist,l->l.t)<>List(o.rng(),l->l.t) then
      Error("Unification of final output failed");
    fi;

    inputlist :=[];
    outputlist :=[];
    for i in [1..DimLength(o.dims()[1])] do
      Append(outputlist, [var(ConcatenationString("Y",String(i)), TPtr(outputTypes[i]))]);
    od;
    for i in [1..DimLength(o.dims()[2])] do
      Append(inputlist, [var(ConcatenationString("X",String(i)), TPtr(inputTypes[i]))]);
    od;

        #We're dropping y and x right away because previous stuff is backward compatibility
    y:=StripList(outputlist);
        x:= StripList(inputlist);

        ## HACK for the unroll of ROIs
        if IsBound(opts.libgen) then
            chash:=spiral.libgen.CreateCodeletHashTable();
            for i in Flat(opts.libgen.codeletTab.entries) do
                if Collect(i.data.sums,Multiplication)=[] then
                    i.data.unrolling:=300;         #this is the unrolling threshold for non kernels
                fi;
                HashAdd(chash,i.key, i.data);
            od;
            opts.libgen.codeletTab:=chash;
        elif Collect(o,Multiplication)=[] then
            o := SubstBottomUp(o,BB,e->e.rChildren()[1]);
            SubstTopDownNR(o, @(1).cond(IsMarkedBlock),
                function(e)
                    local f;
                    f:=@(1).val;
                    f.isBlock:=false;
                    return f;
                end);
            o := compiler.BlockSumsOpts(o,CopyFields(opts,rec(globalUnrolling:=32)));
        fi;

    ## Generating code : codelets
    codelet_recs := spiral.libgen.CompileCodelets(o, opts);
    codelet_codes := List(codelet_recs,
            function(clrec)
              local inputs, outputs, myf;
              outputs:=StripList(List([1..Length(Flat([clrec.sums.dims()[1]]))],x->var(Concat("Y",When(x>1,String(x),"")), TPtr(outputTypes[x]))));
              inputs:=StripList(List([1..Length(Flat([clrec.sums.dims()[2]]))],x->var(Concat("X",When(x>1,String(x),"")), TPtr(inputTypes[x]))));
              myf:=func(TVoid, clrec.name, Concatenation(Flat([outputs, inputs]), clrec.params), clrec.code);
              myf.inline:=true;
              return myf;
            end
        );

    map := tab();
    ## Generating code : main body
    datas := Collect(o, FDataOfs);
        code := self(o, y, x, opts);
    code := SReduce(code, opts);
        code := ESReduce(code, opts);
    code := RemoveAssignAcc(code);
    code := BlockUnroll(code, opts);
#   code := SimplifySingularArray(code);
        # code := PowerOpt(code);
    code := DeclareHidden(code);
    if IsBound(opts.isFixedPoint) and opts.isFixedPoint then
        code := FixedPointCode(code, opts.bits, opts.fracbits);
    fi;
    [buffers, bufalloc, code] := spiral.libgen.AllocBuffersSMP("buf", code, map, opts);

    ## Generating code : initialization
    [dvars, dalloc, ignore] := spiral.libgen.AllocBuffersSMP("dat", decl(datas, skip()), map, opts);

    # NOTE: is there a better way?
    sub := opts.subName;
    initsub := opts.subInitName;
    code := func(TVoid, sub, Concatenation(params,
                When(IsBound(opts.subParams), opts.subParams, []), [y,x]), code);

        init_code := func(TVoid, initsub, [],
            chain(bufalloc, dalloc, List(datas, x -> SReduce(x.var.init, opts))));

        prog := SubstVars(chain(init_code, code), map);

        if Length(Filtered(opts.subParams,x->x.id="num_threads"))>0 then
            prog := data(var("NUM_THREADS", TInt), V(num_threads),prog);
        fi;

        prog := program(
            codelet_codes,
            decl(Concatenation(dvars, buffers, List(datas, x->x.var)),
        prog));

        prog:= GenerateBench(o, prog,opts);

    return FlattenCode(prog);
    end,
));

DefaultCodegen.ExpDiag := (self, o, y, x, opts) >> let(
   i   := Ind(),     d := o.d,
   elt := o.element, s := Length(elt.vars),
   xx  := List([0..s-1], j -> nth(x, i*s + j)),
   loop(i, d,
       assign(nth(y,i), elt.at(xx)))
);