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

# slab allocator.

_LuaInit := function(datas, slab, opts)
    local init;

    Error("haha");

end;

#
## _DefaultWrapInitCompute
#
# this is the first function which gets called on the code. It wraps
# the pure transform code in a function and also extracts any necessary
# init code.
#
# in addition, for the slab allocator, this code defines the slab struct.

Class(_SlabInitCompute, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        local x, y, io, params, datas, slab_struct,
            slab, sub, initsub, init, compute, stackvars,
            replacer,  type, length;

        # replacer function: replaces reference of a slab
        # variable in the code with a reference to the variable
        # in the slab. EG: X ==> slab->X
        replacer := (slab, code) ->
            DoForAll(slab.t.t.getVars(), e ->
                SubstTopDown(code, e, ee -> fld(e.t, slab, e.id))
            );

        type := (a) -> a;
        length := (a) -> a;

        # -------
        # here we build the slab structure
        # -------

        # get the standard input output
        [x, y] := _getXY(sums, opts);
        io := When(x=y, x, Concat(y, x));

        # any parameters in our sigma-spl expression
        params := Set(Collect(sums, param));

        # any data required by the transform
        datas := Collect(sums, FDataOfs);

        [stackvars, code] := Pull(code,

            # compare shape
            @(1, decl, e -> ForAny(e.vars, ee -> ObjId(ee.t) = TArray)),

            # subst shape: always drop array declarations, possibly drop decl.
            e -> let(nonarrayvars := Filtered(e.vars, ee -> ObjId(ee.t) <> TArray),
                When(nonarrayvars = [],
                    @(1).val.cmd,
                    decl(nonarrayvars, @(1).val.cmd)
                )
            ),

            # pull shape: pull out the array vars
            e -> Filtered(@(1).val.vars, ee -> ObjId(ee.t) = TArray)
        );

        stackvars := Flat(stackvars);

        # build the slab structure, and a variable for it
        slab_struct := T_Struct("slab_t", Concat(params, List(datas, e -> e.var), io, stackvars));
        slab := var("slab", TPtr(slab_struct));

        # names
        sub := Cond(IsBound(opts.subName), opts.subName, "compute");
        initsub := Cond(IsBound(opts.subName), Concat("init_", opts.subName), "initz");

        # generate the 'init' code
        if IsBound(opts.generateInitFunc) and opts.generateInitFunc then

            if IsBound(opts.luaInit) and opts.luaInit then
                init := _LuaInit(datas, slab, opts);
            else
                init := chain( List(datas, e -> SReduce(e.var.init, opts)) );
                replacer(slab, init);
            fi;

            init := func(TVoid, initsub, [slab], init);

        else
            init := func(TVoid, initsub, [slab], code);
        fi;

        # convert the direct array references to input, output, and data to
        # references to fields inside the slab alloced block.

        replacer(slab, code);

        compute := func(TVoid, sub, [slab], code);

        return program(
            define([slab_struct]),
            init,
            compute
        );

    end
));

Class(_SlabAlloc, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        local sub, initsub, funcs, slab_struct, slab, pslab, alloc, free, tmp;

        # names
        sub := Cond(IsBound(opts.subName), opts.subName, "compute");
        initsub := Cond(IsBound(opts.subName), Concat("init_", opts.subName), "initz");

        # we expect a program wrapper
        Constraint(ObjId(code) = program);

        # we expect a slab to be defined first.
        Constraint(ObjId(code.cmds[1]) = define);
        Constraint(ObjId(code.cmds[1].types[1]) = T_Struct);

        # extract functions for additional checks.
        funcs := Collect(code, func);

        # ensure ordering, code must already be wrapped by init/compute
        Constraint(2 = Length(Filtered(funcs, e -> e.id = sub or e.id = initsub)));

        slab_struct := code.cmds[1].types[1];
        slab := var("slab", TPtr(slab_struct));
        pslab := var("pslab", TPtr(TPtr(slab_struct)));
        tmp := var("tmp", slab_struct);
        tmp.size := 1;

        # allocation function.
        alloc := func(TVoid, "alloc", [pslab],
            allocate(deref(pslab), tmp)
        );

        free := func(TVoid, "dealloc", [slab],
            deallocate(slab, slab_struct)
        );

        Add(code.cmds, alloc);
        Add(code.cmds, free);

        return code;
    end
));

Class(_SlabTimer, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        local sub, initsub, funcs, i, numruns, slab_struct, slab, t, timer;

        # names
        sub := Cond(IsBound(opts.subName), opts.subName, "compute");
        initsub := Cond(IsBound(opts.subName), Concat("init_", opts.subName), "initz");

        # we expect a program wrapper
        Constraint(ObjId(code) = program);

        # we expect a slab to be defined first.
        Constraint(ObjId(code.cmds[1]) = define);
        Constraint(ObjId(code.cmds[1].types[1]) = T_Struct);

        # extract functions.
        funcs := Collect(code, func);

        # ensure ordering, make sure we have an init and compute
        Constraint(2 = Length(Filtered(funcs, e -> e.id = sub or e.id = initsub)));

        # ensure ordering, make sure we have the alloc/dealloc functions too!
        Constraint(2 = Length(Filtered(funcs, e -> e.id = "alloc" or e.id = "dealloc")));

        # these two are used interchangably, MUST be same type.
        i := var.fresh_t("i", TInt);
        numruns := var.fresh_t("numruns", TInt);

        slab_struct := code.cmds[1].types[1];
        slab := var("slab", TPtr(slab_struct));

        t := var("t", TPtr(TVoid));

        timer := func(TVoid, "timer", [t, numruns],
            decl([slab], chain(
                _fCall("alloc", [addrof(slab)]),
                _fCall(initsub, [slab]),
                # this is used by simics to switch from a fast functional
                # to a slow timed execute mode
                When(IsBound(opts.extraTimerCall) and opts.extraTimerCall,
                    _fCall("timer_start", [t]),
                    skip()
                ),
                When(IsBound(opts.coldcache) and opts.coldcache,
                    skip(),
                    _fCall(sub, [slab]) # warmup the cache by default.
                ),
                _fCall("timer_start", [t]),
                loop(i, numruns,
                    _fCall(sub, [slab])
                ),
                _fCall("timer_end", [t]),
                _fCall("dealloc", [slab])
            ))
        );

        Add(code.cmds, timer);

        return code;
    end,
));

Class(_SlabVerify, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        Add(code.cmds, func(TVoid, "verify", [], chain()));
        return code;
    end
));

#
## _SlabHackInit
#
# this object removes the sines/cosines from the init function. It was written to support
# the simplescalar backend. Simplescalar takes forever (and a day) to compute sin/cos, so
# this hack was necessary to improve the execution turnaround time.
#
# NOTE: It ONLY removes cos/sin calls from the init function, not any other.
#
Class(_SlabHackInit, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        local initsub, init;

        # get the init func name, and find it in the code.
        initsub := Cond(IsBound(opts.subName), Concat("init_", opts.subName), "initz");
        init := Collect(code, @1(1, func, e -> e.id = initsub));

        # replace all sin/cos calculations with constant 1.0
        SubstTopDown(init, sinpi, e -> V(1.0));
        SubstTopDown(init, cospi, e -> V(1.0));

        # replace the init function in the main code.
#        SubstTopDown(code, @(1, func, e -> e.id = initsub), e -> Error("a"));

        return code;
    end
));


#
# francy: Interactive Discrete Mathematics in GAP
#

#############################################################################
##
#M  Canvas( <title>, <options> ) . . . . . a new graphic canvas
##
InstallMethod(Canvas,
  "a title string, a default configurations record",
  true,
  [IsString,
   IsCanvasDefaults],
  0,
function(title, options)
  return MergeObjects(Objectify(CanvasObjectType, rec(
    id       := GenerateID(),
    menus    := rec(),
    graph    := rec(),
    chart    := rec(),
    messages := rec(),
    title    := title
  )), options);
end);

InstallOtherMethod(Canvas,
  "a title string",
  true,
  [IsString],
  0,
function(title)
  return Canvas(title, CanvasDefaults);
end);

#############################################################################
##
#M  Add( <canvas>, <francy object> ) . . . . . add objects to canvas
##
InstallOtherMethod(Add,
  "a canvas, a graph",
  true,
  [IsCanvas,
   IsFrancyGraph],
  0,
function(canvas, graph)
  canvas!.graph := graph;
  # unbind the chart, only graph should exist!
  Unbind(canvas!.chart);
  return canvas;
end);

InstallOtherMethod(Add,
  "a canvas, a chart",
  true,
  [IsCanvas,
   IsChart],
  0,
function(canvas, chart)
  canvas!.chart := chart;
  # unbind the graph, only chart should exist!
  Unbind(canvas!.graph);
  return canvas;
end);

InstallOtherMethod(Add,
  "a canvas, a menu",
  true,
  [IsCanvas,
   IsMenu],
  0,
function(canvas, menu)
  canvas!.menus!.(menu!.id) := menu;
  return canvas;
end);

InstallOtherMethod(Add,
  "a canvas, a message",
  true,
  [IsCanvas,
   IsFrancyMessage],
  0,
function(canvas, message)
  canvas!.messages!.(message!.id) := message;
  return canvas;
end);

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

#############################################################################
##
#M  Remove( <canvas>, <francy object> ) . . . . . remove object from canvas
##
InstallOtherMethod(Remove,
  "a canvas, a graph",
  true,
  [IsCanvas,
   IsFrancyGraph],
  0,
function(canvas, graph)
  Unbind(canvas!.graph);
  canvas!.graph := rec();
  canvas!.chart := rec();
  return canvas;
end);

InstallOtherMethod(Remove,
  "a canvas, a chart",
  true,
  [IsCanvas,
   IsChart],
  0,
function(canvas, chart)
  Unbind(canvas!.chart);
  canvas!.graph := rec();
  canvas!.chart := rec();
  return canvas;
end);

InstallOtherMethod(Remove,
  "a canvas, a menu",
  true,
  [IsCanvas,
   IsMenu],
  0,
function(canvas, menu)
  Unbind(canvas!.menus!.(menu!.id));
  return canvas;
end);

InstallOtherMethod(Remove,
  "a canvas, a message",
  true,
  [IsCanvas,
   IsFrancyMessage],
  0,
function(canvas, message)
  Unbind(canvas!.messages!.(message!.id));
  return canvas;
end);

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

#############################################################################
##
#M  Draw( ) . . . . .
##
InstallMethod(Draw,
  "a canvas",
  true,
  [IsCanvas],
  0,
function(canvas)
  local object;
  object := rec();
  object!.mime    := FrancyMIMEType;
  object!.version := InstalledPackageVersion("francy");
  object!.canvas  := Sanitize(canvas);
  return Objectify(
    JupyterRenderableType,
    rec(
      data := rec((FrancyMIMEType) := GapToJsonString(object)),
      metadata := rec((FrancyMIMEType) := rec())
    )
  );
end);

