forked from AssemblyScript/assemblyscript
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathitcms.ts
414 lines (378 loc) · 15.1 KB
/
itcms.ts
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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
import { BLOCK, BLOCK_OVERHEAD, OBJECT_OVERHEAD, OBJECT_MAXSIZE, TOTAL_OVERHEAD, DEBUG, TRACE, RTRACE, PROFILE } from "./common";
import { onvisit, oncollect, oninterrupt, onyield } from "./rtrace";
import { TypeinfoFlags } from "../shared/typeinfo";
import { E_ALLOCATION_TOO_LARGE, E_ALREADY_PINNED, E_NOT_PINNED } from "../util/error";
// === ITCMS: An incremental Tri-Color Mark & Sweep garbage collector ===
// Adapted from Bach Le's μgc, see: https://github.com/bullno1/ugc
// ╒═════════════╤══════════════ Colors ═══════════════════════════╕
// │ Color │ Meaning │
// ├─────────────┼─────────────────────────────────────────────────┤
// │ WHITE* │ Unprocessed │
// │ BLACK* │ Processed │
// │ GRAY │ Processed with unprocessed children │
// │ TRANSPARENT │ Manually pinned (always reachable) │
// └─────────────┴─────────────────────────────────────────────────┘
// * flipped between cycles
// @ts-ignore: decorator
@lazy let white = 0;
// @ts-ignore: decorator
@inline const gray = 2;
// @ts-ignore: decorator
@inline const transparent = 3;
// @ts-ignore: decorator
@inline const COLOR_MASK = 3;
/** Size in memory of all objects currently managed by the GC. */
// @ts-ignore: decorator
@lazy let total: usize = 0;
/** Currently transitioning from SWEEP to MARK state. */
// @ts-ignore: decorator
@inline const STATE_IDLE = 0;
/** Currently marking reachable objects. */
// @ts-ignore: decorator
@inline const STATE_MARK = 1;
/** Currently sweeping unreachable objects. */
// @ts-ignore: decorator
@inline const STATE_SWEEP = 2;
/** Current collector state. */
// @ts-ignore: decorator
@lazy let state = STATE_IDLE;
// @ts-ignore: decorator
@lazy let fromSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
// @ts-ignore: decorator
@lazy let toSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
// @ts-ignore: decorator
@lazy let pinSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
// @ts-ignore: decorator
@lazy let iter: Object = changetype<Object>(0); // unsafe initializion below
function initLazy(space: Object): Object {
space.nextWithColor = changetype<usize>(space);
space.prev = space;
return space;
}
/** Visit cookie indicating scanning of an object. */
// @ts-ignore: decorator
@inline const VISIT_SCAN = 0;
// ╒═══════════════ Managed object layout (32-bit) ════════════════╕
// 3 2 1
// 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 bits
// ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
// │ Memory manager block │
// ╞═══════════════════════════════════════════════════════════╤═══╡
// │ next │ C │ = nextWithColor
// ├───────────────────────────────────────────────────────────┴───┤
// │ prev │
// ├───────────────────────────────────────────────────────────────┤
// │ rtId │
// ├───────────────────────────────────────────────────────────────┤
// │ rtSize │
// ╞>ptr═══════════════════════════════════════════════════════════╡
// │ ... │
// C: color
/** Represents a managed object in memory, consisting of a header followed by the object's data. */
@unmanaged class Object extends BLOCK {
/** Pointer to the next object with color flags stored in the alignment bits. */
nextWithColor: usize; // *u32
/** Pointer to the previous object. */
prev: Object; // *u32
/** Runtime id. */
rtId: u32;
/** Runtime size. */
rtSize: u32;
/** Gets the pointer to the next object. */
get next(): Object {
return changetype<Object>(this.nextWithColor & ~COLOR_MASK);
}
/** Sets the pointer to the next object. */
set next(obj: Object) {
this.nextWithColor = changetype<usize>(obj) | (this.nextWithColor & COLOR_MASK);
}
/** Gets this object's color. */
get color(): i32 {
return i32(this.nextWithColor & COLOR_MASK);
}
/** Sets this object's color. */
set color(color: i32) {
this.nextWithColor = (this.nextWithColor & ~COLOR_MASK) | color;
}
/** Gets the size of this object in memory. */
get size(): usize {
return BLOCK_OVERHEAD + (this.mmInfo & ~3);
}
/** Tests if this object is pointerfree. */
get isPointerfree(): bool {
let rtId = this.rtId;
// 0: Object, 1: ArrayBuffer, 2: String
return rtId <= idof<string>() || (__typeinfo(rtId) & TypeinfoFlags.POINTERFREE) != 0;
}
/** Unlinks this object from its list. */
unlink(): void {
let next = this.next;
if (next == null) {
if (DEBUG) assert(this.prev == null && changetype<usize>(this) < __heap_base);
return; // static data not yet linked
}
let prev = this.prev;
if (DEBUG) assert(prev);
next.prev = prev;
prev.next = next;
}
/** Links this object to the specified list, with the given color. */
linkTo(list: Object, withColor: i32): void {
let prev = list.prev;
this.nextWithColor = changetype<usize>(list) | withColor;
this.prev = prev;
prev.next = this;
list.prev = this;
}
/** Marks this object as gray, that is reachable with unscanned children. */
makeGray(): void {
if (this == iter) iter = assert(this.prev);
this.unlink();
this.linkTo(toSpace, this.isPointerfree ? i32(!white) : gray);
}
}
/** Visits all objects considered to be program roots. */
function visitRoots(cookie: u32): void {
__visit_globals(cookie);
let pn = pinSpace;
let iter = pn.next;
while (iter != pn) {
if (DEBUG) assert(iter.color == transparent);
__visit_members(changetype<usize>(iter) + TOTAL_OVERHEAD, cookie);
iter = iter.next;
}
}
/** Visits all objects on the stack. */
function visitStack(cookie: u32): void {
let ptr = __stack_pointer;
while (ptr < __heap_base) {
__visit(load<usize>(ptr), cookie);
ptr += sizeof<usize>();
}
}
/** Performs a single step according to the current state. */
function step(): usize {
// Magic constants responsible for pause times. Obtained experimentally
// using the compiler compiling itself. 2048 budget pro run by default.
const MARKCOST = isDefined(ASC_GC_MARKCOST) ? ASC_GC_MARKCOST : 1;
const SWEEPCOST = isDefined(ASC_GC_SWEEPCOST) ? ASC_GC_SWEEPCOST : 10;
let obj: Object;
switch (state) {
case STATE_IDLE: {
state = STATE_MARK;
visitCount = 0;
visitRoots(VISIT_SCAN);
iter = toSpace;
return visitCount * MARKCOST;
}
case STATE_MARK: {
let black = i32(!white);
obj = iter.next;
while (obj != toSpace) {
iter = obj;
if (obj.color != black) { // skip already-blacks (pointerfree)
obj.color = black;
visitCount = 0;
__visit_members(changetype<usize>(obj) + TOTAL_OVERHEAD, VISIT_SCAN);
return visitCount * MARKCOST;
}
obj = obj.next;
}
visitCount = 0;
visitRoots(VISIT_SCAN);
obj = iter.next;
if (obj == toSpace) {
visitStack(VISIT_SCAN);
obj = iter.next;
while (obj != toSpace) {
if (obj.color != black) {
obj.color = black;
__visit_members(changetype<usize>(obj) + TOTAL_OVERHEAD, VISIT_SCAN);
}
obj = obj.next;
}
let from = fromSpace;
fromSpace = toSpace;
toSpace = from;
white = black;
iter = from.next;
state = STATE_SWEEP;
}
return visitCount * MARKCOST;
}
case STATE_SWEEP: {
obj = iter;
if (obj != toSpace) {
iter = obj.next;
if (DEBUG) assert(obj.color == i32(!white)); // old white
free(obj);
return SWEEPCOST;
}
toSpace.nextWithColor = changetype<usize>(toSpace);
toSpace.prev = toSpace;
state = STATE_IDLE;
break;
}
}
return 0;
}
/** Frees an object. */
function free(obj: Object): void {
if (changetype<usize>(obj) < __heap_base) {
obj.nextWithColor = 0; // may become linked again
obj.prev = changetype<Object>(0);
} else {
total -= obj.size;
if (isDefined(__finalize)) {
__finalize(changetype<usize>(obj) + TOTAL_OVERHEAD);
}
__free(changetype<usize>(obj) + BLOCK_OVERHEAD);
}
}
// Garbage collector interface
// @ts-ignore: decorator
@global @unsafe
export function __new(size: usize, id: i32): usize {
if (size >= OBJECT_MAXSIZE) throw new Error(E_ALLOCATION_TOO_LARGE);
if (total >= threshold) interrupt();
let obj = changetype<Object>(__alloc(OBJECT_OVERHEAD + size) - BLOCK_OVERHEAD);
obj.rtId = id;
obj.rtSize = <u32>size;
obj.linkTo(fromSpace, white); // inits next/prev
total += obj.size;
let ptr = changetype<usize>(obj) + TOTAL_OVERHEAD;
// may be visited before being fully initialized, so must fill
memory.fill(ptr, 0, size);
return ptr;
}
// @ts-ignore: decorator
@global @unsafe
export function __renew(oldPtr: usize, size: usize): usize {
let oldObj = changetype<Object>(oldPtr - TOTAL_OVERHEAD);
// Update object size if its block is large enough
if (size <= (oldObj.mmInfo & ~3) - OBJECT_OVERHEAD) {
oldObj.rtSize = <u32>size;
return oldPtr;
}
// If not the same object anymore, we have to move it move it due to the
// shadow stack potentially still referencing the old object
let newPtr = __new(size, oldObj.rtId);
memory.copy(newPtr, oldPtr, min(size, oldObj.rtSize));
return newPtr;
}
// @ts-ignore: decorator
@global @unsafe
export function __link(parentPtr: usize, childPtr: usize, expectMultiple: bool): void {
// Write barrier is unnecessary if non-incremental
if (!childPtr) return;
if (DEBUG) assert(parentPtr);
let child = changetype<Object>(childPtr - TOTAL_OVERHEAD);
if (child.color == white) {
let parent = changetype<Object>(parentPtr - TOTAL_OVERHEAD);
let parentColor = parent.color;
if (parentColor == i32(!white)) {
// Maintain the invariant that no black object may point to a white object.
if (expectMultiple) {
// Move the barrier "backward". Suitable for containers receiving multiple stores.
// Avoids a barrier for subsequent objects stored into the same container.
parent.makeGray();
} else {
// Move the barrier "forward". Suitable for objects receiving isolated stores.
child.makeGray();
}
} else if (parentColor == transparent && state == STATE_MARK) {
// Pinned objects are considered 'black' during the mark phase.
child.makeGray();
}
}
}
// @ts-ignore: decorator
@lazy let visitCount = 0;
// @ts-ignore: decorator
@global @unsafe
export function __visit(ptr: usize, cookie: i32): void {
if (!ptr) return;
let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
if (RTRACE) if (!onvisit(obj)) return;
if (obj.color == white) {
obj.makeGray();
++visitCount;
}
}
// @ts-ignore: decorator
@global @unsafe
export function __pin(ptr: usize): usize {
if (ptr) {
let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
if (obj.color == transparent) {
throw new Error(E_ALREADY_PINNED);
}
obj.unlink(); // from fromSpace
obj.linkTo(pinSpace, transparent);
}
return ptr;
}
// @ts-ignore: decorator
@global @unsafe
export function __unpin(ptr: usize): void {
if (!ptr) return;
let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
if (obj.color != transparent) {
throw new Error(E_NOT_PINNED);
}
if (state == STATE_MARK) {
// We may be right at the point after marking roots for the second time and
// entering the sweep phase, in which case the object would be missed if it
// is not only pinned but also a root. Make sure it isn't missed.
obj.makeGray();
} else {
obj.unlink();
obj.linkTo(fromSpace, white);
}
}
// @ts-ignore: decorator
@global @unsafe
export function __collect(): void {
if (TRACE) trace("GC (full) at", 1, total);
if (state > STATE_IDLE) {
// finish current cycle
while (state != STATE_IDLE) step();
}
// perform a full cycle
step();
while (state != STATE_IDLE) step();
threshold = <usize>(<u64>total * IDLEFACTOR / 100) + GRANULARITY;
if (TRACE) trace("GC (full) done at cur/max", 2, total, memory.size() << 16);
if (RTRACE || PROFILE) oncollect(total);
}
// Garbage collector automation
/** How often to interrupt. The default of 1024 means "interrupt each 1024 bytes allocated". */
// @ts-ignore: decorator
@inline const GRANULARITY: usize = isDefined(ASC_GC_GRANULARITY) ? ASC_GC_GRANULARITY : 1024;
/** How long to interrupt. The default of 200% means "run at double the speed of allocations". */
// @ts-ignore: decorator
@inline const STEPFACTOR: usize = isDefined(ASC_GC_SWEEPFACTOR) ? ASC_GC_SWEEPFACTOR : 200;
/** How long to idle. The default of 200% means "wait for memory to double before kicking in again". */
// @ts-ignore: decorator
@inline const IDLEFACTOR: usize = isDefined(ASC_GC_IDLEFACTOR) ? ASC_GC_IDLEFACTOR : 200;
/** Threshold of memory used by objects to exceed before interrupting again. */
// @ts-ignore: decorator
@lazy let threshold: usize = ((<usize>memory.size() << 16) - __heap_base) >> 1;
/** Performs a reasonable amount of incremental GC steps. */
function interrupt(): void {
if (PROFILE) oninterrupt(total);
if (TRACE) trace("GC (auto) at", 1, total);
let budget: isize = GRANULARITY * STEPFACTOR / 100;
do {
budget -= step();
if (state == STATE_IDLE) {
if (TRACE) trace("└ GC (auto) done at cur/max", 2, total, memory.size() << 16);
threshold = <usize>(<u64>total * IDLEFACTOR / 100) + GRANULARITY;
if (PROFILE) onyield(total);
return;
}
} while (budget > 0);
if (TRACE) trace("└ GC (auto) ongoing at", 1, total);
threshold = total + GRANULARITY * usize(total - threshold < GRANULARITY);
if (PROFILE) onyield(total);
}