MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve gc mmap allocator

+127 -148
+5 -7
examples/test262/index.js
··· 285 285 needsRender = true; 286 286 break; 287 287 case 'g': 288 - state.index = 0; 288 + if (state.mode === 'memory') { 289 + Ant.gc(); 290 + } else { 291 + state.index = 0; 292 + } 289 293 needsRender = true; 290 294 break; 291 295 case 'G': ··· 317 321 case 'm': 318 322 state.mode = state.mode === 'memory' ? 'browse' : 'memory'; 319 323 needsRender = true; 320 - break; 321 - case 'g': 322 - if (state.mode === 'memory') { 323 - Ant.gc(); 324 - needsRender = true; 325 - } 326 324 break; 327 325 case '/': 328 326 if (state.mode === 'browse') {
+34 -34
src/gc.c
··· 6 6 #include <stdlib.h> 7 7 #include <stdio.h> 8 8 9 + #ifdef _WIN32 10 + #include <windows.h> 11 + static inline void *gc_mmap(size_t size) { 12 + return VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); 13 + } 14 + static inline void gc_munmap(void *ptr, size_t size) { 15 + (void)size; 16 + VirtualFree(ptr, 0, MEM_RELEASE); 17 + } 18 + #else 19 + #include <sys/mman.h> 20 + static inline void *gc_mmap(size_t size) { 21 + void *p = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 22 + return (p == MAP_FAILED) ? NULL : p; 23 + } 24 + static inline void gc_munmap(void *ptr, size_t size) { 25 + munmap(ptr, size); 26 + } 27 + #endif 28 + 9 29 #define MCO_API extern 10 30 #include "minicoro.h" 11 31 12 - #define GC_MIN_HEAP_SIZE (64 * 1024) 13 - #define GC_SHRINK_THRESHOLD 4 14 32 #define GC_FWD_LOAD_FACTOR 70 33 + 34 + static uint8_t *gc_scratch_buf = NULL; 35 + static size_t gc_scratch_size = 0; 15 36 16 37 #define FWD_EMPTY ((jsoff_t)~0) 17 38 #define FWD_TOMBSTONE ((jsoff_t)~1) ··· 492 513 if (in_coroutine || js_has_pending_coroutines()) return 0; 493 514 494 515 size_t old_brk = js->brk; 495 - size_t old_size = js->size; 496 - size_t new_size = old_size; 516 + size_t new_size = js->size; 497 517 498 - uint8_t *new_mem = (uint8_t *)ant_calloc(new_size); 499 - if (!new_mem) return 0; 518 + if (new_size > gc_scratch_size) { 519 + if (gc_scratch_buf) gc_munmap(gc_scratch_buf, gc_scratch_size); 520 + gc_scratch_buf = (uint8_t *)gc_mmap(new_size); 521 + gc_scratch_size = gc_scratch_buf ? new_size : 0; 522 + } 523 + if (!gc_scratch_buf) return 0; 524 + uint8_t *new_mem = gc_scratch_buf; 500 525 memset(new_mem, 0, new_size); 501 526 502 - size_t bitmap_size = (js->brk / 4 + 7) / 8 + 1; 527 + size_t bitmap_size = (js->brk / 8 + 7) / 8 + 1; 503 528 uint8_t *mark_bits = (uint8_t *)calloc(1, bitmap_size); 504 - if (!mark_bits) { 505 - free(new_mem); 506 - return 0; 507 - } 529 + if (!mark_bits) return 0; 508 530 509 531 size_t estimated_objs = js->brk / 64; 510 532 if (estimated_objs < 256) estimated_objs = 256; ··· 517 539 ctx.mark_bits = mark_bits; 518 540 519 541 if (!fwd_init(&ctx.fwd, estimated_objs)) { 520 - free(new_mem); 521 542 free(mark_bits); 522 543 return 0; 523 544 } 524 545 525 546 if (!work_init(&ctx.work, estimated_objs / 4 < 64 ? 64 : estimated_objs / 4)) { 526 547 fwd_free(&ctx.fwd); 527 - free(new_mem); 528 548 free(mark_bits); 529 549 return 0; 530 550 } ··· 554 574 free(mark_bits); 555 575 work_free(&ctx.work); 556 576 fwd_free(&ctx.fwd); 557 - free(new_mem); 558 577 return 0; 559 578 } 560 579 ··· 566 585 js->tval = gc_apply_val(&ctx, js->tval); 567 586 js_gc_update_roots(js, gc_apply_off_callback, gc_apply_val_callback, &ctx); 568 587 569 - uint8_t *old_mem = js->mem; 570 - js->mem = new_mem; 588 + memcpy(js->mem, new_mem, ctx.new_brk); 571 589 js->brk = ctx.new_brk; 572 - 573 - free(old_mem); 574 - 575 - size_t used = ctx.new_brk; 576 - size_t shrunk_size = used * 2; 577 - if (shrunk_size < GC_MIN_HEAP_SIZE) shrunk_size = GC_MIN_HEAP_SIZE; 578 - shrunk_size = (shrunk_size + 7) & ~7; 579 - 580 - if (old_size >= GC_SHRINK_THRESHOLD * shrunk_size && shrunk_size < old_size) { 581 - uint8_t *shrunk_mem = (uint8_t *)ant_calloc(shrunk_size); 582 - if (shrunk_mem) { 583 - memcpy(shrunk_mem, js->mem, used); 584 - memset(shrunk_mem + used, 0, shrunk_size - used); 585 - free(js->mem); 586 - js->mem = shrunk_mem; 587 - js->size = (jsoff_t)shrunk_size; 588 - } 589 - } 590 590 591 591 free(mark_bits); 592 592 work_free(&ctx.work);
+6 -10
tests/bench_gc.js
··· 105 105 106 106 console.log('=== GC Benchmark ===\n'); 107 107 108 - let initial = Ant.alloc(); 108 + let initial = Ant.stats(); 109 109 console.log('Initial state:'); 110 - console.log(' arenaSize:', fmt(initial.arenaSize)); 111 - console.log(' heapSize:', fmt(initial.heapSize)); 112 - console.log(' usedBytes:', fmt(initial.usedBytes)); 113 - console.log(' totalBytes:', fmt(initial.totalBytes)); 110 + console.log(' arenaUsed:', fmt(initial.arenaUsed)); 111 + console.log(' rss:', fmt(initial.rss)); 114 112 console.log(''); 115 113 116 114 bench('Many small objects (10k objects, 5k garbage)', manySmallObjects, 5); ··· 120 118 bench('Mixed workload', mixedWorkload, 5); 121 119 bench('Repeated GC cycles (10 cycles x 1000 objects)', repeatedGcCycles, 3); 122 120 123 - let final = Ant.alloc(); 121 + let final = Ant.stats(); 124 122 console.log('Final state:'); 125 - console.log(' arenaSize:', fmt(final.arenaSize)); 126 - console.log(' heapSize:', fmt(final.heapSize)); 127 - console.log(' usedBytes:', fmt(final.usedBytes)); 128 - console.log(' totalBytes:', fmt(final.totalBytes)); 123 + console.log(' arenaUsed:', fmt(final.arenaUsed)); 124 + console.log(' rss:', fmt(final.rss));
+24 -58
tests/test_gc.js
··· 4 4 return (bytes / 1024 / 1024).toFixed(2) + ' MB'; 5 5 } 6 6 7 - console.log('=== Testing Ant.alloc() (bdwgc) ==='); 8 - let alloc1 = Ant.alloc(); 9 - console.log('Initial allocation:'); 10 - console.log(' heapSize:', fmt(alloc1.heapSize)); 11 - console.log(' usedBytes:', fmt(alloc1.usedBytes)); 12 - console.log(' freeBytes:', fmt(alloc1.freeBytes)); 13 - console.log(' totalBytes:', fmt(alloc1.totalBytes)); 7 + console.log('=== Testing Ant.stats() ==='); 8 + let stats1 = Ant.stats(); 9 + console.log('Initial:'); 10 + console.log(' arenaUsed:', fmt(stats1.arenaUsed)); 11 + console.log(' arenaSize:', fmt(stats1.arenaSize)); 12 + console.log(' rss:', fmt(stats1.rss)); 14 13 15 14 console.log('\n=== Creating objects to allocate memory ==='); 16 15 let arr = []; ··· 19 18 } 20 19 console.log('Created array with 100 objects'); 21 20 22 - let alloc2 = Ant.alloc(); 21 + let stats2 = Ant.stats(); 23 22 console.log('After allocation:'); 24 - console.log(' heapSize:', fmt(alloc2.heapSize)); 25 - console.log(' usedBytes:', fmt(alloc2.usedBytes)); 26 - console.log(' totalBytes:', fmt(alloc2.totalBytes)); 27 - console.log(' totalBytes increase:', fmt(alloc2.totalBytes - alloc1.totalBytes)); 28 - 29 - console.log('\n=== Testing Ant.stats() ==='); 30 - let stats1 = Ant.stats(); 31 - console.log('Memory stats:'); 32 - console.log(' arenaUsed:', fmt(stats1.arenaUsed)); 33 - console.log(' cstack:', fmt(stats1.cstack)); 34 - console.log(' gcHeapSize:', fmt(stats1.gcHeapSize)); 35 - console.log(' gcUsedBytes:', fmt(stats1.gcUsedBytes)); 36 - console.log(' gcFreeBytes:', fmt(stats1.gcFreeBytes)); 23 + console.log(' arenaUsed:', fmt(stats2.arenaUsed)); 24 + console.log(' arenaSize:', fmt(stats2.arenaSize)); 25 + console.log(' rss:', fmt(stats2.rss)); 26 + console.log(' arenaUsed increase:', fmt(stats2.arenaUsed - stats1.arenaUsed)); 37 27 38 28 console.log('\n=== Testing Ant.gc() ==='); 39 29 arr = null; 40 30 41 31 let gcResult = Ant.gc(); 42 32 console.log('GC result:'); 43 - console.log(' heapBefore:', fmt(gcResult.heapBefore)); 44 - console.log(' heapAfter:', fmt(gcResult.heapAfter)); 45 - console.log(' usedBefore:', fmt(gcResult.usedBefore)); 46 - console.log(' usedAfter:', fmt(gcResult.usedAfter)); 47 - console.log(' freed:', fmt(gcResult.freed)); 48 33 console.log(' arenaBefore:', fmt(gcResult.arenaBefore)); 49 34 console.log(' arenaAfter:', fmt(gcResult.arenaAfter)); 50 35 console.log(' arenaFreed:', fmt(gcResult.arenaFreed)); 51 36 52 - console.log('\n=== Testing Ant.stats() ==='); 53 - let stats2 = Ant.stats(); 37 + console.log('\n=== Stats after GC ==='); 38 + let stats3 = Ant.stats(); 54 39 console.log('Memory stats:'); 55 - console.log(' arenaUsed:', fmt(stats2.arenaUsed)); 56 - console.log(' cstack:', fmt(stats2.cstack)); 57 - console.log(' gcHeapSize:', fmt(stats2.gcHeapSize)); 58 - console.log(' gcUsedBytes:', fmt(stats2.gcUsedBytes)); 59 - console.log(' gcFreeBytes:', fmt(stats2.gcFreeBytes)); 60 - 61 - console.log('\n=== Verifying memory after GC ==='); 62 - let alloc3 = Ant.alloc(); 63 - console.log('After GC:'); 64 - console.log(' heapSize:', fmt(alloc3.heapSize)); 65 - console.log(' usedBytes:', fmt(alloc3.usedBytes)); 66 - console.log(' freeBytes:', fmt(alloc3.freeBytes)); 40 + console.log(' arenaUsed:', fmt(stats3.arenaUsed)); 41 + console.log(' arenaSize:', fmt(stats3.arenaSize)); 42 + console.log(' rss:', fmt(stats3.rss)); 67 43 68 44 console.log('\n=== Testing multiple GC cycles ==='); 69 45 for (let cycle = 0; cycle < 3; cycle = cycle + 1) { ··· 73 49 for (let i = 0; i < 50; i = i + 1) { 74 50 temp.push({ data: 'test data ' + i }); 75 51 } 76 - let beforeGc = Ant.alloc(); 77 - console.log(' Before GC - usedBytes:', fmt(beforeGc.usedBytes)); 52 + let before = Ant.stats(); 53 + console.log(' Before GC - arenaUsed:', fmt(before.arenaUsed)); 78 54 79 55 temp = null; 80 56 let gc = Ant.gc(); 81 - console.log(' After GC - usedAfter:', fmt(gc.usedAfter), 'freed:', fmt(gc.freed), 'arenaFreed:', fmt(gc.arenaFreed)); 57 + console.log(' After GC - arenaFreed:', fmt(gc.arenaFreed)); 82 58 } 83 59 84 - console.log('\n=== Testing Ant.stats() ==='); 85 - let stats3 = Ant.stats(); 60 + console.log('\n=== Final stats ==='); 61 + let stats4 = Ant.stats(); 86 62 console.log('Memory stats:'); 87 - console.log(' arenaUsed:', fmt(stats3.arenaUsed)); 88 - console.log(' cstack:', fmt(stats3.cstack)); 89 - console.log(' gcHeapSize:', fmt(stats3.gcHeapSize)); 90 - console.log(' gcUsedBytes:', fmt(stats3.gcUsedBytes)); 91 - console.log(' gcFreeBytes:', fmt(stats3.gcFreeBytes)); 92 - 93 - console.log('\n=== Testing stats consistency ==='); 94 - let statsA = Ant.stats(); 95 - let allocA = Ant.alloc(); 96 - console.log('Stats and alloc should match:'); 97 - console.log(' stats.gcUsedBytes:', fmt(statsA.gcUsedBytes)); 98 - console.log(' alloc.usedBytes:', fmt(allocA.usedBytes)); 99 - console.log(' match:', statsA.gcUsedBytes === allocA.usedBytes); 63 + console.log(' arenaUsed:', fmt(stats4.arenaUsed)); 64 + console.log(' arenaSize:', fmt(stats4.arenaSize)); 65 + console.log(' rss:', fmt(stats4.rss)); 100 66 101 67 console.log('\n=== Test complete ===');
+33 -18
tests/test_gc_comprehensive.js
··· 1 1 console.log('=== Comprehensive GC Test ==='); 2 2 console.log('Starting...\n'); 3 3 4 + function fmt(bytes) { 5 + if (bytes < 1024) return bytes + ' B'; 6 + if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; 7 + return (bytes / 1024 / 1024).toFixed(2) + ' MB'; 8 + } 9 + 4 10 let failures = 0; 5 11 function assert(condition, msg) { 6 12 if (!condition) { ··· 51 57 let descObj = {}; 52 58 let _hidden = 'initial'; 53 59 Object.defineProperty(descObj, 'prop', { 54 - get: function() { return _hidden; }, 55 - set: function(v) { _hidden = v; }, 60 + get: function () { 61 + return _hidden; 62 + }, 63 + set: function (v) { 64 + _hidden = v; 65 + }, 56 66 enumerable: true, 57 67 configurable: true 58 68 }); ··· 67 77 console.log('Test 5: Promises'); 68 78 let promiseResolved = false; 69 79 let promiseValue = null; 70 - let p = new Promise((resolve) => { 80 + let p = new Promise(resolve => { 71 81 resolve({ result: 'success' }); 72 82 }); 73 - p.then((val) => { 83 + p.then(val => { 74 84 promiseResolved = true; 75 85 promiseValue = val; 76 86 }); 77 87 Ant.gc(); 78 - // Run microtasks 79 88 console.log(' Waiting for promise...'); 80 89 81 90 // Test 6: Proxy survives GC 82 91 console.log('Test 6: Proxy'); 83 92 let proxyTarget = { x: 10, y: 20 }; 84 93 let proxyHandler = { 85 - get: function(target, prop) { 94 + get: function (target, prop) { 86 95 if (prop === 'sum') return target.x + target.y; 87 96 return target[prop]; 88 97 } ··· 99 108 function makeCounter() { 100 109 let count = 0; 101 110 return { 102 - inc: function() { count = count + 1; return count; }, 103 - get: function() { return count; } 111 + inc: function () { 112 + count = count + 1; 113 + return count; 114 + }, 115 + get: function () { 116 + return count; 117 + } 104 118 }; 105 119 } 106 120 let counter = makeCounter(); ··· 130 144 for (let i = 0; i < 1000; i = i + 1) { 131 145 largeArr.push({ index: i, data: 'item ' + i }); 132 146 } 133 - let allocBefore = Ant.alloc(); 147 + let statsBefore = Ant.stats(); 134 148 Ant.gc(); 135 - let allocAfter = Ant.alloc(); 149 + let statsAfter = Ant.stats(); 136 150 assert(largeArr.length === 1000, 'large array should have 1000 elements'); 137 151 assert(largeArr[500].index === 500, 'element 500 should be correct'); 138 152 assert(largeArr[999].data === 'item 999', 'last element should be correct'); 139 - console.log(' Large allocations: OK\n'); 153 + console.log(' Large allocations: OK'); 154 + console.log(' arenaUsed before:', fmt(statsBefore.arenaUsed), 'after:', fmt(statsAfter.arenaUsed), '\n'); 140 155 141 156 // Test 10: Nested structures 142 157 console.log('Test 10: Nested Structures'); ··· 169 184 console.log('\n=== Async GC Test ==='); 170 185 async function testAsyncGC() { 171 186 let asyncData = { value: 'before await' }; 172 - 187 + 173 188 await new Promise(resolve => setTimeout(resolve, 10)); 174 - 189 + 175 190 // GC inside coroutine (should skip compaction) 176 191 Ant.gc(); 177 - 192 + 178 193 asyncData.value = 'after first await'; 179 - 194 + 180 195 await new Promise(resolve => setTimeout(resolve, 10)); 181 - 196 + 182 197 assert(asyncData.value === 'after first await', 'async data should survive await'); 183 - 198 + 184 199 console.log(' Async GC: OK'); 185 200 return 'async complete'; 186 201 } 187 202 188 - testAsyncGC().then((result) => { 203 + testAsyncGC().then(result => { 189 204 console.log(' Result:', result); 190 205 console.log('\n=== All Tests Complete ==='); 191 206 });
+25 -21
tests/test_gc_large.js
··· 6 6 return (bytes / 1024 / 1024).toFixed(2) + ' MB'; 7 7 } 8 8 9 - let alloc1 = Ant.alloc(); 9 + let stats1 = Ant.stats(); 10 10 console.log('Initial:'); 11 - console.log(' heapSize:', fmt(alloc1.heapSize)); 12 - console.log(' totalBytes:', fmt(alloc1.totalBytes)); 11 + console.log(' arenaUsed:', fmt(stats1.arenaUsed)); 12 + console.log(' arenaSize:', fmt(stats1.arenaSize)); 13 + console.log(' rss:', fmt(stats1.rss)); 13 14 console.log(''); 14 15 15 16 // Allocate large objects ··· 22 23 largeObjects.push(arr); 23 24 } 24 25 25 - let alloc2 = Ant.alloc(); 26 - console.log('After allocating 5MB:'); 27 - console.log(' heapSize:', fmt(alloc2.heapSize)); 28 - console.log(' totalBytes:', fmt(alloc2.totalBytes)); 26 + let stats2 = Ant.stats(); 27 + console.log('After allocating large arrays:'); 28 + console.log(' arenaUsed:', fmt(stats2.arenaUsed)); 29 + console.log(' arenaSize:', fmt(stats2.arenaSize)); 30 + console.log(' rss:', fmt(stats2.rss)); 29 31 console.log(''); 30 32 31 33 // Free the objects 32 34 largeObjects = null; 33 35 34 36 let gc1 = Ant.gc(); 35 - console.log('GC 1: freed:', fmt(gc1.freed), 'arenaFreed:', fmt(gc1.arenaFreed)); 37 + console.log('GC 1: arenaFreed:', fmt(gc1.arenaFreed)); 36 38 let gc2 = Ant.gc(); 37 - console.log('GC 2: freed:', fmt(gc2.freed), 'arenaFreed:', fmt(gc2.arenaFreed)); 39 + console.log('GC 2: arenaFreed:', fmt(gc2.arenaFreed)); 38 40 let gc3 = Ant.gc(); 39 - console.log('GC 3: freed:', fmt(gc3.freed), 'arenaFreed:', fmt(gc3.arenaFreed)); 41 + console.log('GC 3: arenaFreed:', fmt(gc3.arenaFreed)); 40 42 41 - let alloc3 = Ant.alloc(); 42 - console.log('After 3x GC:'); 43 - console.log(' heapSize:', fmt(alloc3.heapSize)); 44 - console.log(' totalBytes:', fmt(alloc3.totalBytes)); 43 + let stats3 = Ant.stats(); 44 + console.log('\nAfter 3x GC:'); 45 + console.log(' arenaUsed:', fmt(stats3.arenaUsed)); 46 + console.log(' arenaSize:', fmt(stats3.arenaSize)); 47 + console.log(' rss:', fmt(stats3.rss)); 45 48 console.log(''); 46 49 47 - // Allocate again - if GC worked, heap shouldn't grow much 50 + // Allocate again - if GC worked, arena shouldn't grow much 48 51 let moreObjects = []; 49 52 for (let i = 0; i < 5; i++) { 50 53 let arr = new Array(1024 * 128); ··· 54 57 moreObjects.push(arr); 55 58 } 56 59 57 - let alloc4 = Ant.alloc(); 58 - console.log('After allocating another 5MB:'); 59 - console.log(' heapSize:', fmt(alloc4.heapSize)); 60 - console.log(' totalBytes:', fmt(alloc4.totalBytes)); 61 - console.log(' heap increase from GC point:', fmt(alloc4.heapSize - alloc3.heapSize)); 60 + let stats4 = Ant.stats(); 61 + console.log('After allocating another batch:'); 62 + console.log(' arenaUsed:', fmt(stats4.arenaUsed)); 63 + console.log(' arenaSize:', fmt(stats4.arenaSize)); 64 + console.log(' rss:', fmt(stats4.rss)); 65 + console.log(' arenaUsed increase from GC point:', fmt(stats4.arenaUsed - stats3.arenaUsed)); 62 66 console.log(''); 63 67 64 - console.log('If heapSize stayed same, GC reclaimed memory for reuse!'); 68 + console.log('If arenaSize stayed same, GC reclaimed memory for reuse!'); 65 69 console.log('=== Test Complete ===');