#############################################################################
##
#M  DrawSplash( ) . . . . .
##
InstallMethod(DrawSplash,
  "a canvas",
  true,
  [IsCanvas],
  0,
function(canvas)
    local name, result, page;

    name := Filename(DirectoryTemporary(), Concatenation("francy_", LowercaseString(ReplacedString(canvas!.title, " ", "_")) ,".html"));

    result := Draw(canvas);

    page := Concatenation(
    "<!DOCTYPE html>\n\
    \n\
      \n\
        <meta charset=\"utf-8\" content=\"text/html\" property=\"GAP,francy,d3.v5\"></meta>\n\
        <link rel=\"stylesheet\" type=\"text/css\" href=\"https://cdn.rawgit.com/mcmartins/francy/develop/js/extensions/browser/index.css\"></link>\n\
        \n\
        \n\
        <title>Francy</title>\n\
      \n\
      \n\

\n\
        \n\
      \n\
    ");

    PrintTo(name, page);

    if ARCH_IS_MAC_OS_X() or ARCH_IS_UNIX() then
        Exec("open ",name);
    elif ARCH_IS_WINDOWS() then
        Exec("start ",name);
    fi;

    return page;
end);


# As is, will not work if val is a String
Assign := function(var, val)
	Read(InputTextString(Concatenation(var, " := ", String(val), ";")));
end;


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

_HCOLProof_apply := (R, nt, children, nonterms) -> Checked(IsRule(R), IsSPL(nt),
    When(IsNewRule(R),
     R.apply(nt, children, nonterms),
     When(NumGenArgs(R.rule)=2,
          R.rule(nt.params, children),
          R.rule(nt.params, children, nonterms)))
);

HCOLProof_Codegen := function(ss, opts)
    local c;
    trace_log.beginStage("Sigma-SPL","icode", ss);
    c := opts.funcgen.generate(ss, opts);
    trace_log.endStage("Sigma-SPL","icode", c);
    return c;
end;

HCOLProof_CodeConversion := function(c, visitor, opts)
    local c2;
    # dead code?
    trace_log.beginStage("icode","icode", c);
    c2 := visitor(c, opts);
    trace_log.beginStage("icode","icode", c2);
    return c2;
end;


# Apparently GAP can only remove a file, not a directory
RemoveFile("input.txt");
# true
RemoveFile("docs");
# fail


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

Declare(DPBench);

#F DPBench(rec(experiment1 := opts1, ...), <dpopts>)
#F
#F d := DPBench(rec(default := SpiralDefaults), rec())
#F
#F DPBench Interface:
#F
#F     .build(transforms, opts, bopts, name, dpopts),
#F
#F     .generateCfiles := [true, false],  default = true
#F     .matrixVerify   := [true, false],  default = false
#F     .fftwVerify     := [true, false],  default = false
#F     .quickVerify     := [true, false],  default = false
#F
#F     .fileTransform(exp, t, opts),    .c filenames:   override in a subclass
#F     .funcTransform(exp, t, opts),    function names: override in a subclass
#F     .txtFileName(exp, runMethod),    timing file name
#F
#F     .runAll()                        run all transforms from every experiment's opts.benchTransforms
#F     .resumeAll()                     same as .runAll() but try to reload hash from disk
#F
#F     .run(transforms)                 run given transforms in all experiments
#F     .resume(transforms)              same as .run() but try to relaod hash from disk
#F
#F      .runRandomAll(),  .runRandom()    run a random ruletree
#F      .runExhaustiveAll(), .runExhaustive()   run all ruletrees
#F
#F     .generateCode(transforms, exp)
#F     .generateProductionCode(transforms, exp)   same as .generateCode() but will use opts.production()
#F
#F     .entries(transforms, exp)
#F     .times(transforms, exp)
#F     .alltimes(transforms)
#F     .speedup(transforms, baselineExp)
#F
#F     .flopcyc(transforms)      # FLoating point Operations Per Cycle
#F     .mflops(transforms, mhz)
#F     .scaledTimes(transforms, scaleFunc)
#F
#F      getResult(i)                    returns "rec(t, opts, rt)" for the <i>th experiment run by runAll()
Class(DPBench, rec(
    ##
    ## Private methods
    ##
    _checkExp := exp ->
        Cond(not IsRec(exp),        Error("Experiments record <exp> must be a record"),
         NumRecFields(exp) < 1, Error("Experiments record is empty"),
         not ForAll(UserRecFields(exp), f -> IsRec(exp.(f))),
                 Error("Each entry must be a valid Spiral options record (ie. SpiralDefaults)"),
         exp),

    _startHashFile := (self, hfile, exp, d) >> PrintTo(hfile,
        "<# DPBench experiment '", exp, "'\n",
        " # Started ", d[2], " ", d[3], " ", d[1], "  ", d[4], ":", d[5], "\n",
        " # Transforms: ", Cond(IsBound(self.transforms), self.transforms, ""), "#> \n\n",
        "ImportAll(spiral); Import(paradigms.common, paradigms.smp, platforms.sse, platforms.avx, paradigms.vector, nontransforms.ol); \n",
        "ImportAll(platforms.scalar); \n",
        "ImportAll(paradigms.vector); \n\n",
        "hash := HashTableDP(); \n"
    ),

    _loadHash := meth(self, hfile, opts)
        local b, bkdowns, ns, result;
	# Create a package that contains mappings from breakdown names to the corresponding
	# objects. This is needed because global names map to global breakdown rule objects
	# which might have different settings from the ones in opts.breakdownRules
	# Note: package is essentially a namespace that is always on top of imports
	#       so imports within a hash file will be superceded by it
	bkdowns := ConcatList(UserRecFields(opts.breakdownRules), f->opts.breakdownRules.(f));
        ns := tab();
	for b in bkdowns do ns.(b.name) := b; od;

        result := READ(hfile, ns);
        if result = false or not IsBound(ns.hash) then return false;
        else return ns.hash;
        fi;
    end,

    _saveHash := meth(self, hfile, exp, date, hash)
        local bucket, e;
        var.print := var.printFull;
        self._startHashFile(hfile, exp, date);
        for bucket in hash.entries do
            for e in bucket do
                if e.data<>[] then
                    AppendTo(hfile, "HashAdd(hash, ", e.key, ", [", e.data[1], "]);\n");
                fi;
            od;
        od;
        var.print := var.printShort;
    end,

    # merging two hash files by taking fastest entries, returns resulting hash
    _mergeHashes := meth(self, src_file1, src_file2, opts)
        local h1, h2;
        h1 := self._loadHash(src_file1, opts);
        h2 := self._loadHash(src_file2, opts);
        Checked(h1<>false and h2<>false,
            HashWalk(h1, function(key, data)
                local d;
                d := HashLookup(h2, key);
                if d=false or (d[1].measured>data[1].measured and data[1].measured>0) then
                    HashAdd(h2, key, data);
                fi;
            end));
        return h2;
    end,

    _reloadAllHashes := meth(self)
       local hash, exp, e;
       for e in UserRecFields(self.exp) do
           exp := self.exp.(e);
           hash := self._loadHash(exp.hashFile, exp);
           if (self.verbosity>0) then
              PrintLine(When(hash=false, "Could not load ", "Loaded "), e, " (", exp.hashFile, ")");
           fi;
           if hash <> false then
               exp.hashTable := hash;
           fi;
       od;
    end,

    _generateCode := meth(self, transforms, exp, opts)
         local entries, e, c, r, t;
         for t in transforms do
             e := self.entries([HashAsSPL(t)], exp)[1];
             if e = false then Error("Transform ", t, " not found in hashTable for experiment '", exp, "'"); fi;
             r := ApplyRuleTreeSPL(e.ruletree, t, opts);
             c := CodeRuleTree(r, opts);
             PrintLine(t, " -> ", self.prodFileTransform(exp, t, opts));
             PrintTo(self.prodFileTransform(exp, t, opts), PrintCode(self.prodFuncTransform(exp, t, opts), c, opts));
         od;
    end,

    ##
    ## Public methods
    ##
    __call__ := meth(self, experiments, dpopts)
        local e, exp;
        self._checkExp(experiments);
        exp:=rec();
        for e in UserRecFields(experiments) do
           exp.(e) := CopyFields(experiments.(e));
           if not IsBound(exp.(e).hashTable) then
               exp.(e).hashTable := HashTableDP(); fi;
           if not IsBound(exp.(e).hashFile) then
               exp.(e).hashFile := Concat(e, ".hash"); fi;
        od;

        return WithBases(self,
            rec(dpopts:=dpopts, ran:=false, exp:=exp, transforms:=[], verbosity:=1, callbacks:=[]));
    end,

    resume := meth(self, transforms)
        if not ForAll(UserRecFields(self.exp), e -> ForAll(self.entries(transforms, e), e->e<>false))
            then self._reloadAllHashes();
        fi;
        self.run(transforms);
    end,

    generateCfiles := true,
    measureFinal := true,

	_fileRoot := meth(self, exp, t, opts)
		if IsBound(opts.vector) and IsBound(opts.vector.conf) and IsBound(opts.vector.conf.functionNameRoot) then
			return opts.vector.conf.functionNameRoot;
		else
			return Concat(exp, "_", Drop(CodeletName(CodeletShape(t)), 1));
		fi;
	end,

	_freq := meth(self, opts)
		if IsBound(opts.vector) and IsBound(opts.vector.conf) and IsBound(opts.vector.conf.target) and IsBound(opts.vector.conf.target.freq) then
			return opts.vector.conf.target.freq;
		elif IsBound(LocalConfig.cpuinfo.freq) then
			return LocalConfig.cpuinfo.freq;
		else
			return 1000;
		fi;
	end,

    fileTimer     := (self, exp, t, opts) >> Concat(self._fileRoot(exp, t, opts), ".timer"),
    fileVerifierf := (self, exp, t, opts) >> Concat(self._fileRoot(exp, t, opts), ".verifier"),
    fileStub      := (self, exp, t, opts) >> Concat(self._fileRoot(exp, t, opts), ".h"),
    fileTransform := (self, exp, t, opts) >> Concat(self._fileRoot(exp, t, opts), ".c"),
    funcTransform := (self, exp, t, opts) >> self._fileRoot(exp, t, opts),
    prodFileTransform := (self, exp, t, opts) >> self.fileTransform(exp, t, opts), # used in generateProductionCode
    prodFuncTransform := (self, exp, t, opts) >> self.funcTransform(exp, t, opts),

    txtFileName   := (exp, runMethod) -> Concat(exp, ".", SubString(runMethod, 5), ".txt"),

    verify := meth(self, opts, ruletree, code)
        local mat;
        mat := When(ruletree.node.isReal() or opts.dataType = "complex" or opts.generateComplexCode,
                    MatSPL(ruletree.node),
                    RCMatCyc(MatSPL(ruletree.node)));
        return VerifyMatrixCode(code, mat, opts);
    end,

    #NOTE: Slightly hacked in. Check for opts.profile being bound etc. Look at VerifyMatrixRuleTree.
    verifyfftw := (self, opts, code) >> opts.profile.verifyfftw(code, opts),
    verifyquick := (self, opts, code) >> opts.profile.verifyquick(code, opts),

    resumeAll := meth(self)
        local e;
        for e in UserRecFields(self.exp) do
            if (self.verbosity>0) then
               PrintLine("Resuming ", e);
            fi;
            self.resume(self.exp.(e).benchTransforms);
        od;
    end,

    _runAll := meth(self, runMethod)
        local e;
        for e in UserRecFields(self.exp) do
            PrintLine("Running ", e);
            self.(runMethod)(self.exp.(e).benchTransforms);
        od;
    end,

    allTrees := (self) >> let(exp := self.exp.(UserRecFields(self.exp)[1]),
    List(exp.benchTransforms, t ->
        ApplyRuleTreeSPL( HashLookup(exp.hashTable, HashAsSPL(t))[1].ruletree,
        t, exp))),

    runAll            := (self) >> self._runAll("run"),
    runExhaustiveAll  := (self) >> self._runAll("runExhaustive"),
    runRandomAll      := (self) >> self._runAll("runRandom"),
    runRandomSaveAll  := (self) >> self._runAll("runRandomSave"),
    pickRandomSaveAll := (self) >> self._runAll("pickRandomSave"),

    run            := (self, transforms) >> self._run(transforms, "_runDP", true),
    runExhaustive  := (self, transforms) >> self._run(transforms, "_runExhaustive", true),
    runRandom      := (self, transforms) >> self._run(transforms, "_runRandom", false),
    runRandomSave  := (self, transforms) >> self._run(transforms, "_runRandomSave", true),
    pickRandomSave := meth(self, transforms)
        local generateCfiles;

        #NOTE: once generateCfiles quits measuring things, this can be removed
        generateCfiles := self.generateCfiles;
        self.generateCfiles := false;
        self._run(transforms, "_pickRandomSave", true);
        self.generateCfiles := generateCfiles;
    end,

    # Find best using DP
    _runDP         := (self, e, t, opts) >> TimedAction(DP(t, self.dpopts, opts)),

    # Find best using an exhaustive search
    outputExhaustive := false,
    _runExhaustive := meth(self, e, t, opts)
       local r, searchTime, mincycles, mintree, rt, c, compiletime, cm, measuretime;

       r := AllRuleTrees(t, opts);
       searchTime := 0;
       mincycles := 10^100;

       for rt in r do
          [c,  compiletime] := TimedAction(CodeRuleTreeOpts(rt, opts));
          [cm, measuretime] := TimedAction(CMeasure(c, opts));
          if self.outputExhaustive then _seqPerfStatsGflops(Concat(e, ".Exhaustive-all.txt"), t, self._freq(opts), self.artcost(rt), cm, compiletime+measuretime); fi;
          if (cm < mincycles) then
              mincycles := cm; mintree := Copy(rt);
          fi;
          searchTime:=searchTime+compiletime+measuretime;
       od;
       HashDelete(opts.hashTable,t);
       HashAdd(opts.hashTable, t, [rec(ruletree:=mintree, measured:=mincycles)]);
       return([mintree, searchTime, c, mincycles]);
    end,

    # Run a Random ruletree. Useful for quick, dirty, non-comprehensive tests.
    _runRandom := meth(self, e, t, opts)
       local r, c, rrtime, codetime, runtime, cycles;

       [r, rrtime]      := TimedAction(RandomRuleTree(t, opts));
       [c, codetime]    := TimedAction(CodeRuleTreeOpts(r, opts));
       [cycles, runtime]:= TimedAction(CMeasure(c, opts));

       return([r, (rrtime+codetime+runtime), c, cycles]);
     end,

    # Run random search and save result in hash.
    _runRandomSave := meth(self, e, t, opts)
       local r, c, rrtime, codetime, runtime, cycles;

       [r, rrtime]      := TimedAction(RandomRuleTree(t, opts));
       [c, codetime]    := TimedAction(CodeRuleTreeOpts(r, opts));
       [cycles, runtime]:= TimedAction(CMeasure(c, opts));

       HashDelete(opts.hashTable, t);
       HashAdd(opts.hashTable, t, [rec(ruletree:=r, measured:=runtime)]);

       return([r, (rrtime+codetime+runtime), c, cycles]);
    end,

    # Pick random and save result in hash (NOT measured).
    _pickRandomSave := meth(self, e, t, opts)
       local r, searchtime;
       [r, searchtime] := TimedAction(RandomRuleTree(t, opts));

       HashDelete(opts.hashTable,t);
       HashAdd(opts.hashTable,t,[rec(ruletree:=r)]);

       return([r, searchtime, false, -1]);
    end,

    _run := meth(self, transforms, runMethod, useHash)
        local code, t, e, outf, res, opts, cycles, hentry,  date, i, searchTime, acc, optsForFile, f, ruletree, randomRes;

        Constraint(ForAll(transforms, IsSPL));

        for f in self.callbacks do
			f(self);
		od;

        for e in UserRecFields(self.exp) do
            opts := self.exp.(e);
            date := Date();

            for t in transforms do
				code := false;
				t := SumsUnification(t, opts);
				if useHash then
					# For run methods that use hash tables
					hentry := HashLookup(opts.hashTable, HashAsSPL(t));
					if hentry = false or hentry = [] then
						res := self.(runMethod)(e, HashAsSPL(t), opts);
						if res[1] = [] then
							Error("DP did not find any ruletrees for <t> (", t, ")");
						fi;
						self._saveHash(opts.hashFile, e, date, opts.hashTable);
						hentry := HashLookup(opts.hashTable, HashAsSPL(t))[1];
						hentry.searchTime := res[2];
						searchTime := res[2];
						if Length(res) >= 3 then
							# _runDP doesn't return code
							code := res[3];
						fi;
					else
						hentry := hentry[1];
						searchTime := -1;
					fi;
					hentry.spectree := ApplyRuleTreeSPL(hentry.ruletree, t, opts);
                    #NOTE: exhaustive search will not update cycles
					cycles := When(IsBound(hentry.measured), hentry.measured, 0);
					if not t in self.transforms then
						Add(self.transforms, t);
					fi;
					ruletree := hentry.spectree;
				else
					# For run methods that don't use hash tables
					randomRes  := self.(runMethod)(e, HashAsSPL(t), opts);
					ruletree   := ApplyRuleTreeSPL(randomRes[1], t, opts);
					searchTime := randomRes[2];
					code       := randomRes[3];
					cycles     := randomRes[4];
				fi;

				#HACK: It's a pain to do this in a cleaner way
				if runMethod = "_pickRandomSave" then
					return;
				fi;

				if self.generateCfiles then
					#NOTE: Should also output stub.h as <filename.h>
					compiler.CMEASURE_CURRENT_TREE := ruletree;
					compiler.CMEASURE_LAST_CODE := false;
					if (code=false or HashAsSPL(t)<>t) then
						code := CodeRuleTree(ruletree, opts);
					fi;
					compiler.CMEASURE_LAST_CODE := code;
					if (self.measureFinal and (runMethod <> "_runRandom"))then
						cycles := CMeasure(code, opts);
					fi;

					if useHash then
						hentry.measured := cycles;
					fi;

					opts.fileinfo := rec(
						cycles  := cycles,
						flops   := self.artcost(ruletree),
						file    := self.fileTransform(e,t,opts),
						algorithm := ruletree
					);
					PrintTo(self.fileTransform(e,t,opts), PrintCode(self.funcTransform(e,t,opts), code, opts));
					Unbind(opts.fileinfo);
				fi;

				if self.matrixVerify or self.fftwVerify or self.quickVerify then
					if code=false then
						code := CodeRuleTree(ruletree, opts);
					fi;
					if self.matrixVerify then
						acc := self.verify(opts, ruletree, code);
					elif self.fftwVerify then
                        acc := self.verifyfftw(opts, code);
                    else
                        acc := self.verifyquick(opts, code);
                    fi;
                    if IsBound(opts.outputVecStatistics) and opts.outputVecStatistics then
                        _seqPerfStatsGflopsAccCount(self.txtFileName(e, runMethod), t,
                                [self.artcost(ruletree), code.countedArithCost(opts.vector.isa.countrec)*opts.vector.vlen], cycles, searchTime, acc,
                                    code.countOps(opts.vector.isa.countrec), opts.vector.isa.countrec);
                    else
                        _seqPerfStatsGflopsAcc(self.txtFileName(e, runMethod), t,
                                [self.artcost(ruletree), self.countedArithCost(code, opts)], cycles, searchTime, acc);
                    fi;
                else
					if (opts.verbosity>-1) then
                       if IsBound(opts.outputVecStatistics) and opts.outputVecStatistics then
                            _seqPerfStatsGflopsCount(self.txtFileName(e, runMethod), t,
                                    [self.artcost(ruletree), code.countedArithCost(opts.vector.isa.countrec)*opts.vector.vlen], cycles, searchTime,
                                        code.countOps(opts.vector.isa.countrec), opts.vector.isa.countrec);
                       else
                            _seqPerfStatsGflops(self.txtFileName(e, runMethod), t, self._freq(opts),
                                    [self.artcost(ruletree), self.countedArithCost(code, opts)], cycles, searchTime);
                       fi;
                    fi;
                fi;
            od;
        od;
        self.ran := true;
    end,

    generateCode := (self, transforms, exp) >> self._generateCode(transforms, exp, self.exp.(exp)),

    generateProductionCode := (self, transforms, exp) >> self._generateCode(transforms, exp, self.exp.(exp).production()),

    generateAllCode := self >> DoForAll(UserRecFields(self.exp), exp ->
        self._generateCode(self.exp.(exp).benchTransforms, exp, self.exp.(exp))),

    generateAllProductionCode := self >> DoForAll(UserRecFields(self.exp), exp ->
        self._generateCode(self.exp.(exp).benchTransforms, exp, self.exp.(exp).production())),

    entries := (self, transforms, exp) >>
        When(not IsBound(self.exp.(exp)), Error("No such experiment '",exp, "'"),
         Map(transforms,
         x -> let(lookup := MultiHashLookup(Concatenation([self.exp.(exp).hashTable], self.exp.(exp).baseHashes), x),
                When(lookup = false, false,
                #Error("Transform '", x, "' not found in the '", exp, "' table"),
                lookup[1])))),

    times := (self, transforms, exp) >> Map(self.entries(transforms, exp), x->x.measured),

    alltimes := (self, transforms) >>
        let(tr := When(IsList(transforms), transforms, [transforms]),
            Map(UserRecFields(self.exp), e -> Concatenation([e], self.times(tr, e)))),

    speedup := (self, transforms, baselineExp) >>
        let(b := self.times(transforms, baselineExp),
        Map(self.alltimes(transforms),
            times -> Map([1..Length(times)],
                         i -> When(not IsInt(times[i]), times[i], times[i] / b[i-1])))),

    flopcyc := (self, transforms) >>
        self.scaledTimes(transforms, e -> self.acost(e) / e.measured),

    mflops := (self, transforms, mhz) >>
        self.scaledTimes(transforms, e -> self.acost(e) * mhz / e.measured),

    # FLoating point Operations Per Cycle
    scaledTimes := (self, transforms, scaleFunc) >>
        let(tr := When(IsList(transforms), transforms, [transforms]),
            Map(UserRecFields(self.exp),
                e -> Concatenation([e], Map(self.entries(tr, e), scaleFunc)))),

    # Use NonTerminal.normalizedArithCost() if it is there, otherwise return 0
    acost := entry -> let(
        t := entry.ruletree.node,
        When(IsBound(t.normalizedArithCost), t.normalizedArithCost(), 0)
    ),

    artcost := ruletree -> let(
        t := ruletree.node,
        When(IsBound(t.normalizedArithCost), t.normalizedArithCost(), 0)
    ),

    countedArithCost := (self, c, opts) >>
        When(IsRec(c) and
             IsBound(c.countedArithCost) and
             IsBound(opts.vector) and IsBound(opts.vector.isa) and IsBound(opts.vector.isa.countrec) and IsBound(opts.vector.vlen),
             c.countedArithCost(opts.vector.isa.countrec)*opts.vector.vlen, 0),

    matrixVerify := false,
    setMatrixVerify := self >> CopyFields(self, rec(matrixVerify := true)),

    fftwVerify   := false,
    quickVerify   := false,

    getOpts := self >> self.exp.(UserRecFields(self.exp)[1]),

    _getResult := meth(arg)
        local self, t, exp, lookup, rt;
        self := arg[1];
        exp := UserRecFields(self.exp)[1];
        t := When(Length(arg) >= 2, self.exp.(exp).benchTransforms[arg[2]], self.exp.(exp).benchTransforms[1]);
        lookup := MultiHashLookup(Concatenation([ self.exp.(exp).hashTable ], self.exp.(exp).baseHashes), HashAsSPL(t));
        rt := When(lookup = false, false, lookup[1].ruletree);
        return rec(opts := self.exp.(exp), t := t, rt:= rt);
    end,

    getResult := meth(arg)
        local self, l, rt, opts, c;
        self := arg[1];
        l := When(Length(arg)>1, self._getResult(arg[2]), self._getResult());
        rt := l.rt;
        opts := l.opts;
        c := CodeRuleTree(rt, opts);
        return CopyFields(l, rec(c := c, opcount := c.countOps(opts.vector.isa.countrec)));
    end,

    _runAll := meth(self, runMethod)
        local e;
        for e in UserRecFields(self.exp) do
            PrintLine("Running ", e);
            self.(runMethod)(self.exp.(e).benchTransforms);
        od;
    end,

    build := function(arg)
        local transforms, opts, bopts, name, dpr;

        transforms := When(IsList(arg[1]), arg[1], [arg[1]]);
        opts := When(Length(arg) >= 2, arg[2], SpiralDefaults);
        opts.benchTransforms := transforms;
        bopts := When(Length(arg) >= 3, arg[3], rec());
        name := When(Length(arg) >= 4, arg[4], "spiral");
        dpr := When(Length(arg) >= 5, arg[5], rec(verbosity := 0, timeBaseCases:=true));

        return CopyFields(DPBench(rec((name) := opts), dpr), bopts);

    end

));

# Class(NewDPBench, DPBench, rec(
#     measureNtimes := 10,
#     remeasure := (self, c, opts) >> List([1..self.measureNtimes],
#         i -> CMeasure(c, opts)),

#     runHooks := [
#         meth(self, t, c, hentry, opts)
#             hentry.remeasure := self.remeasure(c, opts);
#             PrintLine("remeasure : ", hentry.remeasure);
#         end
#     ]
# ));

#opts := SpiralDefaults; opts2 := CopyFields(opts, rec(declareConstants := true));
#d := NewDPBench(rec(default:=opts,pullconst:=opts2), rec(timeBaseCases := false, verbosity := 0));
sampleDPBench := DPBench(rec(default := SpiralDefaults), rec(timeBaseCases := false, verbosity := 0));
sampleDPBench.transforms := [DFT(2), DFT(3), DFT(4)];


Class(OpenMP_POWERUnparser, paradigms.smp.OpenMP_UnparseMixin, packages.powerisa.power9.p9macro.POWER9Unparser);
Class(OpenMP_POWERUnparser_ParFor, paradigms.smp.OpenMP_UnparseMixin_ParFor, packages.powerisa.power9.p9macro.POWER9Unparser);


#############################################################################
####
##
#W  streams.gi                 ACE Package                        Greg Gamble
##
##  This file installs non-user functions used in interactions with streams.
##
#Y  Copyright (C) 2000  Centre for Discrete Mathematics and Computing
#Y                      Department of Information Technology & Electrical Eng.
#Y                      University of Queensland, Australia.
##

#############################################################################
####
##
#F  ACE_PRINT_AND_EVAL( <varname>, <expr> ) . . . print and evaluate a string
##
##  emulates a user typing at the `gap> ' prompt: `<varname> := <expr>;'  and
##  prints and returns the evaluation of <expr>; <varname> and <expr>  should
##  be strings.
##
InstallGlobalFunction(ACE_PRINT_AND_EVAL, function(varname, expr)

  Print("gap> ", varname, " := ", ReplacedString(expr, "\n ", "\n>"), ";\n");
  expr := EvalString(expr);
  Print(expr, "\n");
  return expr;
end);

#############################################################################
####
##
#F  ACE_READ_NEXT_LINE(<iostream>) . read complete line but never return fail
##
##  We know there is a complete line to be got; so we  wait  for  it,  before
##  returning.
##
InstallGlobalFunction(ACE_READ_NEXT_LINE, function(iostream)
  return ReadAllLine(iostream, true);
end);

#############################################################################
####
##
#F  FLUSH_ACE_STREAM_UNTIL(<iostream>, <infoLevelFlushed>, <infoLevelMyLine>,
##    <readline>, <IsMyLine>) . . . . . . flush a stream until a desired line
##
##  reads lines in iostream <iostream> via function  <readline>  and  `Info's
##  those lines at `InfoACELevel' <infoLevelFlushed> until a line <line>  for
##  which `<IsMyLine>(<line>) is `true'. The line <line> is then `Info'-ed at
##  `InfoACELevel' <infoLevelMyLine> and returned.
##
InstallGlobalFunction(FLUSH_ACE_STREAM_UNTIL,
function(iostream, infoLevelFlushed, infoLevelMyLine, readline, IsMyLine)
local line;

  line := readline(iostream);
  while not IsMyLine(line) do
    Info(InfoACE, infoLevelFlushed, Chomp(line));
    line := readline(iostream);
  od;
  if line <> fail and infoLevelMyLine < 10 then
    Info(InfoACE, infoLevelMyLine, Chomp(line));
  fi;
  return line;
end);

#############################################################################
####
##
#F  WRITE_LIST_TO_ACE_STREAM( <stream>, <list> ) . . . write to an ACE stream
##
##  writes the list <list> to the iostream <stream>, `Info's <list> following
##  a `ToACE> ' ``prompt'' at `InfoACE' level 4, returns `true' if successful
##  or `fail' otherwise.
##
InstallGlobalFunction(WRITE_LIST_TO_ACE_STREAM, function(stream, list)
local string;

  if not IsOutputTextStream(stream) and IsEndOfStream(stream) then
    Info(InfoACE + InfoWarning, 1, "Sorry. Process stream has died!");
    Info(InfoACE + InfoWarning, 1,
         "You might like to try using 'ACEResurrectProcess(<i>);'");
    return fail;
  fi;
  string := Concatenation( List(list, String) );
  Info(InfoACE, 4, "ToACE> ", string);
  return WriteLine(stream, string);
end);

#E  streams.gi  . . . . . . . . . . . . . . . . . . . . . . . . . . ends here


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

#
#################################
#
# CodegenStrategy Stages
#
# NOTE: these are wrapped in classes because their names are simpler in a list.
#################################

#
## _EnumBlocks
#
# Add a bbnum tag to each BB, enumerating them.
#
# changes the sums INPLACE. Has to appear before _ApplyCodegen
# to be effective.
Class(_EnumBlocks, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        EnumBlocks(sums, opts);

        return code;
    end,
));

