toilscript 0.0.1 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (121) hide show
  1. package/LICENSE +201 -201
  2. package/NOTICE +94 -94
  3. package/README.md +101 -114
  4. package/bin/asc.js +0 -0
  5. package/bin/asinit.js +6 -6
  6. package/dist/asc.generated.d.ts +10027 -0
  7. package/dist/asc.js +24474 -0
  8. package/dist/asc.js.map +7 -0
  9. package/dist/importmap.json +9 -0
  10. package/dist/toilscript.generated.d.ts +11242 -0
  11. package/dist/toilscript.js +337 -0
  12. package/dist/toilscript.js.map +7 -0
  13. package/dist/web.js +22 -0
  14. package/lib/binaryen.d.ts +2 -2
  15. package/lib/binaryen.js +2 -2
  16. package/package.json +115 -114
  17. package/std/README.md +6 -6
  18. package/std/assembly/array.ts +550 -550
  19. package/std/assembly/arraybuffer.ts +77 -77
  20. package/std/assembly/atomics.ts +127 -127
  21. package/std/assembly/bindings/asyncify.ts +16 -16
  22. package/std/assembly/bindings/dom.ts +291 -291
  23. package/std/assembly/bindings/node.ts +6 -6
  24. package/std/assembly/bitflags.ts +53 -53
  25. package/std/assembly/builtins.ts +2650 -2650
  26. package/std/assembly/byteslice.ts +177 -177
  27. package/std/assembly/compat.ts +2 -2
  28. package/std/assembly/console.ts +42 -42
  29. package/std/assembly/crypto.ts +9 -9
  30. package/std/assembly/dataview.ts +181 -181
  31. package/std/assembly/date.ts +375 -375
  32. package/std/assembly/diagnostics.ts +11 -11
  33. package/std/assembly/encoding.ts +151 -151
  34. package/std/assembly/endian.ts +45 -45
  35. package/std/assembly/error.ts +44 -44
  36. package/std/assembly/fixedarray.ts +173 -173
  37. package/std/assembly/fixedmap.ts +326 -326
  38. package/std/assembly/fixedset.ts +275 -275
  39. package/std/assembly/function.ts +42 -42
  40. package/std/assembly/index.d.ts +2892 -2891
  41. package/std/assembly/iterator.ts +35 -35
  42. package/std/assembly/map.ts +269 -269
  43. package/std/assembly/math.ts +3289 -3289
  44. package/std/assembly/memory.ts +123 -123
  45. package/std/assembly/number.ts +388 -388
  46. package/std/assembly/object.ts +36 -36
  47. package/std/assembly/performance.ts +9 -9
  48. package/std/assembly/pointer.ts +80 -80
  49. package/std/assembly/polyfills.ts +27 -27
  50. package/std/assembly/process.ts +50 -50
  51. package/std/assembly/reference.ts +48 -48
  52. package/std/assembly/regexp.ts +12 -12
  53. package/std/assembly/rt/README.md +83 -83
  54. package/std/assembly/rt/common.ts +81 -81
  55. package/std/assembly/rt/index-incremental.ts +2 -2
  56. package/std/assembly/rt/index-memory.ts +1 -1
  57. package/std/assembly/rt/index-minimal.ts +2 -2
  58. package/std/assembly/rt/index-stub.ts +1 -1
  59. package/std/assembly/rt/index.d.ts +37 -37
  60. package/std/assembly/rt/itcms.ts +419 -419
  61. package/std/assembly/rt/memory-runtime.ts +94 -94
  62. package/std/assembly/rt/rtrace.ts +15 -15
  63. package/std/assembly/rt/stub.ts +133 -133
  64. package/std/assembly/rt/tcms.ts +254 -254
  65. package/std/assembly/rt/tlsf.ts +592 -592
  66. package/std/assembly/rt.ts +90 -90
  67. package/std/assembly/set.ts +225 -225
  68. package/std/assembly/shared/feature.ts +68 -68
  69. package/std/assembly/shared/runtime.ts +13 -13
  70. package/std/assembly/shared/target.ts +11 -11
  71. package/std/assembly/shared/tsconfig.json +11 -11
  72. package/std/assembly/shared/typeinfo.ts +72 -72
  73. package/std/assembly/staticarray.ts +423 -423
  74. package/std/assembly/string.ts +850 -850
  75. package/std/assembly/symbol.ts +114 -114
  76. package/std/assembly/table.ts +16 -16
  77. package/std/assembly/toilscript.ts +16 -0
  78. package/std/assembly/tsconfig.json +6 -6
  79. package/std/assembly/typedarray.ts +1954 -1954
  80. package/std/assembly/uri.ts +17 -17
  81. package/std/assembly/util/bytes.ts +107 -107
  82. package/std/assembly/util/casemap.ts +497 -497
  83. package/std/assembly/util/error.ts +58 -58
  84. package/std/assembly/util/hash.ts +117 -117
  85. package/std/assembly/util/math.ts +1922 -1922
  86. package/std/assembly/util/memory.ts +290 -290
  87. package/std/assembly/util/number.ts +873 -873
  88. package/std/assembly/util/sort.ts +313 -313
  89. package/std/assembly/util/string.ts +1202 -1202
  90. package/std/assembly/util/uri.ts +275 -275
  91. package/std/assembly/vector.ts +4 -4
  92. package/std/assembly.json +16 -16
  93. package/std/portable/index.d.ts +461 -461
  94. package/std/portable/index.js +416 -416
  95. package/std/portable.json +11 -11
  96. package/std/types/assembly/index.d.ts +1 -1
  97. package/std/types/assembly/package.json +2 -2
  98. package/std/types/portable/index.d.ts +1 -1
  99. package/std/types/portable/package.json +2 -2
  100. package/tsconfig-base.json +13 -13
  101. package/util/README.md +23 -23
  102. package/util/browser/fs.js +1 -1
  103. package/util/browser/module.js +5 -5
  104. package/util/browser/path.js +520 -520
  105. package/util/browser/process.js +59 -59
  106. package/util/browser/url.js +23 -23
  107. package/util/cpu.d.ts +9 -9
  108. package/util/cpu.js +42 -42
  109. package/util/find.d.ts +6 -6
  110. package/util/find.js +20 -20
  111. package/util/node.d.ts +21 -21
  112. package/util/node.js +34 -34
  113. package/util/options.d.ts +70 -70
  114. package/util/options.js +262 -262
  115. package/util/terminal.d.ts +52 -52
  116. package/util/terminal.js +35 -35
  117. package/util/text.d.ts +26 -26
  118. package/util/text.js +114 -114
  119. package/util/tsconfig.json +9 -9
  120. package/util/web.d.ts +11 -11
  121. package/util/web.js +33 -33
