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     import std.traits : ReturnType;
43     // alias Args = Parameters!fun; // Bugzilla 13580
44 
45     ReturnType!fun _memoize(Parameters!fun args)
46     {
47         alias Args = Parameters!fun;
48         import std.typecons : Tuple;
49 
50         mixin(attr ~ " static ReturnType!fun[Tuple!Args] memo;");
51         auto t = Tuple!Args(args);
52         if (auto p = t in memo)
53             return *p;
54         return memo[t] = fun(args);
55     }
56 }
57 
58 private template _memoize(alias fun, uint maxSize, string modifier)
59 {
60     import std.traits : ReturnType;
61     // alias Args = Parameters!fun; // Bugzilla 13580
62     ReturnType!fun _memoize(Parameters!fun args)
63     {
64         import std.traits : hasIndirections;
65         import std.typecons : tuple;
66         static struct Value { Parameters!fun args; ReturnType!fun res; }
67         mixin(modifier ~ " static Value[] memo;");
68         mixin(modifier ~ " static size_t[] initialized;");
69 
70         if (!memo.length)
71         {
72             import core.memory : GC;
73 
74             // Ensure no allocation overflows
75             static assert(maxSize < size_t.max / Value.sizeof);
76             static assert(maxSize < size_t.max - (8 * size_t.sizeof - 1));
77 
78             enum attr = GC.BlkAttr.NO_INTERIOR | (hasIndirections!Value ? 0 : GC.BlkAttr.NO_SCAN);
79             mixin("alias VType = " ~ modifier ~ " Value;");
80             memo = (cast(VType*) GC.malloc(Value.sizeof * maxSize, attr))[0 .. maxSize];
81             enum nwords = (maxSize + 8 * size_t.sizeof - 1) / (8 * size_t.sizeof);
82             mixin("alias mysize_t = " ~ modifier ~ " size_t;");
83             initialized = (cast(mysize_t*) GC.calloc(nwords * size_t.sizeof, attr | GC.BlkAttr.NO_SCAN))[0 .. nwords];
84         }
85 
86         import core.bitop : bt, bts;
87         import std.conv : emplace;
88 
89         size_t hash;
90         foreach (ref arg; args)
91             hash = hashOf(arg, hash);
92         // cuckoo hashing
93         immutable idx1 = hash % maxSize;
94         if (!bt(cast(size_t*) initialized.ptr, idx1))
95         {
96             emplace(&memo[idx1], args, fun(args));
97             bts(cast(size_t*) initialized.ptr, idx1); // only set to initialized after setting args and value (bugzilla 14025)
98             return memo[idx1].res;
99         }
100         else if (memo[idx1].args == args)
101             return memo[idx1].res;
102         // FNV prime
103         immutable idx2 = (hash * 16_777_619) % maxSize;
104         if (!bt(cast(size_t*) initialized.ptr, idx2))
105         {
106             emplace(&memo[idx2], memo[idx1]);
107             bts(cast(size_t*) initialized.ptr, idx2); // only set to initialized after setting args and value (bugzilla 14025)
108         }
109         else if (memo[idx2].args == args)
110             return memo[idx2].res;
111         else if (idx1 != idx2)
112             memo[idx2] = memo[idx1];
113 
114         memo[idx1] = Value(args, fun(args));
115         return memo[idx1].res;
116     }
117 }
118 
119 /**
120 The same as in Phobos `std.functional`.
121 */
122 alias memoize(alias fun) = _memoize!(fun, "");
123 
124 /// ditto
125 alias memoize(alias fun, uint maxSize) = _memoize!(fun, maxSize, "");
126 
127 /// must be locked explicitly!
128 alias noLockMemoize(alias fun) = _memoize!(fun, "shared");
129 
130 /// must be locked explicitly!
131 alias noLockMemoize(alias fun, uint maxSize) = _memoize!(fun, maxSize, "shared");
132 
133 /**
134 Synchronized version of `memoize` using global (interthread) cache.
135 */
136 template synchronizedMemoize(alias fun) {
137     private alias impl = noLockMemoize!fun;
138     ReturnType!fun synchronizedMemoize(Parameters!fun args) {
139         synchronized {
140             return impl(args);
141         }
142     }
143 }
144 
145 /// ditto
146 template synchronizedMemoize(alias fun, uint maxSize) {
147     private alias impl = noLockMemoize!(fun, maxSize);
148     ReturnType!fun synchronizedMemoize(Parameters!fun args) {
149         synchronized {
150             return impl(args);
151         }
152     }
153 }
154 
155 @safe unittest
156 {
157     ulong fib(ulong n) @safe
158     {
159         return n < 2 ? n : memoize!fib(n - 2) + memoize!fib(n - 1);
160     }
161     assert(fib(10) == 55);
162     ulong fib2(ulong n) @safe
163     {
164         return n < 2 ? n : synchronizedMemoize!fib2(n - 2) + synchronizedMemoize!fib2(n - 1);
165     }
166     assert(fib2(10) == 55);
167 }
168 
169 @safe unittest
170 {
171     ulong fact(ulong n) @safe
172     {
173         return n < 2 ? 1 : n * memoize!fact(n - 1);
174     }
175     assert(fact(10) == 3628800);
176     ulong fact2(ulong n) @safe
177     {
178         return n < 2 ? 1 : n * synchronizedMemoize!fact2(n - 1);
179     }
180     assert(fact2(10) == 3628800);
181 }
182 
183 @safe unittest
184 {
185     ulong factImpl(ulong n) @safe
186     {
187         return n < 2 ? 1 : n * factImpl(n - 1);
188     }
189     alias fact = memoize!factImpl;
190     assert(fact(10) == 3628800);
191     ulong factImpl2(ulong n) @safe
192     {
193         return n < 2 ? 1 : n * factImpl2(n - 1);
194     }
195     alias fact2 = synchronizedMemoize!factImpl;
196     assert(fact2(10) == 3628800);
197 }
198 
199 @system unittest // not @safe due to memoize
200 {
201     ulong fact(ulong n)
202     {
203         // Memoize no more than 8 values
204         return n < 2 ? 1 : n * memoize!(fact, 8)(n - 1);
205     }
206     assert(fact(8) == 40320);
207     // using more entries than maxSize will overwrite existing entries
208     assert(fact(10) == 3628800);
209     ulong fact2(ulong n)
210     {
211         // Memoize no more than 8 values
212         return n < 2 ? 1 : n * synchronizedMemoize!(fact2, 8)(n - 1);
213     }
214     assert(fact2(8) == 40320);
215     // using more entries than maxSize will overwrite existing entries
216     assert(fact2(10) == 3628800);
217 }
218 
219 /**
220 Use it to memoize both a struct or class instance for a member function and function arguments like:
221 ```
222 struct S {
223     int f(int a, int b) {
224         return a + b;
225     }
226 }
227 alias f2 = memoizeMember!(S, "f");
228 alias f3 = memoizeMember!(S, "f", 10);
229 S s;
230 assert(f2(s, 2, 3) == 5);
231 ```
232 
233 As you see the member function name ("f" in the example) is passed as a string.
234 This is very unnatural, but I found no other way to do it.
235 */
236 template memoizeMember(S, string name) {
237     alias Member = __traits(getMember, S, name);
238     ReturnType!Member f(S s, Parameters!Member others) {
239         return __traits(getMember, s, name)(others);
240     }
241     alias memoizeMember = memoize!f;
242 }
243 
244 /// ditto
245 template memoizeMember(S, string name, uint maxSize) {
246     alias Member = __traits(getMember, S, name);
247     ReturnType!Member f(S s, Parameters!Member others) {
248         return __traits(getMember, s, name)(others);
249     }
250     alias memoizeMember = memoize!(f, maxSize);
251 }
252 
253 unittest {
254     struct S2 {
255         int f(int a, int b) {
256             return a + b;
257         }
258     }
259     alias f2 = memoizeMember!(S2, "f");
260     alias f3 = memoizeMember!(S2, "f", 10);
261     S2 s;
262     assert(f2(s, 2, 3) == 5);
263     assert(f2(s, 2, 3) == 5);
264     assert(f3(s, 2, 3) == 5);
265 }