#
## Apply Codegen
#
# convert SigmaSPL to Code using opts.codegen
#
Class(_ApplyCodegen, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        local X, Y;

        [X, Y] := _getXY(sums, opts);

        return opts.codegen(sums, StripList(Y), StripList(X), opts);
    end,
));

#
## _BlockSums
#
#
Class(_BlockSums, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        return BlockSums(code, opts);
    end,
));

#
## _ESReduce
#
#
Class(_ESReduce, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        return ESReduce(code, opts);
    end,
));

#
## _RemoveAssignAcc
#
#
Class(_RemoveAssignAcc, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        return RemoveAssignAcc(code);
    end
));

#
## __BlockUnroll
#
#
Class(__BlockUnroll, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        return BlockUnroll(code, opts);
    end,
));

#
## _DeclareHidden
#
#
Class(_DeclareHidden, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        return DeclareHidden(code);
    end,
));

#
## _FixedPointCode
#
#
Class(_FixedPointCode, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        if IsBound(opts.isFixedPoint) and opts.isFixedPoint then
            code := FixedPointCode(code, opts.bits, opts.fracbits);
        fi;

        return code;
    end,
));

#
## new legacy wrap
#
# temporaries on stack
# out of place arrays, global ptrs, malloced on 128b boundary
#
# format is that used in the new type, with all the new functions
#
Class(_NewLegacyWrap, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        local x, y, io, params, datas, sub, initsub, init, compute, alloc, free, timer, data, prog;

        # data from sums
        [x, y] := _getXY(sums, opts);
        io := When(x=y, [x], [y, x]);
        params := Set(Collect(sums, param));
        datas := Collect(sums, FDataOfs);

        # names
        sub := Cond(IsBound(opts.subName), opts.subName, "transform");
        initsub := Cond(IsBound(opts.subName), Concat("init_", opts.subName), "initz");

        #
        # code sections start here:
        #

        # generate the 'init' code.
        if IsBound(opts.generateInitFunc) and opts.generateInitFunc then
            init := func(TVoid, initsub, params, chain(
                List(datas, x -> SReduce(x.var.init, opts))
            ));
        else
            init := func(TVoid, initsub, params, code);
        fi;

        # wrap the 'compute'
        compute := func(TVoid, sub, Concatenation(io, params), code);

        # create the allocs/free
        alloc := skip();
        free := skip();

        # setup timer
        timer := skip();

        # generate data
        data := List(datas, x -> x.var);

        #
        # put pieces together
        #

        prog := program(
            decl(data, chain(
                init,
                compute,
                alloc,
                free,
                timer
            ))
        );

        return prog;
    end,
));