@@ -1,419 +1,419 @@
1
- import { BLOCK, BLOCK_OVERHEAD, OBJECT_OVERHEAD, OBJECT_MAXSIZE, TOTAL_OVERHEAD, DEBUG, TRACE, RTRACE, PROFILE } from "./common";
2
- import { onvisit, oncollect, oninterrupt, onyield } from "./rtrace";
3
- import { TypeinfoFlags } from "../shared/typeinfo";
4
- import { E_ALLOCATION_TOO_LARGE, E_ALREADY_PINNED, E_NOT_PINNED } from "../util/error";
5
-
6
- // === ITCMS: An incremental Tri-Color Mark & Sweep garbage collector ===
7
- // Adapted from Bach Le's μgc, see: https://github.com/bullno1/ugc
8
-
9
- // ╒═════════════╤══════════════ Colors ═══════════════════════════╕
10
- // │ Color │ Meaning │
11
- // ├─────────────┼─────────────────────────────────────────────────┤
12
- // │ WHITE* │ Unprocessed │
13
- // │ BLACK* │ Processed │
14
- // │ GRAY │ Processed with unprocessed children │
15
- // │ TRANSPARENT │ Manually pinned (always reachable) │
16
- // └─────────────┴─────────────────────────────────────────────────┘
17
- // * flipped between cycles
18
-
19
- // @ts-ignore: decorator
20
- @lazy let white = 0;
21
- // @ts-ignore: decorator
22
- @inline const gray = 2;
23
- // @ts-ignore: decorator
24
- @inline const transparent = 3;
25
- // @ts-ignore: decorator
26
- @inline const COLOR_MASK = 3;
27
-
28
- /** Size in memory of all objects currently managed by the GC. */
29
- // @ts-ignore: decorator
30
- @lazy let total: usize = 0;
31
-
32
- /** Currently transitioning from SWEEP to MARK state. */
33
- // @ts-ignore: decorator
34
- @inline const STATE_IDLE = 0;
35
- /** Currently marking reachable objects. */
36
- // @ts-ignore: decorator
37
- @inline const STATE_MARK = 1;
38
- /** Currently sweeping unreachable objects. */
39
- // @ts-ignore: decorator
40
- @inline const STATE_SWEEP = 2;
41
- /** Current collector state. */
42
- // @ts-ignore: decorator
43
- @lazy let state = STATE_IDLE;
44
-
45
- // @ts-ignore: decorator
46
- @lazy let fromSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
47
- // @ts-ignore: decorator
48
- @lazy let toSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
49
- // @ts-ignore: decorator
50
- @lazy let pinSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
51
- // @ts-ignore: decorator
52
- @lazy let iter: Object = changetype<Object>(0); // unsafe initializion below
53
-
54
- function initLazy(space: Object): Object {
55
- space.nextWithColor = changetype<usize>(space);
56
- space.prev = space;
57
- return space;
58
- }
59
-
60
- /** Visit cookie indicating scanning of an object. */
61
- // @ts-ignore: decorator
62
- @inline const VISIT_SCAN = 0;
63
-
64
- // ╒═══════════════ Managed object layout (32-bit) ════════════════╕
65
- // 3 2 1
66
- // 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
67
- // ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
68
- // │ Memory manager block │
69
- // ╞═══════════════════════════════════════════════════════════╤═══╡
70
- // │ next │ C │ = nextWithColor
71
- // ├───────────────────────────────────────────────────────────┴───┤
72
- // │ prev │
73
- // ├───────────────────────────────────────────────────────────────┤
74
- // │ rtId │
75
- // ├───────────────────────────────────────────────────────────────┤
76
- // │ rtSize │
77
- // ╞>ptr═══════════════════════════════════════════════════════════╡
78
- // │ ... │
79
- // C: color
80
-
81
- /** Represents a managed object in memory, consisting of a header followed by the object's data. */
82
- @unmanaged class Object extends BLOCK {
83
- /** Pointer to the next object with color flags stored in the alignment bits. */
84
- nextWithColor: usize; // *u32
85
- /** Pointer to the previous object. */
86
- prev: Object; // *u32
87
- /** Runtime id. */
88
- rtId: u32;
89
- /** Runtime size. */
90
- rtSize: u32;
91
-
92
- /** Gets the pointer to the next object. */
93
- get next(): Object {
94
- return changetype<Object>(this.nextWithColor & ~COLOR_MASK);
95
- }
96
-
97
- /** Sets the pointer to the next object. */
98
- set next(obj: Object) {
99
- this.nextWithColor = changetype<usize>(obj) | (this.nextWithColor & COLOR_MASK);
100
- }
101
-
102
- /** Gets this object's color. */
103
- get color(): i32 {
104
- return i32(this.nextWithColor & COLOR_MASK);
105
- }
106
-
107
- /** Sets this object's color. */
108
- set color(color: i32) {
109
- this.nextWithColor = (this.nextWithColor & ~COLOR_MASK) | color;
110
- }
111
-
112
- /** Gets the size of this object in memory. */
113
- get size(): usize {
114
- return BLOCK_OVERHEAD + (this.mmInfo & ~3);
115
- }
116
-
117
- /** Tests if this object is pointerfree. */
118
- get isPointerfree(): bool {
119
- let rtId = this.rtId;
120
- // 0: Object, 1: ArrayBuffer, 2: String
121
- return rtId <= idof<string>() || (__typeinfo(rtId) & TypeinfoFlags.POINTERFREE) != 0;
122
- }
123
-
124
- /** Unlinks this object from its list. */
125
- unlink(): void {
126
- let next = this.next;
127
- if (next == null) {
128
- if (DEBUG) assert(this.prev == null && changetype<usize>(this) < __heap_base);
129
- return; // static data not yet linked
130
- }
131
- let prev = this.prev;
132
- if (DEBUG) assert(prev);
133
- next.prev = prev;
134
- prev.next = next;
135
- }
136
-
137
- /** Links this object to the specified list, with the given color. */
138
- linkTo(list: Object, withColor: i32): void {
139
- let prev = list.prev;
140
- this.nextWithColor = changetype<usize>(list) | withColor;
141
- this.prev = prev;
142
- prev.next = this;
143
- list.prev = this;
144
- }
145
-
146
- /** Marks this object as gray, that is reachable with unscanned children. */
147
- makeGray(): void {
148
- if (this == iter) iter = assert(this.prev);
149
- this.unlink();
150
- this.linkTo(toSpace, this.isPointerfree ? i32(!white) : gray);
151
- }
152
- }
153
-
154
- /** Visits all objects considered to be program roots. */
155
- function visitRoots(cookie: u32): void {
156
- __visit_globals(cookie);
157
- let pn = pinSpace;
158
- let iter = pn.next;
159
- while (iter != pn) {
160
- if (DEBUG) assert(iter.color == transparent);
161
- __visit_members(changetype<usize>(iter) + TOTAL_OVERHEAD, cookie);
162
- iter = iter.next;
163
- }
164
- }
165
-
166
- /** Visits all objects on the stack. */
167
- function visitStack(cookie: u32): void {
168
- let ptr = __stack_pointer;
169
- while (ptr < __heap_base) {
170
- __visit(load<usize>(ptr), cookie);
171
- ptr += sizeof<usize>();
172
- }
173
- }
174
-
175
- /** Performs a single step according to the current state. */
176
- function step(): usize {
177
- // Magic constants responsible for pause times. Obtained experimentally
178
- // using the compiler compiling itself. 2048 budget pro run by default.
179
- const MARKCOST = isDefined(ASC_GC_MARKCOST) ? ASC_GC_MARKCOST : 1;
180
- const SWEEPCOST = isDefined(ASC_GC_SWEEPCOST) ? ASC_GC_SWEEPCOST : 10;
181
- let obj: Object;
182
- switch (state) {
183
- case STATE_IDLE: {
184
- state = STATE_MARK;
185
- visitCount = 0;
186
- visitRoots(VISIT_SCAN);
187
- iter = toSpace;
188
- return visitCount * MARKCOST;
189
- }
190
- case STATE_MARK: {
191
- let black = i32(!white);
192
- obj = iter.next;
193
- while (obj != toSpace) {
194
- iter = obj;
195
- if (obj.color != black) { // skip already-blacks (pointerfree)
196
- obj.color = black;
197
- visitCount = 0;
198
- __visit_members(changetype<usize>(obj) + TOTAL_OVERHEAD, VISIT_SCAN);
199
- return visitCount * MARKCOST;
200
- }
201
- obj = obj.next;
202
- }
203
- visitCount = 0;
204
- visitRoots(VISIT_SCAN);
205
- obj = iter.next;
206
- if (obj == toSpace) {
207
- visitStack(VISIT_SCAN);
208
- obj = iter.next;
209
- while (obj != toSpace) {
210
- if (obj.color != black) {
211
- obj.color = black;
212
- __visit_members(changetype<usize>(obj) + TOTAL_OVERHEAD, VISIT_SCAN);
213
- }
214
- obj = obj.next;
215
- }
216
- let from = fromSpace;
217
- fromSpace = toSpace;
218
- toSpace = from;
219
- white = black;
220
- iter = from.next;
221
- state = STATE_SWEEP;
222
- }
223
- return visitCount * MARKCOST;
224
- }
225
- case STATE_SWEEP: {
226
- obj = iter;
227
- if (obj != toSpace) {
228
- iter = obj.next;
229
- if (DEBUG) assert(obj.color == i32(!white)); // old white
230
- free(obj);
231
- return SWEEPCOST;
232
- }
233
- toSpace.nextWithColor = changetype<usize>(toSpace);
234
- toSpace.prev = toSpace;
235
- state = STATE_IDLE;
236
- break;
237
- }
238
- }
239
- return 0;
240
- }
241
-
242
- /** Frees an object. */
243
- function free(obj: Object): void {
244
- if (changetype<usize>(obj) < __heap_base) {
245
- obj.nextWithColor = 0; // may become linked again
246
- obj.prev = changetype<Object>(0);
247
- } else {
248
- total -= obj.size;
249
- if (isDefined(__finalize)) {
250
- __finalize(changetype<usize>(obj) + TOTAL_OVERHEAD);
251
- }
252
- __free(changetype<usize>(obj) + BLOCK_OVERHEAD);
253
- }
254
- }
255
-
256
- // Garbage collector interface
257
-
258
- // @ts-ignore: decorator
259
- @global @unsafe
260
- export function __new(size: usize, id: i32): usize {
261
- if (size >= OBJECT_MAXSIZE) throw new Error(E_ALLOCATION_TOO_LARGE);
262
- if (total >= threshold) interrupt();
263
- let obj = changetype<Object>(__alloc(OBJECT_OVERHEAD + size) - BLOCK_OVERHEAD);
264
- obj.rtId = id;
265
- obj.rtSize = <u32>size;
266
- obj.linkTo(fromSpace, white); // inits next/prev
267
- total += obj.size;
268
- let ptr = changetype<usize>(obj) + TOTAL_OVERHEAD;
269
- // may be visited before being fully initialized, so must fill
270
- memory.fill(ptr, 0, size);
271
- return ptr;
272
- }
273
-
274
- // @ts-ignore: decorator
275
- @global @unsafe
276
- export function __renew(oldPtr: usize, size: usize): usize {
277
- let oldObj = changetype<Object>(oldPtr - TOTAL_OVERHEAD);
278
- // Update object size if its block is large enough
279
- if (size <= (oldObj.mmInfo & ~3) - OBJECT_OVERHEAD) {
280
- oldObj.rtSize = <u32>size;
281
- return oldPtr;
282
- }
283
- // If not the same object anymore, we have to move it move it due to the
284
- // shadow stack potentially still referencing the old object
285
- let newPtr = __new(size, oldObj.rtId);
286
- memory.copy(newPtr, oldPtr, min(size, oldObj.rtSize));
287
- return newPtr;
288
- }
289
-
290
- // @ts-ignore: decorator
291
- @global @unsafe
292
- export function __link(parentPtr: usize, childPtr: usize, expectMultiple: bool): void {
293
- // Write barrier is unnecessary if non-incremental
294
- if (!childPtr) return;
295
- if (DEBUG) assert(parentPtr);
296
- let child = changetype<Object>(childPtr - TOTAL_OVERHEAD);
297
- if (child.color == white) {
298
- let parent = changetype<Object>(parentPtr - TOTAL_OVERHEAD);
299
- let parentColor = parent.color;
300
- if (parentColor == i32(!white)) {
301
- // Maintain the invariant that no black object may point to a white object.
302
- if (expectMultiple) {
303
- // Move the barrier "backward". Suitable for containers receiving multiple stores.
304
- // Avoids a barrier for subsequent objects stored into the same container.
305
- parent.makeGray();
306
- } else {
307
- // Move the barrier "forward". Suitable for objects receiving isolated stores.
308
- child.makeGray();
309
- }
310
- } else if (parentColor == transparent && state == STATE_MARK) {
311
- // Pinned objects are considered 'black' during the mark phase.
312
- child.makeGray();
313
- }
314
- }
315
- }
316
-
317
- // @ts-ignore: decorator
318
- @lazy let visitCount = 0;
319
-
320
- // @ts-ignore: decorator
321
- @global @unsafe
322
- export function __visit(ptr: usize, cookie: i32): void {
323
- if (!ptr) return;
324
- let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
325
- if (RTRACE) if (!onvisit(obj)) return;
326
- if (obj.color == white) {
327
- obj.makeGray();
328
- ++visitCount;
329
- }
330
- }
331
-
332
- // @ts-ignore: decorator
333
- @global @unsafe
334
- export function __pin(ptr: usize): usize {
335
- if (ptr) {
336
- let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
337
- if (obj.color == transparent) {
338
- throw new Error(E_ALREADY_PINNED);
339
- }
340
- obj.unlink(); // from fromSpace
341
- obj.linkTo(pinSpace, transparent);
342
- }
343
- return ptr;
344
- }
345
-
346
- // @ts-ignore: decorator
347
- @global @unsafe
348
- export function __unpin(ptr: usize): void {
349
- if (!ptr) return;
350
- let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
351
- if (obj.color != transparent) {
352
- throw new Error(E_NOT_PINNED);
353
- }
354
- if (state == STATE_MARK) {
355
- // We may be right at the point after marking roots for the second time and
356
- // entering the sweep phase, in which case the object would be missed if it
357
- // is not only pinned but also a root. Make sure it isn't missed.
358
- obj.makeGray();
359
- } else {
360
- obj.unlink();
361
- obj.linkTo(fromSpace, white);
362
- }
363
- }
364
-
365
- // @ts-ignore: decorator
366
- @global @unsafe
367
- export function __collect(): void {
368
- if (TRACE) trace("GC (full) at", 1, total);
369
- if (state > STATE_IDLE) {
370
- // finish current cycle
371
- while (state != STATE_IDLE) step();
372
- }
373
- // perform a full cycle
374
- step();
375
- while (state != STATE_IDLE) step();
376
- threshold = <usize>(<u64>total * IDLEFACTOR / 100) + GRANULARITY;
377
- if (TRACE) trace("GC (full) done at cur/max", 2, total, memory.size() << 16);
378
- if (RTRACE || PROFILE) oncollect(total);
379
- }
380
-
381
- // Garbage collector automation
382
-
383
- /** How often to interrupt. The default of 1024 means "interrupt each 1024 bytes allocated". */
384
- // @ts-ignore: decorator
385
- @inline const GRANULARITY: usize = isDefined(ASC_GC_GRANULARITY) ? ASC_GC_GRANULARITY : 1024;
386
- /** How long to interrupt. The default of 200% means "run at double the speed of allocations". */
387
- // @ts-ignore: decorator
388
- @inline const STEPFACTOR: usize = isDefined(ASC_GC_SWEEPFACTOR) ? ASC_GC_SWEEPFACTOR : 200;
389
- /** How long to idle. The default of 200% means "wait for memory to double before kicking in again". */
390
- // @ts-ignore: decorator
391
- @inline const IDLEFACTOR: usize = isDefined(ASC_GC_IDLEFACTOR) ? ASC_GC_IDLEFACTOR : 200;
392
-
393
- /** Threshold of memory used by objects to exceed before interrupting again. */
394
- // @ts-ignore: decorator
395
- @lazy let threshold: usize = ((<usize>memory.size() << 16) - __heap_base) >> 1;
396
-
397
- /** Performs a reasonable amount of incremental GC steps. */
398
- function interrupt(): void {
399
- if (PROFILE) oninterrupt(total);
400
- if (TRACE) trace("GC (auto) at", 1, total);
401
- let budget: isize = GRANULARITY * STEPFACTOR / 100;
402
- do {
403
- budget -= step();
404
- if (state == STATE_IDLE) {
405
- if (TRACE) trace("└ GC (auto) done at cur/max", 2, total, memory.size() << 16);
406
- if (IDLEFACTOR % 100 == 0) {
407
- // optimization for the default GC parameters which idle factor is 200%
408
- threshold = total * (IDLEFACTOR / 100) + GRANULARITY;
409
- } else {
410
- threshold = <usize>((<u64>total * IDLEFACTOR) / 100) + GRANULARITY;
411
- }
412
- if (PROFILE) onyield(total);
413
- return;
414
- }
415
- } while (budget > 0);
416
- if (TRACE) trace("└ GC (auto) ongoing at", 1, total);
417
- threshold = total + GRANULARITY * usize(total - threshold < GRANULARITY);
418
- if (PROFILE) onyield(total);
419
- }
1
+ import { BLOCK, BLOCK_OVERHEAD, OBJECT_OVERHEAD, OBJECT_MAXSIZE, TOTAL_OVERHEAD, DEBUG, TRACE, RTRACE, PROFILE } from "./common";
2
+ import { onvisit, oncollect, oninterrupt, onyield } from "./rtrace";
3
+ import { TypeinfoFlags } from "../shared/typeinfo";
4
+ import { E_ALLOCATION_TOO_LARGE, E_ALREADY_PINNED, E_NOT_PINNED } from "../util/error";
5
+
6
+ // === ITCMS: An incremental Tri-Color Mark & Sweep garbage collector ===
7
+ // Adapted from Bach Le's μgc, see: https://github.com/bullno1/ugc
8
+
9
+ // ╒═════════════╤══════════════ Colors ═══════════════════════════╕
10
+ // │ Color │ Meaning │
11
+ // ├─────────────┼─────────────────────────────────────────────────┤
12
+ // │ WHITE* │ Unprocessed │
13
+ // │ BLACK* │ Processed │
14
+ // │ GRAY │ Processed with unprocessed children │
15
+ // │ TRANSPARENT │ Manually pinned (always reachable) │
16
+ // └─────────────┴─────────────────────────────────────────────────┘
17
+ // * flipped between cycles
18
+
19
+ // @ts-ignore: decorator
20
+ @lazy let white = 0;
21
+ // @ts-ignore: decorator
22
+ @inline const gray = 2;
23
+ // @ts-ignore: decorator
24
+ @inline const transparent = 3;
25
+ // @ts-ignore: decorator
26
+ @inline const COLOR_MASK = 3;
27
+
28
+ /** Size in memory of all objects currently managed by the GC. */
29
+ // @ts-ignore: decorator
30
+ @lazy let total: usize = 0;
31
+
32
+ /** Currently transitioning from SWEEP to MARK state. */
33
+ // @ts-ignore: decorator
34
+ @inline const STATE_IDLE = 0;
35
+ /** Currently marking reachable objects. */
36
+ // @ts-ignore: decorator
37
+ @inline const STATE_MARK = 1;
38
+ /** Currently sweeping unreachable objects. */
39
+ // @ts-ignore: decorator
40
+ @inline const STATE_SWEEP = 2;
41
+ /** Current collector state. */
42
+ // @ts-ignore: decorator
43
+ @lazy let state = STATE_IDLE;
44
+
45
+ // @ts-ignore: decorator
46
+ @lazy let fromSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
47
+ // @ts-ignore: decorator
48
+ @lazy let toSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
49
+ // @ts-ignore: decorator
50
+ @lazy let pinSpace = initLazy(changetype<Object>(memory.data(offsetof<Object>())));
51
+ // @ts-ignore: decorator
52
+ @lazy let iter: Object = changetype<Object>(0); // unsafe initializion below
53
+
54
+ function initLazy(space: Object): Object {
55
+ space.nextWithColor = changetype<usize>(space);
56
+ space.prev = space;
57
+ return space;
58
+ }
59
+
60
+ /** Visit cookie indicating scanning of an object. */
61
+ // @ts-ignore: decorator
62
+ @inline const VISIT_SCAN = 0;
63
+
64
+ // ╒═══════════════ Managed object layout (32-bit) ════════════════╕
65
+ // 3 2 1
66
+ // 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
67
+ // ├─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┤
68
+ // │ Memory manager block │
69
+ // ╞═══════════════════════════════════════════════════════════╤═══╡
70
+ // │ next │ C │ = nextWithColor
71
+ // ├───────────────────────────────────────────────────────────┴───┤
72
+ // │ prev │
73
+ // ├───────────────────────────────────────────────────────────────┤
74
+ // │ rtId │
75
+ // ├───────────────────────────────────────────────────────────────┤
76
+ // │ rtSize │
77
+ // ╞>ptr═══════════════════════════════════════════════════════════╡
78
+ // │ ... │
79
+ // C: color
80
+
81
+ /** Represents a managed object in memory, consisting of a header followed by the object's data. */
82
+ @unmanaged class Object extends BLOCK {
83
+ /** Pointer to the next object with color flags stored in the alignment bits. */
84
+ nextWithColor: usize; // *u32
85
+ /** Pointer to the previous object. */
86
+ prev: Object; // *u32
87
+ /** Runtime id. */
88
+ rtId: u32;
89
+ /** Runtime size. */
90
+ rtSize: u32;
91
+
92
+ /** Gets the pointer to the next object. */
93
+ get next(): Object {
94
+ return changetype<Object>(this.nextWithColor & ~COLOR_MASK);
95
+ }
96
+
97
+ /** Sets the pointer to the next object. */
98
+ set next(obj: Object) {
99
+ this.nextWithColor = changetype<usize>(obj) | (this.nextWithColor & COLOR_MASK);
100
+ }
101
+
102
+ /** Gets this object's color. */
103
+ get color(): i32 {
104
+ return i32(this.nextWithColor & COLOR_MASK);
105
+ }
106
+
107
+ /** Sets this object's color. */
108
+ set color(color: i32) {
109
+ this.nextWithColor = (this.nextWithColor & ~COLOR_MASK) | color;
110
+ }
111
+
112
+ /** Gets the size of this object in memory. */
113
+ get size(): usize {
114
+ return BLOCK_OVERHEAD + (this.mmInfo & ~3);
115
+ }
116
+
117
+ /** Tests if this object is pointerfree. */
118
+ get isPointerfree(): bool {
119
+ let rtId = this.rtId;
120
+ // 0: Object, 1: ArrayBuffer, 2: String
121
+ return rtId <= idof<string>() || (__typeinfo(rtId) & TypeinfoFlags.POINTERFREE) != 0;
122
+ }
123
+
124
+ /** Unlinks this object from its list. */
125
+ unlink(): void {
126
+ let next = this.next;
127
+ if (next == null) {
128
+ if (DEBUG) assert(this.prev == null && changetype<usize>(this) < __heap_base);
129
+ return; // static data not yet linked
130
+ }
131
+ let prev = this.prev;
132
+ if (DEBUG) assert(prev);
133
+ next.prev = prev;
134
+ prev.next = next;
135
+ }
136
+
137
+ /** Links this object to the specified list, with the given color. */
138
+ linkTo(list: Object, withColor: i32): void {
139
+ let prev = list.prev;
140
+ this.nextWithColor = changetype<usize>(list) | withColor;
141
+ this.prev = prev;
142
+ prev.next = this;
143
+ list.prev = this;
144
+ }
145
+
146
+ /** Marks this object as gray, that is reachable with unscanned children. */
147
+ makeGray(): void {
148
+ if (this == iter) iter = assert(this.prev);
149
+ this.unlink();
150
+ this.linkTo(toSpace, this.isPointerfree ? i32(!white) : gray);
151
+ }
152
+ }
153
+
154
+ /** Visits all objects considered to be program roots. */
155
+ function visitRoots(cookie: u32): void {
156
+ __visit_globals(cookie);
157
+ let pn = pinSpace;
158
+ let iter = pn.next;
159
+ while (iter != pn) {
160
+ if (DEBUG) assert(iter.color == transparent);
161
+ __visit_members(changetype<usize>(iter) + TOTAL_OVERHEAD, cookie);
162
+ iter = iter.next;
163
+ }
164
+ }
165
+
166
+ /** Visits all objects on the stack. */
167
+ function visitStack(cookie: u32): void {
168
+ let ptr = __stack_pointer;
169
+ while (ptr < __heap_base) {
170
+ __visit(load<usize>(ptr), cookie);
171
+ ptr += sizeof<usize>();
172
+ }
173
+ }
174
+
175
+ /** Performs a single step according to the current state. */
176
+ function step(): usize {
177
+ // Magic constants responsible for pause times. Obtained experimentally
178
+ // using the compiler compiling itself. 2048 budget pro run by default.
179
+ const MARKCOST = isDefined(ASC_GC_MARKCOST) ? ASC_GC_MARKCOST : 1;
180
+ const SWEEPCOST = isDefined(ASC_GC_SWEEPCOST) ? ASC_GC_SWEEPCOST : 10;
181
+ let obj: Object;
182
+ switch (state) {
183
+ case STATE_IDLE: {
184
+ state = STATE_MARK;
185
+ visitCount = 0;
186
+ visitRoots(VISIT_SCAN);
187
+ iter = toSpace;
188
+ return visitCount * MARKCOST;
189
+ }
190
+ case STATE_MARK: {
191
+ let black = i32(!white);
192
+ obj = iter.next;
193
+ while (obj != toSpace) {
194
+ iter = obj;
195
+ if (obj.color != black) { // skip already-blacks (pointerfree)
196
+ obj.color = black;
197
+ visitCount = 0;
198
+ __visit_members(changetype<usize>(obj) + TOTAL_OVERHEAD, VISIT_SCAN);
199
+ return visitCount * MARKCOST;
200
+ }
201
+ obj = obj.next;
202
+ }
203
+ visitCount = 0;
204
+ visitRoots(VISIT_SCAN);
205
+ obj = iter.next;
206
+ if (obj == toSpace) {
207
+ visitStack(VISIT_SCAN);
208
+ obj = iter.next;
209
+ while (obj != toSpace) {
210
+ if (obj.color != black) {
211
+ obj.color = black;
212
+ __visit_members(changetype<usize>(obj) + TOTAL_OVERHEAD, VISIT_SCAN);
213
+ }
214
+ obj = obj.next;
215
+ }
216
+ let from = fromSpace;
217
+ fromSpace = toSpace;
218
+ toSpace = from;
219
+ white = black;
220
+ iter = from.next;
221
+ state = STATE_SWEEP;
222
+ }
223
+ return visitCount * MARKCOST;
224
+ }
225
+ case STATE_SWEEP: {
226
+ obj = iter;
227
+ if (obj != toSpace) {
228
+ iter = obj.next;
229
+ if (DEBUG) assert(obj.color == i32(!white)); // old white
230
+ free(obj);
231
+ return SWEEPCOST;
232
+ }
233
+ toSpace.nextWithColor = changetype<usize>(toSpace);
234
+ toSpace.prev = toSpace;
235
+ state = STATE_IDLE;
236
+ break;
237
+ }
238
+ }
239
+ return 0;
240
+ }
241
+
242
+ /** Frees an object. */
243
+ function free(obj: Object): void {
244
+ if (changetype<usize>(obj) < __heap_base) {
245
+ obj.nextWithColor = 0; // may become linked again
246
+ obj.prev = changetype<Object>(0);
247
+ } else {
248
+ total -= obj.size;
249
+ if (isDefined(__finalize)) {
250
+ __finalize(changetype<usize>(obj) + TOTAL_OVERHEAD);
251
+ }
252
+ __free(changetype<usize>(obj) + BLOCK_OVERHEAD);
253
+ }
254
+ }
255
+
256
+ // Garbage collector interface
257
+
258
+ // @ts-ignore: decorator
259
+ @global @unsafe
260
+ export function __new(size: usize, id: i32): usize {
261
+ if (size >= OBJECT_MAXSIZE) throw new Error(E_ALLOCATION_TOO_LARGE);
262
+ if (total >= threshold) interrupt();
263
+ let obj = changetype<Object>(__alloc(OBJECT_OVERHEAD + size) - BLOCK_OVERHEAD);
264
+ obj.rtId = id;
265
+ obj.rtSize = <u32>size;
266
+ obj.linkTo(fromSpace, white); // inits next/prev
267
+ total += obj.size;
268
+ let ptr = changetype<usize>(obj) + TOTAL_OVERHEAD;
269
+ // may be visited before being fully initialized, so must fill
270
+ memory.fill(ptr, 0, size);
271
+ return ptr;
272
+ }
273
+
274
+ // @ts-ignore: decorator
275
+ @global @unsafe
276
+ export function __renew(oldPtr: usize, size: usize): usize {
277
+ let oldObj = changetype<Object>(oldPtr - TOTAL_OVERHEAD);
278
+ // Update object size if its block is large enough
279
+ if (size <= (oldObj.mmInfo & ~3) - OBJECT_OVERHEAD) {
280
+ oldObj.rtSize = <u32>size;
281
+ return oldPtr;
282
+ }
283
+ // If not the same object anymore, we have to move it move it due to the
284
+ // shadow stack potentially still referencing the old object
285
+ let newPtr = __new(size, oldObj.rtId);
286
+ memory.copy(newPtr, oldPtr, min(size, oldObj.rtSize));
287
+ return newPtr;
288
+ }
289
+
290
+ // @ts-ignore: decorator
291
+ @global @unsafe
292
+ export function __link(parentPtr: usize, childPtr: usize, expectMultiple: bool): void {
293
+ // Write barrier is unnecessary if non-incremental
294
+ if (!childPtr) return;
295
+ if (DEBUG) assert(parentPtr);
296
+ let child = changetype<Object>(childPtr - TOTAL_OVERHEAD);
297
+ if (child.color == white) {
298
+ let parent = changetype<Object>(parentPtr - TOTAL_OVERHEAD);
299
+ let parentColor = parent.color;
300
+ if (parentColor == i32(!white)) {
301
+ // Maintain the invariant that no black object may point to a white object.
302
+ if (expectMultiple) {
303
+ // Move the barrier "backward". Suitable for containers receiving multiple stores.
304
+ // Avoids a barrier for subsequent objects stored into the same container.
305
+ parent.makeGray();
306
+ } else {
307
+ // Move the barrier "forward". Suitable for objects receiving isolated stores.
308
+ child.makeGray();
309
+ }
310
+ } else if (parentColor == transparent && state == STATE_MARK) {
311
+ // Pinned objects are considered 'black' during the mark phase.
312
+ child.makeGray();
313
+ }
314
+ }
315
+ }
316
+
317
+ // @ts-ignore: decorator
318
+ @lazy let visitCount = 0;
319
+
320
+ // @ts-ignore: decorator
321
+ @global @unsafe
322
+ export function __visit(ptr: usize, cookie: i32): void {
323
+ if (!ptr) return;
324
+ let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
325
+ if (RTRACE) if (!onvisit(obj)) return;
326
+ if (obj.color == white) {
327
+ obj.makeGray();
328
+ ++visitCount;
329
+ }
330
+ }
331
+
332
+ // @ts-ignore: decorator
333
+ @global @unsafe
334
+ export function __pin(ptr: usize): usize {
335
+ if (ptr) {
336
+ let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
337
+ if (obj.color == transparent) {
338
+ throw new Error(E_ALREADY_PINNED);
339
+ }
340
+ obj.unlink(); // from fromSpace
341
+ obj.linkTo(pinSpace, transparent);
342
+ }
343
+ return ptr;
344
+ }
345
+
346
+ // @ts-ignore: decorator
347
+ @global @unsafe
348
+ export function __unpin(ptr: usize): void {
349
+ if (!ptr) return;
350
+ let obj = changetype<Object>(ptr - TOTAL_OVERHEAD);
351
+ if (obj.color != transparent) {
352
+ throw new Error(E_NOT_PINNED);
353
+ }
354
+ if (state == STATE_MARK) {
355
+ // We may be right at the point after marking roots for the second time and
356
+ // entering the sweep phase, in which case the object would be missed if it
357
+ // is not only pinned but also a root. Make sure it isn't missed.
358
+ obj.makeGray();
359
+ } else {
360
+ obj.unlink();
361
+ obj.linkTo(fromSpace, white);
362
+ }
363
+ }
364
+
365
+ // @ts-ignore: decorator
366
+ @global @unsafe
367
+ export function __collect(): void {
368
+ if (TRACE) trace("GC (full) at", 1, total);
369
+ if (state > STATE_IDLE) {
370
+ // finish current cycle
371
+ while (state != STATE_IDLE) step();
372
+ }
373
+ // perform a full cycle
374
+ step();
375
+ while (state != STATE_IDLE) step();
376
+ threshold = <usize>(<u64>total * IDLEFACTOR / 100) + GRANULARITY;
377
+ if (TRACE) trace("GC (full) done at cur/max", 2, total, memory.size() << 16);
378
+ if (RTRACE || PROFILE) oncollect(total);
379
+ }
380
+
381
+ // Garbage collector automation
382
+
383
+ /** How often to interrupt. The default of 1024 means "interrupt each 1024 bytes allocated". */
384
+ // @ts-ignore: decorator
385
+ @inline const GRANULARITY: usize = isDefined(ASC_GC_GRANULARITY) ? ASC_GC_GRANULARITY : 1024;
386
+ /** How long to interrupt. The default of 200% means "run at double the speed of allocations". */
387
+ // @ts-ignore: decorator
388
+ @inline const STEPFACTOR: usize = isDefined(ASC_GC_SWEEPFACTOR) ? ASC_GC_SWEEPFACTOR : 200;
389
+ /** How long to idle. The default of 200% means "wait for memory to double before kicking in again". */
390
+ // @ts-ignore: decorator
391
+ @inline const IDLEFACTOR: usize = isDefined(ASC_GC_IDLEFACTOR) ? ASC_GC_IDLEFACTOR : 200;
392
+
393
+ /** Threshold of memory used by objects to exceed before interrupting again. */
394
+ // @ts-ignore: decorator
395
+ @lazy let threshold: usize = ((<usize>memory.size() << 16) - __heap_base) >> 1;
396
+
397
+ /** Performs a reasonable amount of incremental GC steps. */
398
+ function interrupt(): void {
399
+ if (PROFILE) oninterrupt(total);
400
+ if (TRACE) trace("GC (auto) at", 1, total);
401
+ let budget: isize = GRANULARITY * STEPFACTOR / 100;
402
+ do {
403
+ budget -= step();
404
+ if (state == STATE_IDLE) {
405
+ if (TRACE) trace("└ GC (auto) done at cur/max", 2, total, memory.size() << 16);
406
+ if (IDLEFACTOR % 100 == 0) {
407
+ // optimization for the default GC parameters which idle factor is 200%
408
+ threshold = total * (IDLEFACTOR / 100) + GRANULARITY;
409
+ } else {
410
+ threshold = <usize>((<u64>total * IDLEFACTOR) / 100) + GRANULARITY;
411
+ }
412
+ if (PROFILE) onyield(total);
413
+ return;
414
+ }
415
+ } while (budget > 0);
416
+ if (TRACE) trace("└ GC (auto) ongoing at", 1, total);
417
+ threshold = total + GRANULARITY * usize(total - threshold < GRANULARITY);
418
+ if (PROFILE) onyield(total);
419
+ }