1 module chimpfella.execute;
2 import chimpfella.kernels;
3 import std.meta;
4 
5 ///Information from you about what to do with your benchmarks, where to put them, and what to do in between them.
6 public struct MetaExecution(ProcessBench)
7 {
8     ///Output range to deal with the data generated (i.e. store in a file, dump to stdout etc.)
9     ProcessBench storeData;
10     this(ProcessBench sendSet)
11     {
12         storeData = sendSet;
13     }
14     //The output range is generic, so it can be from you but could also be dispatched at runtime via an interface
15     ///What to do with the GC
16     struct GCActions
17     {
18 
19     }
20     //
21 }
22 
23 /+++
24     Execution Engine:
25     Takes multiple functions to be executed, however, they should be considered as a group, use multiple engines 
26     for totally distinct functions.
27 +/
28 private template DefaultExecutionEngine(string fullName, FuncPack...)
29         if (FuncPack.length > 0)
30 {
31     alias theFunc = FuncPack[0];
32     import chimpfella.measurement;
33 
34     void runEngine(MetaT, alias genInd, alias indToData)(
35             scope ref MetaT metaExecutionState, const Measurements[] runThese)
36     {
37         import std.traits : isCallable;
38 
39         //start at one so we can store the independant variable
40         size_t width = 1;
41         foreach (meas; runThese)
42         {
43             width += procMeas!(m => m.eventCount())(meas);
44         }
45         size_t[] resultBuffer;
46         {
47             import core.stdc.stdlib : alloca;
48 
49             void* x = alloca(width * size_t.sizeof);
50             resultBuffer = (cast(size_t*) x)[0 .. width];
51             resultBuffer[] = 0;
52         }
53         static if (isCallable!genInd)
54             auto indData = genInd();
55         else
56             auto indData = genInd;
57 
58         auto outputDataHere = metaExecutionState.storeData.getOutputHandle(fullName, runThese);
59         foreach (indItem; indData)
60         {
61             resultBuffer[0] = indItem;
62             //If generator function is void, give the independant variable straight to the function
63             static if (is(indToData == void))
64                 const theData = indItem;
65             else
66                 const theData = indToData(indItem);
67             //Iterate over each measurement, but we don't yet consider the cost of each one.
68             foreach (i, counter; runThese)
69             {
70                 const measurementWidth = procMeas!(m => m.eventCount())(counter);
71                 //How many results have been given so far
72                 size_t countsRetired = 0;
73                 scope (exit)
74                     countsRetired += measurementWidth;
75                 import std.traits : ReturnType;
76 
77                 StateTypes state = counter.getState;
78                 procState!((ref x) => x.start)(state);
79                 //Hot stuff starts here
80                 static if (__traits(isSame, ReturnType!theFunc, void))
81                 {
82                     //throw away, nothing we can do
83                     theFunc(theData);
84                     //Hot region ends
85                     procState!((ref x) => x.stop)(state);
86                 }
87                 else
88                 {
89                     import std.traits : arity, hasMember;
90 
91                     static if (arity!theFunc > 1)
92                     {
93 
94                         static assert(hasMember!(typeof(theData), "pack"),
95                                 "Since D does not have multiple return values this library requires that you return some\n
96                         type containing 
97                         an AliasSeq of values named \"pack\" which the library will expand for you");
98                         const res = theFunc(theData.pack);
99                     }
100                     else
101                     {
102                         const res = theFunc(theData);
103                     }
104 
105                     //Hot region ends
106                     procState!((ref x) => x.stop)(state);
107                     //force a data dependancy on the output to trick the compiler
108                     import core..volatile;
109 
110                     ubyte[res.sizeof] outputVolatile;
111                     ubyte[] slicedInput = (cast(ubyte*)&res)[0 .. res.sizeof];
112                     foreach (__i, ref d; slicedInput)
113                     {
114                         volatileStore(&outputVolatile[__i], d);
115                     }
116                 }
117 
118                 //We have the data, do something with it.
119                 {
120                     import std.array : RefAppender, appender;
121 
122                     const startIdx = 1 + countsRetired;
123                     auto app = resultBuffer[startIdx .. startIdx + measurementWidth];
124 
125                     procState!((ref x) => x.read(app))(state);
126                 }
127 
128                 outputDataHere.put(resultBuffer);
129             }
130 
131         }
132     }
133 
134 }
135 ///Execute benchmarks from `fromHere` - i.e. this runs ExecutionEngine for you all those Benchmarks
136 template ExecuteBenchmarks(alias fromHere)
137 {
138     import std.traits;
139     import std.range : ElementType;
140 
141     void run(MetaT)(scope ref MetaT metaExecutionState)
142     {
143         alias udaList = getSymbolsByUDA!(fromHere, FunctionBenchmark);
144         //Dispatch based on what other Benchmark UDAs we find.
145         foreach (symbol; udaList)
146         {
147             //Symbol has a FunctionBenchmark
148             alias funcBenchPack = getUDAs!(symbol, FunctionBenchmark);
149             //If there is anything other than one the benchmarkark is ill formed
150             static assert(funcBenchPack.length == 1);
151             enum theFunctionBench = funcBenchPack[0];
152             //Is it a template?
153             static if (__traits(isTemplate, symbol))
154             {
155                 //Should have a TemplateBenchmark attached to it to tell us what metaparameters to use.
156 
157                 //Data generator is a template; We must instantiate it.
158                 enum generatorIsATemplate = __traits(isTemplate, theFunctionBench.genData);
159 
160                 alias templateMetaPack = getUDAs!(symbol, TemplateBenchmark);
161                 const uint paramCov = templateMetaPack.length;
162                 //We need at least one
163                 static assert(paramCov, "there is a function benchmark attached to a symbol that is a template - it does not have a TemplateBenchmark");
164                 enum order(alias left, alias right) = left.index < right.index;
165                 //Get 'em in the right order
166 
167                 alias sortedMetaPack = staticSort!(order, templateMetaPack);
168                 //Doesn't do every permutation (yet)
169                 foreach (packIdx, packItem; sortedMetaPack)
170                 {
171                     //Process a single parameter
172                     foreach (innerIdx, paramItem; packItem.paramPack)
173                     {
174                         import std.range.primitives;
175 
176                         //pragma(msg, "\t", paramItem);
177                         //Work out what we're doing, then instantiate theFunc with it.
178                         //Is it a type 
179                         
180                         //Parameter is fits the slot
181                         static if (__traits(compiles, symbol!paramItem))
182                         {
183                             alias func = symbol!paramItem;
184 
185                             auto bench = theFunctionBench;
186                             //static assert(!is(ReturnType!symbol == void), "benchmarked function may not (yet) be void. Support for ref args coming soon");
187                             //Run the benchmark
188                             alias engine = DefaultExecutionEngine!(theFunctionBench.benchmarkName ~ paramItem.stringof,
189                                     func);
190                             enum forwardTemplateParam = bench.forward;
191                             alias genInd = bench.genIndSet;
192                             alias genData_ = bench.genData;
193                             alias IndT = ElementType!(typeof(genInd));
194 
195                             static if (__traits(isTemplate, genData_))
196                             {
197                                 static assert(__traits(compiles, genData_!IndT));
198                                 alias genFunc = genData_!IndT;
199                             }
200                             else
201                             {
202                                 static if (forwardTemplateParam)
203                                 {
204                                     alias genFunc = genData_.contents!paramItem;
205                                 }
206                                 else
207                                 {
208                                     alias genFunc = genData_;
209                                 }
210 
211                             }
212 
213                             engine.runEngine!(MetaT, genInd,
214                                     genFunc)(metaExecutionState, bench.measurementList);
215                         }
216                         else
217                         {
218                             //It's not a type, so it better be a range literal.
219                             //Arrays are special cased
220                             alias specRangeT = typeof(paramItem);
221                             //pragma(msg, isInputRange!specRangeT);
222                             static assert(isInputRange!specRangeT
223                                     && !isInfinite!(specRangeT), "not a valid parameter");
224                             //It's a range 
225                             alias ElemT = ElementType!specRangeT;
226                             //pragma(msg, ElemT);
227                             
228                             static assert(__traits(compiles, symbol!(ElemT.init)),
229                                     " symbol = " ~ fullyQualifiedName!symbol);
230                             import std.array : array;
231                             enum ctRange = paramItem.array;
232 
233                             static foreach (ctRangeItem; ctRange)
234                             {
235                                 {
236                                     alias func = symbol!ctRangeItem;
237 
238                                     auto bench = theFunctionBench;
239                                     static if(isSomeString!(typeof(ctRangeItem)))
240                                         enum stringSummary = ctRangeItem;
241                                     else 
242                                         enum stringSummary = ctRangeItem.stringof;
243                                     //Run the benchmark
244                                     alias engine = DefaultExecutionEngine!(
245                                             theFunctionBench.benchmarkName ~ stringSummary, func);
246                                     enum forwardTemplateParam = bench.forward;
247                                     alias genInd = bench.genIndSet;
248                                     alias genData_ = bench.genData;
249                                     alias IndT = ElementType!(typeof(genInd));
250 
251                                     static if (__traits(isTemplate, genData_))
252                                     {
253                                         static assert(__traits(compiles, genData_!IndT));
254                                         alias genFunc = genData_!IndT;
255                                     }
256                                     else
257                                     {
258                                         static if (forwardTemplateParam)
259                                         {
260                                             alias genFunc = genData_.contents!paramItem;
261                                         }
262                                         else
263                                         {
264                                             alias genFunc = genData_;
265                                         }
266                                     }
267                                     engine.runEngine!(MetaT, genInd,
268                                         genFunc)(metaExecutionState, bench.measurementList);
269                                 }
270                                 
271                             }
272 
273                         }
274 
275                     }
276                 }
277                 //A parameter can be a range, don't forget
278 
279                 //Can't introspect over the number of template parameters realistically. 
280             }
281             else
282             {
283                 //It's a function benchmark
284                 //To find out how many there are
285 
286                 auto bench = theFunctionBench;
287                 //static assert(!is(ReturnType!symbol == void), "benchmarked function may not (yet) be void. Support for ref args coming soon");
288                 //Run the benchmark
289                 alias engine = DefaultExecutionEngine!(theFunctionBench.benchmarkName, symbol);
290                 alias genInd = bench.genIndSet;
291                 alias genData_ = bench.genData;
292                 alias IndT = ElementType!(typeof(genInd));
293 
294                 static if (__traits(isTemplate, genData_))
295                 {
296                     static assert(__traits(compiles, genData_!IndT));
297                     alias genFunc = genData_!IndT;
298                 }
299                 else
300                 {
301                     alias genFunc = genData_;
302                 }
303 
304                 engine.runEngine!(MetaT, genInd, genFunc)(metaExecutionState,
305                         bench.measurementList);
306             }
307 
308         }
309 
310     }
311 
312 }
313 
314 ///
315 unittest
316 {
317     @trusted auto getStdoutRange()
318     {
319         import std.stdio : stdout;
320 
321         return stdout.lockingTextWriter;
322     }
323     //Lump the benchmarks together somewhere.
324     static class StaticStore
325     {
326 
327         import chimpfella.kernels;
328         import chimpfella.measurement;
329         import std.range : iota, repeat;
330         import std.random : uniform;
331 
332         enum meas = [PhobosTimer("A stub").toMeasurement];
333 
334         @FunctionBenchmark!("Something with an integer", iota(1, 50), (x) => uniform(-x, x))(meas) static void func(
335                 int l)
336         {
337             uint x;
338             import core..volatile;
339 
340             foreach (_; 0 .. l)
341                 volatileStore(&x, _);
342         }
343 
344         static auto getRandPair(T)(int x)
345         {
346             import std.random;
347 
348             struct rndParams
349             {
350                 AliasSeq!(T, T) pack;
351             }
352 
353             rndParams tmp;
354             auto rnd = Random(unpredictableSeed);
355 
356             // Generate an integer in [0, 1023]
357             tmp.pack[0] = cast(T) uniform(0, 1024, rnd);
358             tmp.pack[1] = cast(T) uniform(0, 1024, rnd);
359             return tmp;
360         }
361 
362         @TemplateBenchmark!(0, int, float, double) @FunctionBenchmark!("Templated add benchmark",
363                 0.repeat(100), ForwardTemplate!(getRandPair))(meas) static T templatedAdd(T)(T x,
364                 T y)
365         {
366             return x + y;
367         }
368         import std.algorithm;
369         import std.array;
370         static string ctfeRepeater(int n)
371         {
372             return "cpuid;".repeat(n).join();
373         }
374         
375         enum cpuidRange = iota(1, 10).map!(ctfeRepeater).array;
376         @TemplateBenchmark!(0, cpuidRange) 
377         @FunctionBenchmark!("Measure", iota(1, 10), (_) => [1, 2, 3, 4])(meas) 
378         static int sum(string asmLine)(inout int[] input)
379         {
380             //This is quite fun because ldc will sometimes get rid of the entire function body and just loop over the asm's
381             int tmp;
382             foreach (i; input)
383             {
384                 mixin("asm { ", asmLine, ";}");
385             }
386             return tmp;
387         }
388 
389         alias summat = sum!"mfence";
390     }
391 
392     @safe struct PrintData
393     {
394         import chimpfella.measurement;
395 
396         auto getOutputHandle(string benchmarkName, scope const Measurements[] counters)
397         {
398             struct outputVoldy
399             {
400                 string name;
401                 size_t width;
402                 import std;
403 
404                 //You don't have to do anything with the header
405                 this(string setName, scope const Measurements[] them)
406                 {
407                     name = setName;
408                     writefln!"---------------%s---------------"(setName);
409                     auto counterPut = counters.map!(x => x.getHeader);
410                     //pragma(msg, ElementType!(typeof(counterPut)));
411                     writeln("I;", counterPut.joiner!(typeof(counterPut)).joiner.joiner(";"));
412                 }
413 
414                 void put(scope size_t[] data)
415                 {
416                     data.map!(s => s.to!string).joiner(";").writeln;
417                 }
418 
419             }
420 
421             return outputVoldy(benchmarkName, counters);
422         }
423     }
424 
425     PrintData outBuf;
426     auto dataOutput = MetaExecution!PrintData(outBuf);
427     ExecuteBenchmarks!(StaticStore).run(dataOutput);
428 }