#
## _LegacyWrap
#
#
Class(_LegacyWrap, CodegenStrat, rec(
    __call__ := function(code, sums, opts)
        local x, y, params, datas, io, sub, initsub, prog;

        [x, y] := _getXY(sums, opts);

        params := Set(Collect(sums, param));
        datas := Collect(sums, FDataOfs);

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

        if IsBound(opts.generateInitFunc) and opts.generateInitFunc then
            prog := program(
                decl(List(datas, x->x.var),
                    chain(
                        func(TVoid, initsub, params, chain(List(datas,
                            x -> SReduce(x.var.init, opts)
                        ))),
                        code
                    )));
        else
            prog := program( func(TVoid, initsub, params, chain()), code);
        fi;

        prog.dimensions := sums.dims();

        return prog;
    end,
));

###
#
#
_BasicCodegenStrat := [
    _EnumBlocks,
    _ApplyCodegen,
    _ESReduce,
    _RemoveAssignAcc,
    __BlockUnroll,
    _DeclareHidden,
    _FixedPointCode
];

# these functions are in cgwrap.gi
_DefaultWrapStrat := [
    _DefaultWrapInitCompute,
    _DefaultWrapData,
    _DefaultWrapAlloc,
    _DefaultWrapTimer,
    _DefaultWrapVerify
];

_SlabWrapStrat := [
    _SlabInitCompute,
    _SlabAlloc,
    _SlabTimer,
    _SlabVerify
];

DefaultCodegenStrat := Concat(_BasicCodegenStrat, _DefaultWrapStrat);
SlabCodegenStrat := Concat(_BasicCodegenStrat, _SlabWrapStrat);

# a version where all sin/cos computation has been removed from the init function
# ::: useful for getting something running on the simulator.
SlabHackedCodegenStrat := Flat([_BasicCodegenStrat, _SlabWrapStrat, _SlabHackInit]);

#
## ApplyCodegenStrat
#
# applies the codegen strategy.
#
ApplyCodegenStrat := function(sums, opts)
    local code, s;

    # some default
    code := skip();

    # apply each function in the strategy in order
    # sums and opts do not change (hopefully), but code does.
    for s in opts.codegenStrat do
        code := s(code, sums, opts);
    od;

    return code;
end;


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

Class(loopc, loop_base, rec(
   __call__ := (self, loopvar, range, cnd, cmd) >> WithBases(self,
               rec(operations := CmdOps, cmd := cmd, var := loopvar, range := listRange(range), cnd := cnd)),
   rChildren := self >> [self.var, self.range, self.cnd, self.cmd],
   rSetChild := rSetChildFields("var", "range", "cnd", "cmd"),

   print := (self, i, si) >> Print(self.__name__, "(", self.var, ", ",
       self.range, ", ", self.cnd, ",\n", Blanks(i+si),
       self.cmd.print(i+si, si),
       Print("\n", Blanks(i), ")")),
   free := self >> self.cmd.free()
));

