1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
|
/**
BlkInfo thread-local cache. Used for array appending in the conservative GC to avoid the lock when possible.
Note: this used to be in rt.lifetime, but was moved here to allow GCs to take over array operations.
*/
module core.internal.gc.blkcache;
import core.memory;
import core.attribute;
debug (PRINTF) import core.stdc.stdio : printf;
alias BlkInfo = GC.BlkInfo;
alias BlkAttr = GC.BlkAttr;
/**
cache for the lookup of the block info
*/
private enum N_CACHE_BLOCKS = 8;
// note this is TLS, so no need to sync.
BlkInfo *__blkcache_storage;
static if (N_CACHE_BLOCKS == 1)
{
version=single_cache;
}
else
{
//version=simple_cache; // uncomment to test simple cache strategy
//version=random_cache; // uncomment to test random cache strategy
// ensure N_CACHE_BLOCKS is power of 2.
static assert(!((N_CACHE_BLOCKS - 1) & N_CACHE_BLOCKS));
version (random_cache)
{
int __nextRndNum = 0;
}
int __nextBlkIdx;
}
@property BlkInfo *__blkcache() nothrow @nogc
{
if (!__blkcache_storage)
{
import core.stdc.stdlib : calloc;
import core.thread.threadbase;
auto tBase = ThreadBase.getThis();
if (tBase is null)
// if we don't have a thread object, this is a detached thread, and
// this won't be properly maintained by the GC.
return null;
// allocate the block cache for the first time
immutable size = BlkInfo.sizeof * N_CACHE_BLOCKS;
// use C alloc, because this may become a detached thread, and the GC
// would then clean up the cache without zeroing this pointer.
__blkcache_storage = cast(BlkInfo*) calloc(size, 1);
tBase.tlsGCData = __blkcache_storage;
}
return __blkcache_storage;
}
// free the allocation on thread exit.
@standalone static ~this()
{
if (__blkcache_storage)
{
import core.stdc.stdlib : free;
import core.thread.threadbase;
auto tBase = ThreadBase.getThis();
if (tBase !is null)
tBase.tlsGCData = null;
free(__blkcache_storage);
__blkcache_storage = null;
}
}
/**
* Indicates whether an address has been marked by the GC.
*/
enum IsMarked : int
{
no, /// Address is not marked.
yes, /// Address is marked.
unknown, /// Address is not managed by the GC.
}
alias IsMarkedDg = IsMarked delegate(void* addr) nothrow; /// The isMarked callback function.
// we expect this to be called with the lock in place
void processGCMarks(void* data, scope IsMarkedDg isMarked) nothrow
{
if (!data)
return;
auto cache = cast(BlkInfo*) data;
// called after the mark routine to eliminate block cache data when it
// might be ready to sweep
debug(PRINTF) printf("processing GC Marks, %p\n", cache);
debug(PRINTF) foreach (i; 0 .. N_CACHE_BLOCKS)
{
printf("cache entry %d has base ptr %p\tsize %zd\tflags %x\n", i, cache[i].base, cache[i].size, cache[i].attr);
}
auto cache_end = cache + N_CACHE_BLOCKS;
for (;cache < cache_end; ++cache)
{
if (cache.base != null && isMarked(cache.base) == IsMarked.no)
{
debug(PRINTF) printf("clearing cache entry at %p\n", cache.base);
cache.base = null; // clear that data.
}
}
}
unittest
{
// Bugzilla 10701 - segfault in GC
ubyte[] result; result.length = 4096;
GC.free(result.ptr);
GC.collect();
}
/**
Get the cached block info of an interior pointer. Returns null if the
interior pointer's block is not cached.
NOTE: The following note was not valid, but is retained for historical
purposes. The data cannot be cleared because the stack contains a
reference to the affected block (e.g. through `interior`). Therefore,
the element will not be collected, and the data will remain valid.
ORIGINAL: The base ptr in this struct can be cleared asynchronously by the GC,
so any use of the returned BlkInfo should copy it and then check the
base ptr of the copy before actually using it.
*/
BlkInfo *__getBlkInfo(void *interior) nothrow @nogc
{
BlkInfo *ptr = __blkcache;
if (ptr is null)
// if for some reason we don't have a cache, return null.
return null;
version (single_cache)
{
if (ptr.base && ptr.base <= interior && (interior - ptr.base) < ptr.size)
return ptr;
return null; // not in cache.
}
else version (simple_cache)
{
foreach (i; 0..N_CACHE_BLOCKS)
{
if (ptr.base && ptr.base <= interior && (interior - ptr.base) < ptr.size)
return ptr;
ptr++;
}
}
else
{
// try to do a smart lookup, using __nextBlkIdx as the "head"
auto curi = ptr + __nextBlkIdx;
for (auto i = curi; i >= ptr; --i)
{
if (i.base && i.base <= interior && cast(size_t)(interior - i.base) < i.size)
return i;
}
for (auto i = ptr + N_CACHE_BLOCKS - 1; i > curi; --i)
{
if (i.base && i.base <= interior && cast(size_t)(interior - i.base) < i.size)
return i;
}
}
return null; // not in cache.
}
void __insertBlkInfoCache(BlkInfo bi, BlkInfo *curpos) nothrow @nogc
{
auto cache = __blkcache;
if (cache is null)
// no cache to use.
return;
version (single_cache)
{
*cache = bi;
return;
}
else
{
version (simple_cache)
{
if (curpos)
*curpos = bi;
else
{
// note, this is a super-simple algorithm that does not care about
// most recently used. It simply uses a round-robin technique to
// cache block info. This means that the ordering of the cache
// doesn't mean anything. Certain patterns of allocation may
// render the cache near-useless.
cache[__nextBlkIdx] = bi;
__nextBlkIdx = (__nextBlkIdx+1) & (N_CACHE_BLOCKS - 1);
}
}
else version (random_cache)
{
// strategy: if the block currently is in the cache, move the
// current block index to the a random element and evict that
// element.
if (!curpos)
{
__nextBlkIdx = (__nextRndNum = 1664525 * __nextRndNum + 1013904223) & (N_CACHE_BLOCKS - 1);
curpos = cache + __nextBlkIdx;
}
else
{
__nextBlkIdx = curpos - cache;
}
*curpos = bi;
}
else
{
//
// strategy: If the block currently is in the cache, swap it with
// the head element. Otherwise, move the head element up by one,
// and insert it there.
//
if (!curpos)
{
__nextBlkIdx = (__nextBlkIdx+1) & (N_CACHE_BLOCKS - 1);
curpos = cache + __nextBlkIdx;
}
else if (curpos !is cache + __nextBlkIdx)
{
*curpos = cache[__nextBlkIdx];
curpos = cache + __nextBlkIdx;
}
*curpos = bi;
}
}
}
debug(PRINTF)
{
extern(C) void printArrayCache()
{
auto ptr = __blkcache;
printf("CACHE: \n");
foreach (i; 0 .. N_CACHE_BLOCKS)
{
printf(" %d\taddr:% .8p\tsize:% .10zd\tflags:% .8x\n", i, ptr[i].base, ptr[i].size, ptr[i].attr);
}
}
}
|