1 module memoize;
2 
3 import std.traits;
4 
5 /**
6 The following code makes cached (memoized) property `f`
7 ```
8 class {
9     @property string _f() { ... }
10     mixin CachedProperty!"f";
11 }
12 ```
13 */
14 mixin template CachedProperty(string name, string baseName = '_' ~ name) {
15     mixin("private typeof(" ~ baseName ~ ") " ~ name ~ "Cache;");
16     mixin("private bool " ~ name ~ "IsCached = false;");
17     mixin("@property typeof(" ~ baseName ~ ") " ~ name ~ "() {\n" ~
18           "if (" ~ name ~ "IsCached" ~ ") return " ~ name ~ "Cache;\n" ~
19           name ~ "IsCached = true;\n" ~
20           "return " ~ name ~ "Cache = " ~ baseName ~ ";\n" ~
21           '}');
22 }
23 
24 unittest {
25     struct S {
26         @property float _x() { return 1.5; }
27         mixin CachedProperty!"x";
28     }
29     class C {
30         @property float _x() { return 1.5; }
31         mixin CachedProperty!"x";
32     }
33     S s;
34     assert(s.x == 1.5);
35     assert(s.x == 1.5);
36     C c = new C();
37     assert(c.x == 1.5);
38 }
39 
40 private template _memoize(alias fun, string attr)
41 {
42     // alias Args = Parameters!fun; // Bugzilla 13580
43 
44     ReturnType!fun _memoize(Parameters!fun args)
45     {
46         alias Args = Parameters!fun;
47         import std.typecons : Tuple;
48 
49         mixin(attr ~ " static Unqual!(ReturnType!fun)[Tuple!Args] memo;");
50         auto t = Tuple!Args(args);
51         if (auto p = t in memo)
52             return *p;
53         return memo[t] = fun(args);
54     }
55 }
56 
57 private template _memoize(alias fun, uint maxSize, string modifier)
58 {
59     // alias Args = Parameters!fun; // Bugzilla 13580
60     ReturnType!fun _memoize(Parameters!fun args)
61     {
62         import std.traits : hasIndirections;
63         import std.typecons : tuple;
64         static struct Value { Parameters!fun args; ReturnType!fun res; }
65         mixin(modifier ~ " static Unqual!(Value)[] memo;");
66         mixin(modifier ~ " static size_t[] initialized;");
67 
68         if (!memo.length)
69         {
70             import core.memory : GC;
71 
72             // Ensure no allocation overflows
73             static assert(maxSize < size_t.max / Value.sizeof);
74             static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1));
75 
76             enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN);
77             mixin("alias VType = " ~ modifier ~ " Value;");
78             memo = (cast(VType*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize];
79             enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof);
80             mixin("alias mysize_t = " ~ modifier ~ " size_t;");
81             initialized = (cast(mysize_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords];
82         }
83 
84         import core.bitop : bt, bts;
85         import std.conv : emplace;
86 
87         size_t hash;
88         foreach (ref arg; args)
89             hash = hashOf(arg, hash);
90         // cuckoo hashing
91         immutable idx1 = hash % maxSize;
92         if (!bt(cast(size_t*) initialized.ptr, idx1))
93         {
94             emplace(&memo[idx1], args, fun(args));
95             bts(cast(size_t*) initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025)
96             return memo[idx1].res;
97         }
98         else if (memo[idx1].args == args)
99             return memo[idx1].res;
100         // FNV prime
101         immutable idx2 = (hash * 16_777_619) % maxSize;
102         if (!bt(cast(size_t*) initialized.ptr, idx2))
103         {
104             emplace(&memo[idx2], memo[idx1]);
105             bts(cast(size_t*) initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025)
106         }
107         else if (memo[idx2].args == args)
108             return memo[idx2].res;
109         else if (idx1 != idx2)
110             memo[idx2] = memo[idx1];
111 
112         memo[idx1] = Value(args, fun(args));
113         return memo[idx1].res;
114     }
115 }
116 
117 // See https://issues.dlang.org/show_bug.cgi?id=4533 why aliases are not used.
118 
119 /**
120 The same as in Phobos `std.functional`.
121 */
122 //alias memoize(alias fun) = _memoize!(fun, "");
123 template memoize(alias fun)
124 {
125     ReturnType!fun memoize(Parameters!fun args)
126     {
127         return _memoize!(fun, "")(args);
128     }
129 }
130 
131 /// ditto
132 //alias memoize(alias fun, uint maxSize) = _memoize!(fun, maxSize, "");
133 template memoize(alias fun, uint maxSize)
134 {
135     ReturnType!fun memoize(Parameters!fun args)
136     {
137         return _memoize!(fun, maxSize, "")(args);
138     }
139 }
140 
141 /// must be locked explicitly!
142 //alias noLockMemoize(alias fun) = _memoize!(fun, "shared");
143 template noLockMemoize(alias fun)
144 {
145     ReturnType!fun noLockMemoize(Parameters!fun args)
146     {
147         return _memoize!(fun, "shared")(args);
148     }
149 }
150 
151 /// must be locked explicitly!
152 //alias noLockMemoize(alias fun, uint maxSize) = _memoize!(fun, maxSize, "shared");
153 template noLockMemoize(alias fun, uint maxSize)
154 {
155     ReturnType!fun noLockMemoize(Parameters!fun args)
156     {
157         return _memoize!(fun, maxSize, "shared")(args);
158     }
159 }
160 
161 /**
162 Synchronized version of `memoize` using global (interthread) cache.
163 */
164 template synchronizedMemoize(alias fun) {
165     private alias impl = memoize!fun;
166     ReturnType!fun synchronizedMemoize(Parameters!fun args) {
167         synchronized {
168             return impl(args);
169         }
170     }
171 }
172 
173 /// ditto
174 template synchronizedMemoize(alias fun, uint maxSize) {
175     private alias impl = memoize!(fun, maxSize);
176     ReturnType!fun synchronizedMemoize(Parameters!fun args) {
177         synchronized {
178             return impl(args);
179         }
180     }
181 }
182 
183 @safe unittest
184 {
185     ulong fib(ulong n) @safe
186     {
187         return n < 2 ? n : memoize!fib(n - 2) + memoize!fib(n - 1);
188     }
189     assert(fib(10) == 55);
190     ulong fib2(ulong n) @safe
191     {
192         return n < 2 ? n : synchronizedMemoize!fib2(n - 2) + synchronizedMemoize!fib2(n - 1);
193     }
194     assert(fib2(10) == 55);
195 }
196 
197 @safe unittest
198 {
199     ulong fact(ulong n) @safe
200     {
201         return n < 2 ? 1 : n * memoize!fact(n - 1);
202     }
203     assert(fact(10) == 3628800);
204     ulong fact2(ulong n) @safe
205     {
206         return n < 2 ? 1 : n * synchronizedMemoize!fact2(n - 1);
207     }
208     assert(fact2(10) == 3628800);
209 }
210 
211 @safe unittest
212 {
213     ulong factImpl(ulong n) @safe
214     {
215         return n < 2 ? 1 : n * factImpl(n - 1);
216     }
217     alias fact = memoize!factImpl;
218     assert(fact(10) == 3628800);
219     ulong factImpl2(ulong n) @safe
220     {
221         return n < 2 ? 1 : n * factImpl2(n - 1);
222     }
223     alias fact2 = synchronizedMemoize!factImpl;
224     assert(fact2(10) == 3628800);
225 }
226 
227 @system unittest // not @safe due to memoize
228 {
229     ulong fact(ulong n)
230     {
231         // Memoize no more than 8 values
232         return n < 2 ? 1 : n * memoize!(fact, 8)(n - 1);
233     }
234     assert(fact(8) == 40320);
235     // using more entries than maxSize will overwrite existing entries
236     assert(fact(10) == 3628800);
237     ulong fact2(ulong n)
238     {
239         // Memoize no more than 8 values
240         return n < 2 ? 1 : n * synchronizedMemoize!(fact2, 8)(n - 1);
241     }
242     assert(fact2(8) == 40320);
243     // using more entries than maxSize will overwrite existing entries
244     assert(fact2(10) == 3628800);
245 }
246 
247 /**
248 Use it to memoize both a struct or class instance for a member function and function arguments like:
249 ```
250 struct S {
251     int f(int a, int b) {
252         return a + b;
253     }
254 }
255 alias f2 = memoizeMember!(S, "f");
256 alias f3 = memoizeMember!(S, "f", 10);
257 S s;
258 assert(f2(s, 2, 3) == 5);
259 ```
260 
261 As you see the member function name ("f" in the example) is passed as a string.
262 This is very unnatural, but I found no other way to do it.
263 */
264 template memoizeMember(S, string name) {
265     alias Member = __traits(getMember, S, name);
266     ReturnType!Member f(S s, Parameters!Member others) {
267         return __traits(getMember, s, name)(others);
268     }
269     alias memoizeMember = memoize!f;
270 }
271 
272 /// ditto
273 template memoizeMember(S, string name, uint maxSize) {
274     alias Member = __traits(getMember, S, name);
275     ReturnType!Member f(S s, Parameters!Member others) {
276         return __traits(getMember, s, name)(others);
277     }
278     alias memoizeMember = memoize!(f, maxSize);
279 }
280 
281 /// ditto
282 template noLockMemoizeMember(S, string name) {
283     alias Member = __traits(getMember, S, name);
284     ReturnType!Member f(S s, Parameters!Member others) {
285         return __traits(getMember, s, name)(others);
286     }
287     alias noLockMemoizeMember = noLockMemoize!f;
288 }
289 
290 /// ditto
291 template noLockMemoizeMember(S, string name, uint maxSize) {
292     alias Member = __traits(getMember, S, name);
293     ReturnType!Member f(S s, Parameters!Member others) {
294         return __traits(getMember, s, name)(others);
295     }
296     alias noLockMemoizeMember = noLockMemoize!(f, maxSize);
297 }
298 
299 /// ditto
300 template synchronizedMemoizeMember(S, string name) {
301     alias Member = __traits(getMember, S, name);
302     ReturnType!Member f(S s, Parameters!Member others) {
303         return __traits(getMember, s, name)(others);
304     }
305     alias synchronizedMemoizeMember = synchronizedMemoize!f;
306 }
307 
308 /// ditto
309 template synchronizedMemoizeMember(S, string name, uint maxSize) {
310     alias Member = __traits(getMember, S, name);
311     ReturnType!Member f(S s, Parameters!Member others) {
312         return __traits(getMember, s, name)(others);
313     }
314     alias synchronizedMemoizeMember = synchronizedMemoize!(f, maxSize);
315 }
316 
317 unittest {
318     struct S2 {
319         int f(int a, int b) {
320             return a + b;
321         }
322     }
323     alias f2 = memoizeMember!(S2, "f");
324     alias f3 = memoizeMember!(S2, "f", 10);
325     alias f4 = synchronizedMemoizeMember!(S2, "f");
326     alias f5 = synchronizedMemoizeMember!(S2, "f", 10);
327     S2 s;
328     assert(f2(s, 2, 3) == 5);
329     assert(f2(s, 2, 3) == 5);
330     assert(f3(s, 2, 3) == 5);
331     assert(f4(s, 2, 3) == 5);
332     assert(f5(s, 2, 3) == 5);
333 }