Class(creturn, throw);
Class(creturnCond, throw);

Class(isinf, AutoFoldExp, rec(
  ev := self >> self._ev(self.args).ev(),
  computeType := self >> TBool,
));

Class(isnan, AutoFoldExp, rec(
  ev := self >> self._ev(self.args).ev(),
  computeType := self >> TBool,
));


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

ImportAll(paradigms.scratchpad);

Class(ScratchX86CMContext, rec (
	getOpts := meth(arg)
		local opts, elem, lssize, nrsgmts, vlen, size, swp, globalUnrolling, 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);
		elem := ScratchpadGlobals.getOpts(lssize,nrsgmts,vlen,size,ttype,swp,globalUnrolling);

        	opts := CopyFields(elem);

		opts.unparser := CCMContextScratchUnparserProg;
		opts.codegen := CMContextScratchCodegen;

        	opts.profile.makeopts.CFLAGS := "-O2 -Wall -fomit-frame-pointer -msse4.1 -std=gnu99 -static";

        	opts.includes := [ "<include/omega64.h>" ];
        	Add(opts.includes, "\"scratchc.h\"");

		return opts;
	end,
));

Class(ScratchX86Globals, rec(
	getOpts := meth(arg)
		local opts, elem, lssize, nrsgmts, vlen, size, swp, globalUnrolling, 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);
		elem := ScratchpadGlobals.getOpts(When(swp,lssize/2,lssize),nrsgmts,vlen,size,ttype,swp,globalUnrolling);

        opts := CopyFields(elem);

        opts.swp_var := swp;

        opts.profile.name := "linux-x86-thread";

		opts.unparser := BarrierScratchUnparserProg;
		opts.codegen := SWPBarrierScratchCodegen;

        opts.register := (self, opts) >> "REG_thread";

        opts.profile.makeopts.CFLAGS := "-O2 -Wall -fomit-frame-pointer -msse4.1 -std=gnu99 -static -lpthread";

        opts.barrierCMD := (self, opts) >> "BARRIER";
        opts.initialization := (self, opts) >> "INITIAL";

        opts.includes := [];
        Add(opts.includes, "\"scratch_barrier.h\"");
		Add(opts.includes, "<include/omega64.h>");

		return opts;
	end,
));


%Terminals
    DollarSign ::= '$'
    Percent ::= '%'
    _
    a b c d e f g h i j k l m n o p q r s t u v w x y z
    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
%End

%Headers
    /.
        inline static int tokenKind[128] ={};

        static  bool init()
        {
            tokenKind['$'] = $sym_type::$prefix$DollarSign$suffix$;
            tokenKind['%'] = $sym_type::$prefix$Percent$suffix$;
            tokenKind['_'] = $sym_type::$prefix$_$suffix$;

            tokenKind['a'] = $sym_type::$prefix$a$suffix$;
            tokenKind['b'] = $sym_type::$prefix$b$suffix$;
            tokenKind['c'] = $sym_type::$prefix$c$suffix$;
            tokenKind['d'] = $sym_type::$prefix$d$suffix$;
            tokenKind['e'] = $sym_type::$prefix$e$suffix$;
            tokenKind['f'] = $sym_type::$prefix$f$suffix$;
            tokenKind['g'] = $sym_type::$prefix$g$suffix$;
            tokenKind['h'] = $sym_type::$prefix$h$suffix$;
            tokenKind['i'] = $sym_type::$prefix$i$suffix$;
            tokenKind['j'] = $sym_type::$prefix$j$suffix$;
            tokenKind['k'] = $sym_type::$prefix$k$suffix$;
            tokenKind['l'] = $sym_type::$prefix$l$suffix$;
            tokenKind['m'] = $sym_type::$prefix$m$suffix$;
            tokenKind['n'] = $sym_type::$prefix$n$suffix$;
            tokenKind['o'] = $sym_type::$prefix$o$suffix$;
            tokenKind['p'] = $sym_type::$prefix$p$suffix$;
            tokenKind['q'] = $sym_type::$prefix$q$suffix$;
            tokenKind['r'] = $sym_type::$prefix$r$suffix$;
            tokenKind['s'] = $sym_type::$prefix$s$suffix$;
            tokenKind['t'] = $sym_type::$prefix$t$suffix$;
            tokenKind['u'] = $sym_type::$prefix$u$suffix$;
            tokenKind['v'] = $sym_type::$prefix$v$suffix$;
            tokenKind['w'] = $sym_type::$prefix$w$suffix$;
            tokenKind['x'] = $sym_type::$prefix$x$suffix$;
            tokenKind['y'] = $sym_type::$prefix$y$suffix$;
            tokenKind['z'] = $sym_type::$prefix$z$suffix$;

            tokenKind['A'] = $sym_type::$prefix$A$suffix$;
            tokenKind['B'] = $sym_type::$prefix$B$suffix$;
            tokenKind['C'] = $sym_type::$prefix$C$suffix$;
            tokenKind['D'] = $sym_type::$prefix$D$suffix$;
            tokenKind['E'] = $sym_type::$prefix$E$suffix$;
            tokenKind['F'] = $sym_type::$prefix$F$suffix$;
            tokenKind['G'] = $sym_type::$prefix$G$suffix$;
            tokenKind['H'] = $sym_type::$prefix$H$suffix$;
            tokenKind['I'] = $sym_type::$prefix$I$suffix$;
            tokenKind['J'] = $sym_type::$prefix$J$suffix$;
            tokenKind['K'] = $sym_type::$prefix$K$suffix$;
            tokenKind['L'] = $sym_type::$prefix$L$suffix$;
            tokenKind['M'] = $sym_type::$prefix$M$suffix$;
            tokenKind['N'] = $sym_type::$prefix$N$suffix$;
            tokenKind['O'] = $sym_type::$prefix$O$suffix$;
            tokenKind['P'] = $sym_type::$prefix$P$suffix$;
            tokenKind['Q'] = $sym_type::$prefix$Q$suffix$;
            tokenKind['R'] = $sym_type::$prefix$R$suffix$;
            tokenKind['S'] = $sym_type::$prefix$S$suffix$;
            tokenKind['T'] = $sym_type::$prefix$T$suffix$;
            tokenKind['U'] = $sym_type::$prefix$U$suffix$;
            tokenKind['V'] = $sym_type::$prefix$V$suffix$;
            tokenKind['W'] = $sym_type::$prefix$W$suffix$;
            tokenKind['X'] = $sym_type::$prefix$X$suffix$;
            tokenKind['Y'] = $sym_type::$prefix$Y$suffix$;
            tokenKind['Z'] = $sym_type::$prefix$Z$suffix$;
            return true;
        };
     inline const static bool dd = init();
         int getKind(int c)
        {
            return (((c & 0xFFFFFF80) == 0) /* 0 <= c < 128? */ ? tokenKind[c] : 0);
        }

    ./
%End


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

Class(ScratchSumsGen, LegacySumsGen);


ReadByLines := function(name)
	local file, line, count;
	file := InputTextFile(name);
	count := 0;
	while true do
		line := ReadLine(file);
		if line = fail then
			break;
		fi;
		count := count + 1;
	od;
	CloseStream(file);
	return count;
end;

# With [http://www.ibiblio.org/pub/docs/misc/amnesty.txt amnesty.txt]
ReadByLines("amnesty.txt");
# 384


InstallGlobalFunction(MEMO_DiskCache,
function(memo, path)
  local dir, cache, type;

  # Directory for this function
  dir := Filename(Directory(path), memo!.funcname);

  # Make cache object
  cache := rec(memo := memo,  # memoised function
               dir := dir);  # directory for storing results
  # Objectify
  type := NewType(DictionariesFamily, MEMO_IsDiskCache);
  cache := Objectify(type, cache);

  return cache;
end);

InstallMethod(AddDictionary,
"for a memoisation disk cache and two objects",
[MEMO_IsDiskCache, IsObject, IsObject],
function(cache, key, val)
  local memo, filename, str, write, key_filename, key_str, metadata_filename,
        metadata_str;
  memo := cache!.memo;

  # Create directory if needed
  if not IsDirectoryPath(cache!.dir) then
    MEMO_CreateDirRecursively(cache!.dir);
  fi;

  # Get filename for storage
  filename := MEMO_KeyToFilename(cache, key, MEMO_OUT);
  Info(InfoMemoisation, 3, "Using filename ", filename);

  # Write to disk
  str := memo!.pickle(val);
  write := FileString(filename, str);
  if write = fail then
    Error("Memoisation: could not write result to ", filename);
    # user can "return;" and result will still be returned
  else
    Info(InfoMemoisation, 3, "Result stored in file");
  fi;

  # OPTION: storekey
  if memo!.storekey then
    key_filename := MEMO_KeyToFilename(cache, key, MEMO_KEY);
    key_str := memo!.pickle(key);
    FileString(key_filename, key_str);
    Info(InfoMemoisation, 3, "Key stored at ", key_filename);
  fi;

  # OPTION: metadata
  if memo!.metadata <> fail then
    metadata_filename := MEMO_KeyToFilename(cache, key, MEMO_META);
    metadata_str := memo!.metadata();
    FileString(metadata_filename, metadata_str);
    Info(InfoMemoisation, 3, "Metadata stored at ", metadata_filename);
  fi;

  # no return value
end);

InstallMethod(KnowsDictionary,
"for a memoisation disk cache and an object",
[MEMO_IsDiskCache, IsObject],
function(cache, key)
  local filename;
  filename := MEMO_KeyToFilename(cache, key, MEMO_OUT);
  return IsReadableFile(filename);
end);

InstallMethod(LookupDictionary,
"for a memoisation disk cache and an object",
[MEMO_IsDiskCache, IsObject],
function(cache, key)
  local memo, filename, key_filename, key_str, storedkey, str, val;
  memo := cache!.memo;

  # Get filename
  filename := MEMO_KeyToFilename(cache, key, MEMO_OUT);
  Info(InfoMemoisation, 3, "Using filename ", filename);
  if not IsReadableFile(filename) then
    # We shouldn't normally get here, as we usually check KnowsDictionary first
    Info(InfoMemoisation, 1, "File ", filename, " not readable");
    return fail;
  fi;

  # OPTION: storekey
  if memo!.storekey then
    key_filename := MEMO_KeyToFilename(cache, key, MEMO_KEY);
    key_str := StringFile(key_filename);
    storedkey := memo!.unpickle(key_str);
    # check if key still matches
    if key <> storedkey then
      ErrorNoReturn("Hash collision: <key> does not match <storedkey>");
    fi;
    Info(InfoMemoisation, 3, "Key matches ", key_filename);
  fi;

  # OPTION: unhash
  if memo!.unhash <> fail then
    # unhash and check if key still matches
    storedkey := MEMO_FilenameToKey(cache, filename);
    if key <> storedkey then
      ErrorNoReturn("Hash collision: <key> does not match <storedkey>");
    fi;
  fi;

  # Load result
  str := StringFile(filename);
  Info(InfoMemoisation, 4, "Got ", Length(str), " bytes from file");
  val := memo!.unpickle(str);

  return val;
end);

InstallMethod(MEMO_ClearCache,
"for a memoisation disk cache",
[MEMO_IsDiskCache],
function(cache)
  local dir, file, ext, path, result;
  dir := cache!.dir;
  if not IsDirectoryPath(dir) then
    return true;
  fi;
  for file in DirectoryContents(dir) do
    for ext in [MEMO_OUT, MEMO_KEY, MEMO_META] do
      if EndsWith(file, ext) then
        path := Filename(Directory(dir), file);
        if RemoveFile(path) <> true then
          Info(InfoMemoisation, 1, "Failed to delete ", path);
        fi;
      fi;
    od;
  od;
  result := RemoveDir(dir);
  if result = true then
    return true;
  fi;
  return false;
end);

InstallGlobalFunction(MEMO_KeyToFilename,
function(cache, key, ext)
  local h, fname;
  h := cache!.memo!.hash(key);
  fname := Concatenation(h, ext);
  return Filename(Directory(cache!.dir), fname);
end);

InstallGlobalFunction(MEMO_FilenameToKey,
function(cache, filename)
  local pos, h;
  if StartsWith(filename, cache!.dir) then  # remove directory
    filename := filename{[Length(cache!.dir) + 2 .. Length(filename)]};
  fi;
  # Remove extension
  pos := Remove(Positions(filename, '.'));  # position of final dot
  h := filename{[1 .. pos - 1]};
  return cache!.memo!.unhash(h);
end);

InstallGlobalFunction(MEMO_CreateDirRecursively,
function(dir)
  # Borrowed from PackageManager
  local path, newdir, i, res;
  path := SplitString(dir, "/");
  newdir := "";
  for i in [1 .. Length(path)] do
    Append(newdir, path[i]);
    Append(newdir, "/");
    if not IsDirectoryPath(newdir) then
      res := CreateDir(newdir);
      if res <> true then
        return fail;
      fi;
    fi;
  od;
  return true;
end);


# Several ways to do it
"Hello world!";

Print("Hello world!\n"); # No EOL appended

Display("Hello world!");

f := OutputTextUser();
WriteLine(f, "Hello world!\n");
CloseStream(f);


--/**
-- * <copyright>
-- *
-- * Copyright (c) 2010, 2009 IBM Corporation and others.
-- * All rights reserved.   This program and the accompanying materials
-- * are made available under the terms of the Eclipse Public License v2.0
-- * which accompanies this distribution, and is available at
-- * http://www.eclipse.org/legal/epl-v20.html
-- *
-- * Contributors:
-- *   See (or edit) Notice Declaration below
-- *
-- * </copyright>
-- */
--
-- The Complete OCL Lexer
--

%options escape=$
%options la=2
%options fp=OCLLexer,prefix=Char_
%options single-productions
%options noserialize
%options package=org.eclipse.ocl.xtext.essentialocl.parser
%options template=../lpg/LexerTemplateF.gi
%options filter=OCLKWLexer.gi
%options export_terminals=("OCLParsersym.java", "TK_")
%options include_directory="../lpg"

%Import
	EssentialOCLLexer.gi
%End

%Define

	--
	-- Definition of macro used in the included file LexerBasicMap.g
	-- We redefine that one defined by EssentialOCLLexer
	--
	$kw_lexer_class /.OCLKWLexer./

%End

%Notice
	/./**
 * Complete OCL Lexer
 * <copyright>
 *
 * Copyright (c) 2010, 2009 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 *   E.D.Willink - Bug 292112, 292594
 *   Adolfo Sanchez-Barbudo Herrera (Open Canarias) - LPG v 2.0.17 adoption (242153)
 *   Adolfo Sanchez-Barbudo Herrera (Open Canarias) - Introducing new LPG templates (299396)
 $copyright_contributions
 *******************************************************************************/
	./
%End

%Export
	AT
	CARET
	CARETCARET
	QUESTIONMARK
%End

%Rules
	Token ::= '@'
		/.$BeginAction
					makeToken($_AT);
		  $EndAction
		./

	Token ::= '^'
		/.$BeginAction
					makeToken($_CARET);
		  $EndAction
		./

	Token ::= '^' '^'
		/.$BeginAction
					makeToken($_CARETCARET);
		  $EndAction
		./

	Token ::= '?'
		/.$BeginAction
					makeToken($_QUESTIONMARK);
		  $EndAction
		./
%End


# Return the time passed in last function
time;


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

Class(swp_loop, loop);

Class(dma_signal, ExpCommand);
Class(dma_wait, ExpCommand);

Class(cpu_signal, ExpCommand);
Class(cpu_wait, ExpCommand);

Class(barrier_cmd, ExpCommand);
Class(nop_cmd, ExpCommand);

Class(dma_fence, skip);

Class(dma_transfer, Command, rec(
    isAssign := false, # transfers are not assignments, since they take >2 params
    __call__ := (self, loc, exp, size) >> WithBases(self, rec(
        operations := CmdOps,
        loc := toAssignTarget(loc),
                exp := toExpArg(exp),
        size := toExpArg(size)
   )),

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

   print := (self,i,is) >> let(name := Cond(IsBound(self.isCompute) and self.isCompute,
                                            gap.colors.DarkYellow(self.__name__),
                                            IsBound(self.isLoad) and self.isLoad,
                                            gap.colors.DarkRed(self.__name__),
                                            IsBound(self.isStore) and self.isStore,
                                            gap.colors.DarkGreen(self.__name__),
                                            self.__name__),
                                Print(name, "(", self.loc, ", ", self.exp, ", ", self.size, ")"))
));

Class(dma_load, dma_transfer);
Class(dma_store, dma_transfer);

# wrapper for dma_xfer, includes the dma transfer size.
# dma_size( dma_xfer(a,b), c);
Class(dma_size, ExpCommand);
Class(par_exec, chain);

#The next two dma_jump and cpu_jump are used only in the CM stuff in ScratchX86
Class(dma_jump,ExpCommand);
Class(cpu_jump,ExpCommand);

#For thread registration using the Fast Barrier
Class(register,ExpCommand);
Class(initialization, ExpCommand);
Class(add_buffer,skip);


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

Class(ABuf, AGenericTag, rec(
    __call__ := (self, bs) >> WithBases(self, rec(bs:=bs)),
    print := (self) >> When(IsBound(self.bs), Print(self.name, "(", self.bs, ")"), Print(self.name)),
#D    isBuffer := true,
    operations := Inherit(PrintOps, rec(\= := (self, other) >> ObjId(other) = ObjId(self) and (Same(other, self) or IsBound(self.bs) and IsBound(other.bs) and self.bs = other.bs)))
));

Class(AVecMem, AVec, rec(
    __call__ := (self, v) >> WithBases(self, rec(v:=v)),
    print := (self) >> When(IsBound(self.v), Print(self.name, "(", self.v, ")"), Print(self.name)),
#D    isMem := true,
    operations := Inherit(PrintOps, rec(\= := (self, other) >> ObjId(other) = ObjId(self) and (Same(other, self) or IsBound(self.v) and IsBound(other.v) and self.v = other.v)))
));

Class(AVecMemL, AVec, rec(
    __call__ := (self, v) >> WithBases(self, rec(v:=v)),
    print := (self) >> When(IsBound(self.v), Print(self.name, "(", self.v, ")"), Print(self.name)),
#D    isMemL := true,
    operations := Inherit(PrintOps, rec(\= := (self, other) >> ObjId(other) = ObjId(self) and (Same(other, self) or IsBound(self.v) and IsBound(other.v) and self.v = other.v)))
));

Class(AVecMemR, AVec, rec(
    __call__ := (self, v) >> WithBases(self, rec(v:=v)),
    print := (self) >> When(IsBound(self.v), Print(self.name, "(", self.v, ")"), Print(self.name)),
#D    isMemR := true,
    operations := Inherit(PrintOps, rec(\= := (self, other) >> ObjId(other) = ObjId(self) and (Same(other, self) or IsBound(self.v) and IsBound(other.v) and self.v = other.v)))
));


#############################################################################
##
##                         equivalent mappings
##  named-pipes.gi
##                                                          Sergio Siccha
##
##  Copyright 2017 by the authors.
##  This file is free software, see license file.
##
##  Provides a means to communicate with another process
##  via a named pipe.
##
#############################################################################

## Recognize whether we're running HPCGAP
if IsBound( HPCGAP ) then
    ## Transfer IO record to the public region.
    MakeImmutable( IO );
fi;

## Opens a pipe for both read and write access
## TODO change this later to include parameter for RDONLY or WRONLY
PipeOpen := function( pipeFilename )
    local pipe;
    ## Use `mkfifo pipe-test` to create named pipe
    pipe := IO_open( pipeFilename, IO.O_RDWR, 0 );
    if pipe = fail then
        Error( "Could not open ", pipeFilename );
    fi;
    ## 2^16 corresponds to the default buffer size
    pipe := IO_WrapFD( pipe, 2^16, 2^16 );
    return pipe;
end;

##################################################
# Operation NamedPipeHandle
# Input:
#   pipeFilename - string
#   options - an options record
# Filters:
#   IsString, IsRecord
#
# Output:
#   newPipeHandle
##################################################
InstallMethod( NamedPipeHandle,
"for a pipe-filename string",
[ IsString ],
function( pipeFilename )
    return NamedPipeHandle( pipeFilename, rec() );
end );

##################################################
# Operation NamedPipeHandle
# Input:
#   pipeFilename - string
#   options - an options record
# Filters:
#   IsString, IsRecord
#
# Output:
#   newPipeHandle
##################################################
InstallMethod( NamedPipeHandle,
"for a pipe-filename string and an options record",
[ IsString, IsRecord ],
function( pipeFilename, options )
    local pipe, newPipeHandle, type;
    pipe := PipeOpen( pipeFilename );
    newPipeHandle := rec( pipe := pipe );
    type := NamedPipeHandleType;
    Objectify( type, newPipeHandle );
    return newPipeHandle;
end );

##################################################
# Operation ReadLine
# Input:
#   namedPipeHandle
# Filters:
#   IsNamedPipeHandle
#
# Output:
#   string
##################################################
InstallMethod( ReadLine,
"for a named-pipe-handle",
[ IsNamedPipeHandle ],
function( namedPipeHandle )
    local string;
    string := IO_ReadLine( namedPipeHandle!.pipe );
    ## Ignore lines that start with a '#'
    if string[1] = '#' then
        Info( InfoWarning, 1, "Ignoring input starting with '#'." );
        return ReadLine( namedPipeHandle );
    fi;
    ## Ctrl-D character was passed into pipe
    if string[1] = '\004' and Length( string ) = 2 then
        ## TODO propagate '\004' signal?
        IO_close( namedPipeHandle!.pipe );
        return fail;
    fi;
    return string;
end );
#IO_ReadLine( pipe );
#IO_Flush


IsExistingFile("input.txt");
IsDirectoryPath("docs");
IsExistingFile("/input.txt");
IsDirectoryPath("/docs");


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

Class(CMScratchMainCodegen, ScratchMainCodegen, rec(
    genMain := (self, opts, sub, func1, func2, xy, params) >>
        func(TVoid, sub, Concatenation(xy, params), par_exec(
            ApplyFunc(call, Flat(Concatenation([func1],[false]))),
            ApplyFunc(call, Flat(Concatenation([func2], xy)))
        ))
));

Class(CMContextScratchCodegen, ScratchCodegen, rec(
    CPUCodegen := CPUCodegen,
    DMACodegen := DMACodegen,
    MainCodegen := CMScratchMainCodegen,
    Formula := meth(self, o, y, x, opts)
        local main, initfunc, cpufunc, dmafunc, auxfunc, prog, params, sub, initsub, memvars, scratchvars, v, io, loadvar, storevar, v, substrec, svars, initcode, datas, dvars, dv, tag;

        datas := Collect(o, FDataOfs);
        params := Set(Concatenation(Collect(o, param), Filtered(Collect(o, var), IsParallelLoopIndex)));
        sub := Cond(IsBound(opts.subName), opts.subName, "transform");

        initsub := Cond(IsBound(opts.subName), Concat("init_", opts.subName), "init");
        if IsBound(opts.generateInitFunc) and opts.generateInitFunc then
            initcode := chain(List(datas, x -> SReduce(x.var.init, opts)));
            initfunc := func(TVoid, initsub, params :: Set(Collect(initcode, param)), initcode);
        else
            initfunc := func(TVoid, initsub, params, chain());
        fi;

        dvars := List(datas, x->x.var);
        for dv in dvars do
            dv.t.qualifiers := [opts.romModifier];
        od;

        self.DMACodegen.loadbuffers := Set([]);
        self.DMACodegen.storebuffers := Set([]);
        self.DMACodegen.membuffers := Set([]);
        dmafunc := self.DMACodegen.Formula(o, y, x, opts);
        self.DMACodegen.membuffers := Set(self.DMACodegen.membuffers);
        SubtractSet(self.DMACodegen.membuffers, Set([x, y]));
        for v in self.DMACodegen.membuffers do v.t.qualifiers := [opts.memModifier]; od;

        self.CPUCodegen.loadbuffers := Set([]);
        self.CPUCodegen.storebuffers := Set([]);
        cpufunc := self.CPUCodegen.Formula(o, y, x, opts);

        memvars := Set(Concat(
            Filtered(Flat(List(Collect(cpufunc, decl), i->i.vars)), j->IsBound(j.t.qualifiers) and opts.memModifier in j.t.qualifiers),
            self.DMACodegen.membuffers));
        scratchvars := Set(Filtered(Flat(List(Collect(cpufunc, decl), i->i.vars)), j->IsBound(j.t.qualifiers) and opts.scratchModifier in j.t.qualifiers));

        tag := opts.tags[1];

        loadvar := var.fresh_t("S", TArray(opts.XType.t, Cond(tag.isRegCx, 2 * tag.size, tag.size)));
        loadvar.t.qualifiers := [opts.scratchModifier];
        storevar := var.fresh_t("S", TArray(opts.XType.t, Cond(tag.isRegCx, 2 * tag.size, tag.size)));
        storevar.t.qualifiers := [opts.scratchModifier];

        substrec := rec();
        for v in self.CPUCodegen.loadbuffers do substrec.(v.id) := loadvar; od;
        for v in self.CPUCodegen.storebuffers do substrec.(v.id) := storevar; od;
        for v in self.DMACodegen.loadbuffers do substrec.(v.id) := loadvar; od;
        for v in self.DMACodegen.storebuffers do substrec.(v.id) := storevar; od;
        cpufunc :=  SubstVars(cpufunc, substrec);
        dmafunc :=  SubstVars(dmafunc, substrec);

        cpufunc := SubstTopDown(cpufunc, @(1, decl), e->decl(Filtered(@(1).val.vars, i->not i in Concat(memvars, scratchvars, [loadvar, storevar])), @(1).val.cmd));
        dmafunc := SubstTopDown(dmafunc, @(1, decl), e->decl(Filtered(@(1).val.vars, i->not i in Concat(memvars, scratchvars, [loadvar, storevar])), @(1).val.cmd));

        [cpufunc, dmafunc, svars] := When(opts.swp,_double_buffer(cpufunc, dmafunc, [loadvar, storevar], opts),[cpufunc,dmafunc,[loadvar,storevar]]);

        x.t.qualifiers := Set(x.t.qualifiers);
        y.t.qualifiers := Set(y.t.qualifiers);

        main := self.MainCodegen.genMain(opts, sub, func(TVoid,"DMA_jump",[ false ],dma_jump(false)), dmafunc, [y,x], params);
        prog := program(
            decl(Concat(Set(memvars), svars, dvars), chain(
                initfunc,
                cpufunc,
		dmafunc,
                main
        )));

        prog.dimensions := o.dims();
        return prog;
    end
));


#
# Memoisation: Shared persistent memoisation library for GAP and other systems
#
# Implementations
#

InstallGlobalFunction(MemoisedFunction,
function(func, args...)
  local opts, funcname, key, storekey, pickle, unpickle, hash, unhash, metadata,
        rnam, memo, type, pos, typestring, path, cachetypes, cachetype;

  # Default options
  opts := rec(cache := MEMO_DefaultCache,
              funcname := NameFunction(func),
              key := {args...} -> args,  # default: use list of args as key
              storekey := false,
              pickle := IO_Pickle,
              unpickle := IO_Unpickle,
              hash := MEMO_Hash,
              unhash := fail,  # TODO: iterate through keys (storekey or unhash)
              metadata := fail);

  # Process optional argument
  if Length(args) = 1 then
    if not IsRecord(args[1]) then
      ErrorNoReturn("Memoisation: MemoisedFunction: ",
                    "2nd argument <opts> should be a record");
    fi;
    # Import user options
    for rnam in RecNames(args[1]) do
      opts.(rnam) := args[1].(rnam);
    od;
  elif Length(args) > 1 then
    ErrorNoReturn("Memoisation: MemoisedFunction takes 1 or 2 arguments, not ",
                  Length(args) + 1);
  fi;

  # Checks
  if opts.funcname = "unknown" then
    ErrorNoReturn("Memoisation: memoised function <func> has no name,\n",
                  "and no funcname was specified");
  fi;

  # Make the record
  memo := rec(
               func := func,
               funcname := opts.funcname,
               key := opts.key,
               storekey := opts.storekey,
               pickle := opts.pickle,
               unpickle := opts.unpickle,
               hash := opts.hash,
               unhash := opts.unhash,
               metadata := opts.metadata
             );

  # Objectify
  type := NewType(FunctionsFamily, IsMemoisedFunction);
  memo := Objectify(type, memo);

  # Determine which backend to use
  pos := PositionSublist(opts.cache, "://");
  if pos = fail then  # no backend specified: use disk
    typestring := "file";
    path := opts.cache;
  else
    typestring := opts.cache{[1 .. pos-1]};
    path := opts.cache{[pos+3 .. Length(opts.cache)]};
  fi;
  cachetypes := rec(file := MEMO_DiskCache, mongodb := MEMO_MongoDBCache);
  if not typestring in RecNames(cachetypes) then
    ErrorNoReturn("Memoisation: MemoisedFunction: <cache> cannot start with \"",
                  typestring, "://\"");
  fi;
  cachetype := cachetypes.(typestring);

  # Create backend
  memo!.cache := cachetype(memo, path);

  return memo;
end);

InstallMethod(CallFuncList,
"for a memoised function",
[IsMemoisedFunction, IsList],
function(memo, args)
  local key, val;

  # In case IO_Pickle was recently interrupted
  if memo!.pickle = IO_Pickle or memo!.unpickle = IO_Unpickle then
    IO_ClearPickleCache();
  fi;

  # Compute key
  key := CallFuncList(memo!.key, args);
  Info(InfoMemoisation, 2, "Memo key: ", key);

  # Search in cache
  if KnowsDictionary(memo!.cache, key) then
    # Retrieve cached result
    Info(InfoMemoisation, 2, "Key known!  Loading result from cache...");
    val := LookupDictionary(memo!.cache, key);
  else
    # Compute and store result
    Info(InfoMemoisation, 2, "Key unknown.  Computing result...");
    val := CallFuncList(memo!.func, args);
    AddDictionary(memo!.cache, key, val);
  fi;

  # Set attribute/property
  if Size(args) = 1 and
     (IsAttribute(memo!.func) or IsProperty(memo!.func)) and
     not Tester(memo!.func)(args[1]) then
    Info(InfoMemoisation, 4, "Setting attribute ", NameFunction(memo!.func));
    Setter(memo!.func)(args[1], val);
  fi;

  return val;
end);

InstallMethod(ClearMemoisedFunction,
"for a memoised function",
[IsMemoisedFunction],
function(memo)
  return MEMO_ClearCache(memo!.cache);
end);

InstallMethod(ViewObj,
"for a memoised function",
[IsMemoisedFunction],
function(memo)
  Print("<memoised ");
  ViewObj(memo!.func);
  Print(">");
end);

InstallMethod(PrintObj,
"for a memoised function",
[IsMemoisedFunction],
function(memo)
  Print("MemoisedFunction(\n");
  PrintObj(memo!.func);
  Print(",\nrec(funcname := \"", memo!.funcname, "\") )");
end);

for delegated_function in [NamesLocalVariablesFunction,
                           NumberArgumentsFunction] do
  InstallMethod(delegated_function,
                "for a memoised function",
                [IsMemoisedFunction],
                memo -> delegated_function(memo!.func));
od;


#
# JupyterKernel: Jupyter kernel using ZeroMQ
#
# Implementations
#

# This is a bit ugly: The global variable _KERNEL is assigned to
# the jupyter kernel object at so that we can use it from everywhere.
_KERNEL := "";

## This is plainly wrong, it just reessembles the behaviour of
## `UPDATE_STAT` in newer GAP version we need here.
if not IsBound( UPDATE_STAT ) then
    BindGlobal( "UPDATE_STAT",
      function( string, value )
        time := value;
    end );
fi;

BindConstant( "JUPYTER_KERNEL_MODE_CONTROL", 1 );
BindConstant( "JUPYTER_KERNEL_MODE_EXEC", 2 );

InstallGlobalFunction( JUPYTER_LogProtocol,
function(filename)
    _KERNEL!.ProtocolLog := OutputTextFile(filename, false);
    SetPrintFormattingStatus(_KERNEL!.ProtocolLog, false);
end);

InstallGlobalFunction( JUPYTER_UnlogProtocol,
function()
    local tmp;
    # In case `CloseStream` causes messages to be printed
    tmp := _KERNEL!.ProtocolLog;
    Unbind(_KERNEL!.ProtocolLog);
    CloseStream(tmp);
end);

InstallGlobalFunction( NewJupyterKernel,
function(conf)
    local pid, address, kernel, poll, msg, status, res;

    address := Concatenation(conf.transport, "://", conf.ip, ":");
    kernel := rec( config := Immutable(conf)
                 , Username := "username"
                 , ProtocolVersion := "5.3"
                 , ZmqIdentity := HexStringUUID( RandomUUID() )
                 , SessionKey := conf.key
                 , SessionID := ""
                 , ExecutionCount := 0);

    kernel.MsgHandlers := rec( kernel_info_request := function(msg)
                                 kernel!.SessionID := msg.header.session;
                                 return JupyterMsg( kernel
                                                  , "kernel_info_reply"
                                                  , msg.header
                                                  , rec( protocol_version := kernel!.ProtocolVersion
                                                       , implementation := "GAP"
                                                       , implementation_version := GAPInfo.PackagesInfo.jupyterkernel[1].Version
                                                       , language_info := rec( name := "GAP 4"
                                                                             , version := GAPInfo.Version
                                                                             , mimetype := "text/x-gap"
                                                                             , file_extension := ".g"
                                                                             , pygments_lexer := "gap"
                                                                             , codemirror_mode := "gap"
                                                                             , nbconvert_exporter := "" )
                                                       , banner := Concatenation( "GAP Jupyter kernel ", GAPInfo.PackagesInfo.jupyterkernel[1].Version, "\n",
                                                                                "Running on GAP ", GAPInfo.BuildVersion, "\n")
                                                       , help_links := [ rec( text := "GAP website", url := "https://www.gap-system.org/")
                                                                       , rec( text := "GAP documentation", url := "https://www.gap-system.org/Doc/doc.html")
                                                                       , rec( text := "GAP tutorial", url := "https://www.gap-system.org/Manuals/doc/chap0.html")
                                                                       , rec( text := "GAP reference", url := "https://www.gap-system.org/Manuals/doc/ref/chap0.html") ]
                                                       , status := "ok" )
                                                  , rec() );
                               end,

                               history_request := function(msg)
                                   msg.header.msg_type := "history_reply";
                                   msg.content := rec( history := [] );
                               end,

                               execute_request := function(msg)
                                   local publ, res, rep, r, str, data, metadata, t;

                                   JupyterMsgSend(kernel, kernel!.IOPub, JupyterMsg( kernel
                                                                       , "execute_input"
                                                                       , msg.header
                                                                       , rec( code := msg.content.code
                                                                            , execution_count := kernel!.ExecutionCount )
                                                                       , rec() ) );
                                   str := InputTextString(msg.content.code);

                                   # READ_ALL_COMMANDS was changed from 4.10. We make
                                   # JupyterKernel compatible for the time being (until
                                   # 4.10 is released at least)
                                   t := NanosecondsSinceEpoch();
                                   if CompareVersionNumbers(GAPInfo.Version, "4.10") then
                                       res := READ_ALL_COMMANDS(str, false, false, IdFunc);
                                   else
                                       res := READ_ALL_COMMANDS(str, false);
                                   fi;
                                   # This is probably supremely naughty; we overwrite GAP's
                                   # global time variable
                                   UPDATE_STAT( "time", QuoInt((NanosecondsSinceEpoch() - t), 1000000) );

                                   # Flush StdOut...
                                   Print("\c");
                                   for r in res do
                                       if r[1] = true then
                                           kernel!.ExecutionCount := kernel!.ExecutionCount + 1;

                                           # r[2] contains the result, r[3] is true if a dual semicolon was parsed
                                           if IsBound(r[2]) and r[3] = false then
                                               # FIXME: This is probably doable slightly more nicely
                                               rep := JupyterRender(r[2]);
                                               metadata := JupyterRenderableMetadata(rep);
                                               data := JupyterRenderableData(rep);
                                               # Only send a result message when there is a result
                                               # value
                                               # publ.execution_count := kernel!.ExecutionCount;
                                               JupyterMsgSend(kernel, kernel!.IOPub, JupyterMsg( kernel
                                                                                , "execute_result"
                                                                                , msg.header
                                                                                , rec( transient := rec()
                                                                                , data := data
                                                                                , metadata := metadata
                                                                                , execution_count := kernel!.ExecutionCount )
                                                                                , rec() ) );
                                           fi;
                                       fi;
                                   od;
                                   publ := JupyterMsg( kernel
                                                     , "execute_reply"
                                                     , msg.header
                                                     , rec( status := "ok"
                                                          , execution_count := kernel!.ExecutionCount )
                                                     , rec() );
                                   return publ;
                               end,

                               inspect_request := function(msg)
                                   return JupyterMsg( kernel
                                                    , "inspect_reply"
                                                    , msg.header
                                                    , JUPYTER_Inspect( msg.content.code
                                                                     , msg.content.cursor_pos )
                                                    , rec() );
                               end,

                               complete_request := function(msg)
                                   return JupyterMsg( kernel
                                                    , "complete_reply"
                                                    , msg.header
                                                    , JUPYTER_Complete( msg.content.code
                                                                      , msg.content.cursor_pos )
                                                    , rec() );
                               end,

                               history_request := function(msg)
                                   return JupyterMsg( kernel
                                                    , "history_reply"
                                                    , msg.header
                                                    , rec( history := [] )
                                                    , rec() );
                               end,

                               is_complete_request := function(msg)
                                   return JupyterMsg( kernel
                                                    , "is_complete_reply"
                                                    , msg.header
                                                    , rec( status := "complete" )
                                                    , rec() );
                               end,

                               comm_open := function(msg)
                                   return JupyterMsg( kernel
                                                    , "comm_open_reply"
                                                    , msg.header
                                                    , rec( status := "ok" )
                                                    , rec() );
                               end,

                               comm_info_request := function(msg)
                                   return JupyterMsg( kernel
                                                    , "comm_info_reply"
                                                    , msg.header
                                                    , rec( comms := rec(), status := "ok" )
                                                    , rec() );
                               end,

                               interrupt_request := function(msg)
                                   local status;
                                   # This is SIGINT
                                   status := IO_kill(pid, 2);
                                   return JupyterMsg( kernel
                                                    , "interrupt_reply"
                                                    , msg.header
                                                    , rec()
                                                    , rec() );

                               end,
                               shutdown_request := function(msg)
                                   kernel!.quitting := true;
                                   return JupyterMsg( kernel
                                                 , "shutdown_reply"
                                                 , msg.header
                                                 , rec( restart := msg.content.restart )
                                                 , rec() );
                               end );

    kernel.SignalBusy := function()
        JupyterMsgSend( kernel, kernel!.IOPub
                      , JupyterMsg( kernel
                                  , "status"
                                  , kernel!.CurrentMsg
                                  , rec( execution_state := "busy" )
                                  , rec() ) );
    end;
    kernel.SignalIdle := function()
        JupyterMsgSend( kernel, kernel!.IOPub
                      , JupyterMsg( kernel
                                  , "status"
                                  , kernel!.CurrentMsg
                                  , rec( execution_state := "idle" )
                                  , rec() ) );
    end;

    kernel.HandleShellMsg := function(msg)
        local hdl_dict, f, t, reply;

        # We store the currently processed
        # message header, because we need it
        # for replies
        kernel!.CurrentMsg := msg.header;

        kernel!.SignalBusy();
        t := msg.header.msg_type;
        if IsBound(kernel!.MsgHandlers.(t)) then
            # Currently we send the "reply" to each "request" on the Shell socket
            # here. We might opt to move the sending into the handler functions,
            # since at least "execute" has to send more than one message anyway
            JupyterMsgSend(kernel, kernel!.Shell, kernel!.MsgHandlers.(t)(msg) );

            kernel!.SignalIdle();
            return true;
        else
            Print("unhandled message type: ", msg.header.msg_type, "\n");
            kernel!.SignalIdle();
            return fail;
        fi;

    end;

    kernel.HandleControlMsg := function(msg)
        local hdl_dict, f, t, reply;

        kernel!.CurrentMsg := msg.header;

        t := msg.header.msg_type;
        if IsBound(kernel!.MsgHandlers.(t)) then
            if t in [ "interrupt_request", "shutdown_request" ] then
                JupyterMsgSend(kernel, kernel!.Control, kernel!.MsgHandlers.(t)(msg) );
            fi;
            return true;
        fi;

    end;

    _KERNEL := kernel;

    # This should happen in "Run" somehow, as currently the creation
    # of a Jupyter Kernel breaks the running GAP session, taking
    # every hope of debugging the kernel
    pid := IO_fork();
    if pid = fail then
        return fail;
    elif pid > 0 then # we are the parent and do heartbeat and control messages
        kernel.mode := JUPYTER_KERNEL_MODE_CONTROL;
        kernel.HB := ZmqRouterSocket( Concatenation(address, String(conf.hb_port) ) );
        kernel.Control := ZmqRouterSocket( Concatenation(address, String(conf.control_port) )
                                         , kernel!.ZmqIdentity);
        kernel.quitting := false;
        kernel.Loop := function()
            local topoll, poll, i, msg, res;
            topoll := [ kernel!.HB, kernel!.Control ];
            while true do
                poll := ZmqPoll( topoll, [], 5000 );
                if 1 in poll then
                    msg := ZmqReceiveList(kernel!.HB);
                    ZmqSend(kernel!.HB, msg);
                fi;
                if 2 in poll then
                    msg := JupyterMsgRecv(kernel, kernel!.Control);
                    res := kernel!.HandleControlMsg(msg);
                    if res = fail then
                        Print("failed to handle message\n");
                    fi;
                fi;
                if kernel!.quitting then
                    IO_kill(pid, 3);
                    status := IO_WaitPid(pid, true);
                    QUIT_GAP(0);
                fi;
                # Check whether child has gone away
                status := IO_WaitPid(pid, false);
                if IsRecord(status) then
                    # TODO find out what these statuses mean
                    if status.pid = pid and status.status in [ 3, 9, 15, 131 ] then
                        QUIT_GAP(0);
                    fi;
                fi;
            od;
        end;
    else
        kernel.mode  := JUPYTER_KERNEL_MODE_EXEC; # Handler
        kernel.IOPub := ZmqPublisherSocket( Concatenation(address, String(conf.iopub_port))
                                          , kernel!.ZmqIdentity);
        kernel.Shell := ZmqDealerSocket( Concatenation(address, String(conf.shell_port))
                                       , kernel!.ZmqIdentity);
        kernel.StdIn := ZmqRouterSocket( Concatenation(address, String(conf.stdin_port))
                                       , kernel!.ZmqIdentity);

        # TODO: This is of course still hacky, but better than before
        kernel!.StdOut := OutputStreamZmq(kernel, kernel!.IOPub);
        kernel!.StdErr := OutputStreamZmq(kernel, kernel!.IOPub, "stderr");
        # TODO: Hack to be able to change ERROR_OUTPUT.
        MakeReadWriteGlobal("ERROR_OUTPUT");
        ERROR_OUTPUT := kernel!.StdErr;
        MakeReadOnlyGlobal("ERROR_OUTPUT");
        OutputLogTo(kernel!.StdOut);

        # Jupyter Heartbeat and Control channel is handled by a fork'ed GAP process
        # (yes, really, its better than starting a separate thread, because it
        # doesn't need special pthread code downside is that it doesn't work on
        # cygwin, of course, but maybe we could just ExecuteProcess on windows, or
        # wait for bash on windows to become popular enoug.
        kernel.Loop := function()
            # To catch SIGINT when the kernel is idle
            while true do
                CALL_WITH_CATCH(function()
                                   local topoll, poll, i, msg, res;

                                   topoll := [ kernel!.Shell, kernel!.StdIn ];
                                   while true do
                                       poll := ZmqPoll(topoll, [], 5000);
                                       if 1 in poll then
                                           msg := JupyterMsgRecv(kernel, topoll[1]);
                                           res := kernel!.HandleShellMsg(msg);
                                           if res = fail then
                                               Print("failed to handle message\n");
                                           fi;
                                       fi;
                                       if 2 in poll then
                                           msg := ZmqReceiveList(topoll[2]);
                                       fi;
                                   od;
                               end, []);
            od;
        end;
    fi;

    Objectify(GAPJupyterKernelType, kernel);
    return kernel;
end);

InstallMethod( ViewString
             , "for Jupyter kernels"
             , [ IsGAPJupyterKernel ]
             , x -> "<GAP Jupyter Kernel>" );

InstallMethod( Run
             , "for Jupyter kernel"
             , [ IsGAPJupyterKernel ]
             , function(x)
                 # TODO: we should really not be doing this.
                 MakeReadWriteGlobal("HELP_SHOW_MATCHES");
                 UnbindGlobal("HELP_SHOW_MATCHES");
                 DeclareSynonym("HELP_SHOW_MATCHES", JUPYTER_HELP_SHOW_MATCHES);

                 MakeReadWriteGlobal("HELP");
                 UnbindGlobal("HELP");
                 DeclareSynonym("HELP", JUPYTER_HELP);

                 SetUserPreference("browse", "SelectHelpMatches", false);
                 SetUserPreference("Pager", "tail");
                 SetUserPreference("PagerOptions", "");
                 # This is of course complete nonsense if you're running the jupyter notebook
                 # on your local machine.
                 SetHelpViewer("jupyter_online");
                 x!.Loop();
             end);

InstallGlobalFunction( JUPYTER_KernelStart_HPC,
function(conf)
    Error("HPC-GAP is not supported with this code.");
    QUIT_GAP(0);
end);

InstallGlobalFunction( JUPYTER_KernelStart_GAP,
function(configfile)
    local instream, conf, address, kernel, s;

    instream := InputTextFile(configfile);
    conf := JsonStreamToGap(instream);

    kernel := NewJupyterKernel(conf);
    Run(kernel);
end);