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.

add forward load factor and work queue for garbage collection

+236 -105
+236 -105
src/gc.c
··· 11 11 12 12 #define GC_MIN_HEAP_SIZE (64 * 1024) 13 13 #define GC_SHRINK_THRESHOLD 4 14 + #define GC_FWD_LOAD_FACTOR 70 14 15 15 - typedef struct gc_forward_node { 16 - jsoff_t old_off; 17 - jsoff_t new_off; 18 - struct gc_forward_node *next; 19 - } gc_forward_node_t; 16 + #define FWD_EMPTY ((jsoff_t)~0) 17 + #define FWD_TOMBSTONE ((jsoff_t)~1) 20 18 21 19 typedef struct { 22 - gc_forward_node_t *buckets[JS_HASH_SIZE]; 20 + jsoff_t *old_offs; 21 + jsoff_t *new_offs; 22 + size_t count; 23 + size_t capacity; 24 + size_t mask; 23 25 } gc_forward_table_t; 24 26 25 27 typedef struct { 28 + jsoff_t *items; 29 + size_t count; 30 + size_t capacity; 31 + } gc_work_queue_t; 32 + 33 + typedef struct { 26 34 struct js *js; 27 35 uint8_t *new_mem; 28 36 jsoff_t new_brk; 29 37 jsoff_t new_size; 30 38 uint8_t *mark_bits; 31 39 gc_forward_table_t fwd; 40 + gc_work_queue_t work; 32 41 } gc_ctx_t; 33 42 34 - static jsoff_t gc_copy_entity(gc_ctx_t *ctx, jsoff_t old_off); 35 43 static jsval_t gc_update_val(gc_ctx_t *ctx, jsval_t val); 36 44 37 45 static inline void mark_set(gc_ctx_t *ctx, jsoff_t off) { ··· 39 47 ctx->mark_bits[idx >> 3] |= (1 << (idx & 7)); 40 48 } 41 49 42 - static inline size_t fwd_hash(jsoff_t off) { 43 - return (off >> 2) & (JS_HASH_SIZE - 1); 50 + static inline size_t next_pow2(size_t n) { 51 + n--; 52 + n |= n >> 1; n |= n >> 2; n |= n >> 4; 53 + n |= n >> 8; n |= n >> 16; 54 + #if SIZE_MAX > 0xFFFFFFFF 55 + n |= n >> 32; 56 + #endif 57 + return n + 1; 44 58 } 45 59 46 - static void fwd_init(gc_forward_table_t *fwd) { 47 - memset(fwd->buckets, 0, sizeof(fwd->buckets)); 60 + static bool fwd_init(gc_forward_table_t *fwd, size_t estimated) { 61 + size_t cap = next_pow2(estimated < 64 ? 64 : estimated); 62 + fwd->old_offs = (jsoff_t *)ANT_GC_MALLOC(cap * sizeof(jsoff_t)); 63 + fwd->new_offs = (jsoff_t *)ANT_GC_MALLOC(cap * sizeof(jsoff_t)); 64 + if (!fwd->old_offs || !fwd->new_offs) { 65 + if (fwd->old_offs) ANT_GC_FREE(fwd->old_offs); 66 + if (fwd->new_offs) ANT_GC_FREE(fwd->new_offs); 67 + return false; 68 + } 69 + for (size_t i = 0; i < cap; i++) fwd->old_offs[i] = FWD_EMPTY; 70 + fwd->count = 0; 71 + fwd->capacity = cap; 72 + fwd->mask = cap - 1; 73 + return true; 48 74 } 49 75 50 - static void fwd_add(gc_forward_table_t *fwd, jsoff_t old_off, jsoff_t new_off) { 51 - size_t h = fwd_hash(old_off); 52 - gc_forward_node_t *node = ANT_GC_MALLOC(sizeof(gc_forward_node_t)); 53 - node->old_off = old_off; 54 - node->new_off = new_off; 55 - node->next = fwd->buckets[h]; 56 - fwd->buckets[h] = node; 76 + static bool fwd_grow(gc_forward_table_t *fwd) { 77 + size_t new_cap = fwd->capacity * 2; 78 + size_t new_mask = new_cap - 1; 79 + jsoff_t *new_old = (jsoff_t *)ANT_GC_MALLOC(new_cap * sizeof(jsoff_t)); 80 + jsoff_t *new_new = (jsoff_t *)ANT_GC_MALLOC(new_cap * sizeof(jsoff_t)); 81 + if (!new_old || !new_new) { 82 + if (new_old) ANT_GC_FREE(new_old); 83 + if (new_new) ANT_GC_FREE(new_new); 84 + return false; 85 + } 86 + for (size_t i = 0; i < new_cap; i++) new_old[i] = FWD_EMPTY; 87 + 88 + for (size_t i = 0; i < fwd->capacity; i++) { 89 + jsoff_t key = fwd->old_offs[i]; 90 + if (key == FWD_EMPTY || key == FWD_TOMBSTONE) continue; 91 + size_t h = (key >> 2) & new_mask; 92 + while (new_old[h] != FWD_EMPTY) h = (h + 1) & new_mask; 93 + new_old[h] = key; 94 + new_new[h] = fwd->new_offs[i]; 95 + } 96 + 97 + ANT_GC_FREE(fwd->old_offs); 98 + ANT_GC_FREE(fwd->new_offs); 99 + fwd->old_offs = new_old; 100 + fwd->new_offs = new_new; 101 + fwd->capacity = new_cap; 102 + fwd->mask = new_mask; 103 + return true; 104 + } 105 + 106 + static inline bool fwd_add(gc_forward_table_t *fwd, jsoff_t old_off, jsoff_t new_off) { 107 + if (fwd->count * 100 >= fwd->capacity * GC_FWD_LOAD_FACTOR) { 108 + if (!fwd_grow(fwd)) return false; 109 + } 110 + size_t h = (old_off >> 2) & fwd->mask; 111 + while (fwd->old_offs[h] != FWD_EMPTY && fwd->old_offs[h] != FWD_TOMBSTONE) { 112 + if (fwd->old_offs[h] == old_off) { 113 + fwd->new_offs[h] = new_off; 114 + return true; 115 + } 116 + h = (h + 1) & fwd->mask; 117 + } 118 + fwd->old_offs[h] = old_off; 119 + fwd->new_offs[h] = new_off; 120 + fwd->count++; 121 + return true; 57 122 } 58 123 59 - static jsoff_t fwd_lookup(gc_forward_table_t *fwd, jsoff_t old_off) { 60 - size_t h = fwd_hash(old_off); 61 - for (gc_forward_node_t *n = fwd->buckets[h]; n; n = n->next) { 62 - if (n->old_off == old_off) return n->new_off; 124 + static inline jsoff_t fwd_lookup(gc_forward_table_t *fwd, jsoff_t old_off) { 125 + size_t h = (old_off >> 2) & fwd->mask; 126 + for (size_t i = 0; i < fwd->capacity; i++) { 127 + jsoff_t key = fwd->old_offs[h]; 128 + if (key == FWD_EMPTY) return (jsoff_t)~0; 129 + if (key == old_off) return fwd->new_offs[h]; 130 + h = (h + 1) & fwd->mask; 63 131 } 64 132 return (jsoff_t)~0; 65 133 } 66 134 67 135 static void fwd_free(gc_forward_table_t *fwd) { 68 - for (size_t i = 0; i < JS_HASH_SIZE; i++) { 69 - gc_forward_node_t *n = fwd->buckets[i]; 70 - while (n) { 71 - gc_forward_node_t *next = n->next; 72 - ANT_GC_FREE(n); 73 - n = next; 74 - } 75 - fwd->buckets[i] = NULL; 136 + if (fwd->old_offs) ANT_GC_FREE(fwd->old_offs); 137 + if (fwd->new_offs) ANT_GC_FREE(fwd->new_offs); 138 + fwd->old_offs = NULL; 139 + fwd->new_offs = NULL; 140 + fwd->count = 0; 141 + fwd->capacity = 0; 142 + } 143 + 144 + static bool work_init(gc_work_queue_t *work, size_t initial) { 145 + work->items = (jsoff_t *)ANT_GC_MALLOC(initial * sizeof(jsoff_t)); 146 + if (!work->items) return false; 147 + work->count = 0; 148 + work->capacity = initial; 149 + return true; 150 + } 151 + 152 + static inline bool work_push(gc_work_queue_t *work, jsoff_t off) { 153 + if (work->count >= work->capacity) { 154 + size_t new_cap = work->capacity * 2; 155 + jsoff_t *new_items = (jsoff_t *)ANT_GC_MALLOC(new_cap * sizeof(jsoff_t)); 156 + if (!new_items) return false; 157 + memcpy(new_items, work->items, work->count * sizeof(jsoff_t)); 158 + ANT_GC_FREE(work->items); 159 + work->items = new_items; 160 + work->capacity = new_cap; 76 161 } 162 + work->items[work->count++] = off; 163 + return true; 164 + } 165 + 166 + static inline jsoff_t work_pop(gc_work_queue_t *work) { 167 + if (work->count == 0) return (jsoff_t)~0; 168 + return work->items[--work->count]; 169 + } 170 + 171 + static void work_free(gc_work_queue_t *work) { 172 + if (work->items) ANT_GC_FREE(work->items); 173 + work->items = NULL; 174 + work->count = 0; 175 + work->capacity = 0; 77 176 } 78 177 79 178 static inline jsoff_t gc_loadoff(uint8_t *mem, jsoff_t off) { 80 179 jsoff_t val; 81 - memcpy(&val, &mem[off], sizeof(val)); return val; 180 + memcpy(&val, &mem[off], sizeof(val)); 181 + return val; 82 182 } 83 183 84 184 static inline jsval_t gc_loadval(uint8_t *mem, jsoff_t off) { 85 185 jsval_t val; 86 - memcpy(&val, &mem[off], sizeof(val)); return val; 186 + memcpy(&val, &mem[off], sizeof(val)); 187 + return val; 87 188 } 88 189 89 190 static inline void gc_saveoff(uint8_t *mem, jsoff_t off, jsoff_t val) { ··· 106 207 107 208 static jsoff_t gc_alloc(gc_ctx_t *ctx, size_t size) { 108 209 size = (size + 3) / 4 * 4; 109 - if (ctx->new_brk + size > ctx->new_size) { 110 - return (jsoff_t)~0; 111 - } 210 + if (ctx->new_brk + size > ctx->new_size) return (jsoff_t)~0; 112 211 jsoff_t off = ctx->new_brk; 113 212 ctx->new_brk += (jsoff_t)size; 114 213 return off; ··· 146 245 if (new_off == (jsoff_t)~0) return old_off; 147 246 148 247 memcpy(&ctx->new_mem[new_off], &ctx->js->mem[old_off], size); 248 + fwd_add(&ctx->fwd, old_off, new_off); 249 + mark_set(ctx, old_off); 149 250 251 + return new_off; 252 + } 253 + 254 + static jsoff_t gc_copy_bigint(gc_ctx_t *ctx, jsoff_t old_off) { 255 + if (old_off >= ctx->js->brk) return old_off; 256 + 257 + jsoff_t new_off = fwd_lookup(&ctx->fwd, old_off); 258 + if (new_off != (jsoff_t)~0) return new_off; 259 + 260 + jsoff_t header = gc_loadoff(ctx->js->mem, old_off); 261 + size_t total = (header >> 4) + sizeof(jsoff_t); 262 + total = (total + 3) / 4 * 4; 263 + 264 + new_off = gc_alloc(ctx, total); 265 + if (new_off == (jsoff_t)~0) return old_off; 266 + 267 + memcpy(&ctx->new_mem[new_off], &ctx->js->mem[old_off], total); 150 268 fwd_add(&ctx->fwd, old_off, new_off); 151 269 mark_set(ctx, old_off); 152 270 153 271 return new_off; 154 272 } 155 273 156 - static jsoff_t gc_copy_prop(gc_ctx_t *ctx, jsoff_t old_off) { 274 + static jsoff_t gc_reserve_object(gc_ctx_t *ctx, jsoff_t old_off) { 157 275 if (old_off >= ctx->js->brk) return old_off; 158 276 159 277 jsoff_t new_off = fwd_lookup(&ctx->fwd, old_off); 160 278 if (new_off != (jsoff_t)~0) return new_off; 161 279 162 280 jsoff_t header = gc_loadoff(ctx->js->mem, old_off); 163 - if ((header & 3) != T_PROP) { 164 - return old_off; 165 - } 281 + if ((header & 3) != T_OBJ) return old_off; 166 282 167 283 jsoff_t size = gc_esize(header); 168 284 if (size == (jsoff_t)~0) return old_off; ··· 171 287 if (new_off == (jsoff_t)~0) return old_off; 172 288 173 289 memcpy(&ctx->new_mem[new_off], &ctx->js->mem[old_off], size); 290 + fwd_add(&ctx->fwd, old_off, new_off); 291 + mark_set(ctx, old_off); 292 + work_push(&ctx->work, old_off); 174 293 294 + return new_off; 295 + } 296 + 297 + static jsoff_t gc_reserve_prop(gc_ctx_t *ctx, jsoff_t old_off) { 298 + if (old_off >= ctx->js->brk) return old_off; 299 + 300 + jsoff_t new_off = fwd_lookup(&ctx->fwd, old_off); 301 + if (new_off != (jsoff_t)~0) return new_off; 302 + 303 + jsoff_t header = gc_loadoff(ctx->js->mem, old_off); 304 + if ((header & 3) != T_PROP) return old_off; 305 + 306 + jsoff_t size = gc_esize(header); 307 + if (size == (jsoff_t)~0) return old_off; 308 + 309 + new_off = gc_alloc(ctx, size); 310 + if (new_off == (jsoff_t)~0) return old_off; 311 + 312 + memcpy(&ctx->new_mem[new_off], &ctx->js->mem[old_off], size); 175 313 fwd_add(&ctx->fwd, old_off, new_off); 176 314 mark_set(ctx, old_off); 315 + work_push(&ctx->work, old_off); 316 + 317 + return new_off; 318 + } 319 + 320 + static void gc_process_prop(gc_ctx_t *ctx, jsoff_t old_off) { 321 + jsoff_t new_off = fwd_lookup(&ctx->fwd, old_off); 322 + if (new_off == (jsoff_t)~0) return; 323 + 324 + jsoff_t header = gc_loadoff(ctx->js->mem, old_off); 177 325 178 326 jsoff_t next_prop = header & ~(3U | FLAGMASK); 179 327 if (next_prop != 0 && next_prop < ctx->js->brk) { 180 - jsoff_t new_next = gc_copy_prop(ctx, next_prop); 328 + jsoff_t new_next = gc_reserve_prop(ctx, next_prop); 181 329 jsoff_t new_header = (new_next & ~3U) | (header & (3U | FLAGMASK)); 182 330 gc_saveoff(ctx->new_mem, new_off, new_header); 183 331 } ··· 190 338 jsoff_t new_key = gc_copy_string(ctx, key_off); 191 339 gc_saveoff(ctx->new_mem, new_off + sizeof(jsoff_t), new_key); 192 340 } 193 - 194 - jsval_t val = gc_loadval(ctx->js->mem, old_off + sizeof(jsoff_t) + sizeof(jsoff_t)); 195 - jsval_t new_val = gc_update_val(ctx, val); 196 - gc_saveval(ctx->new_mem, new_off + sizeof(jsoff_t) + sizeof(jsoff_t), new_val); 197 - } else { 198 - jsval_t val = gc_loadval(ctx->js->mem, old_off + sizeof(jsoff_t) + sizeof(jsoff_t)); 199 - jsval_t new_val = gc_update_val(ctx, val); 200 - gc_saveval(ctx->new_mem, new_off + sizeof(jsoff_t) + sizeof(jsoff_t), new_val); 201 341 } 202 342 203 - return new_off; 343 + jsval_t val = gc_loadval(ctx->js->mem, old_off + sizeof(jsoff_t) + sizeof(jsoff_t)); 344 + jsval_t new_val = gc_update_val(ctx, val); 345 + gc_saveval(ctx->new_mem, new_off + sizeof(jsoff_t) + sizeof(jsoff_t), new_val); 204 346 } 205 347 206 - static jsoff_t gc_copy_object(gc_ctx_t *ctx, jsoff_t old_off) { 207 - if (old_off >= ctx->js->brk) return old_off; 208 - 348 + static void gc_process_object(gc_ctx_t *ctx, jsoff_t old_off) { 209 349 jsoff_t new_off = fwd_lookup(&ctx->fwd, old_off); 210 - if (new_off != (jsoff_t)~0) return new_off; 350 + if (new_off == (jsoff_t)~0) return; 211 351 212 352 jsoff_t header = gc_loadoff(ctx->js->mem, old_off); 213 - if ((header & 3) != T_OBJ) return old_off; 214 - 215 - jsoff_t size = gc_esize(header); 216 - if (size == (jsoff_t)~0) return old_off; 217 - 218 - new_off = gc_alloc(ctx, size); 219 - if (new_off == (jsoff_t)~0) return old_off; 220 - 221 - memcpy(&ctx->new_mem[new_off], &ctx->js->mem[old_off], size); 222 - 223 - fwd_add(&ctx->fwd, old_off, new_off); 224 - mark_set(ctx, old_off); 225 353 226 354 jsoff_t first_prop = header & ~(3U | FLAGMASK); 227 355 if (first_prop != 0 && first_prop < ctx->js->brk) { 228 - jsoff_t new_first = gc_copy_prop(ctx, first_prop); 356 + jsoff_t new_first = gc_reserve_prop(ctx, first_prop); 229 357 jsoff_t new_header = (new_first & ~3U) | (header & (3U | FLAGMASK)); 230 358 gc_saveoff(ctx->new_mem, new_off, new_header); 231 359 } 232 360 233 361 jsoff_t parent_off = gc_loadoff(ctx->js->mem, old_off + sizeof(jsoff_t)); 234 362 if (parent_off != 0 && parent_off < ctx->js->brk) { 235 - jsoff_t new_parent = gc_copy_object(ctx, parent_off); 363 + jsoff_t new_parent = gc_reserve_object(ctx, parent_off); 236 364 gc_saveoff(ctx->new_mem, new_off + sizeof(jsoff_t), new_parent); 237 365 } 238 - 239 - return new_off; 240 366 } 241 367 242 - static jsoff_t gc_copy_entity(gc_ctx_t *ctx, jsoff_t old_off) { 243 - if (old_off >= ctx->js->brk) return old_off; 244 - 245 - jsoff_t header = gc_loadoff(ctx->js->mem, old_off); 246 - switch (header & 3) { 247 - case T_OBJ: return gc_copy_object(ctx, old_off); 248 - case T_PROP: return gc_copy_prop(ctx, old_off); 249 - case T_STR: return gc_copy_string(ctx, old_off); 250 - default: return old_off; 368 + static void gc_drain_work_queue(gc_ctx_t *ctx) { 369 + jsoff_t off; 370 + while ((off = work_pop(&ctx->work)) != (jsoff_t)~0) { 371 + jsoff_t header = gc_loadoff(ctx->js->mem, off); 372 + switch (header & 3) { 373 + case T_OBJ: gc_process_object(ctx, off); break; 374 + case T_PROP: gc_process_prop(ctx, off); break; 375 + default: break; 376 + } 251 377 } 252 378 } 253 379 ··· 264 390 case T_PROMISE: 265 391 case T_GENERATOR: { 266 392 if (old_off >= ctx->js->brk) return val; 267 - jsoff_t new_off = gc_copy_object(ctx, old_off); 393 + jsoff_t new_off = gc_reserve_object(ctx, old_off); 268 394 if (new_off != (jsoff_t)~0) return gc_mkval(type, new_off); 269 395 break; 270 396 } 271 397 case T_STR: { 272 398 if (old_off >= ctx->js->brk) return val; 273 399 jsoff_t new_off = gc_copy_string(ctx, old_off); 274 - if (new_off != (jsoff_t)~0) { 275 - return gc_mkval(type, new_off); 276 - } 400 + if (new_off != (jsoff_t)~0) return gc_mkval(type, new_off); 277 401 break; 278 402 } 279 403 case T_PROP: { 280 404 if (old_off >= ctx->js->brk) return val; 281 - jsoff_t new_off = gc_copy_prop(ctx, old_off); 282 - if (new_off != (jsoff_t)~0) { 283 - return gc_mkval(type, new_off); 284 - } 405 + jsoff_t new_off = gc_reserve_prop(ctx, old_off); 406 + if (new_off != (jsoff_t)~0) return gc_mkval(type, new_off); 285 407 break; 286 408 } 287 409 case T_BIGINT: { 288 410 if (old_off >= ctx->js->brk) return val; 289 - jsoff_t new_off = fwd_lookup(&ctx->fwd, old_off); 290 - if (new_off != (jsoff_t)~0) { 291 - return gc_mkval(type, new_off); 292 - } 293 - jsoff_t header = gc_loadoff(ctx->js->mem, old_off); 294 - size_t total = (header >> 4) + sizeof(jsoff_t); 295 - total = (total + 3) / 4 * 4; 296 - new_off = gc_alloc(ctx, total); 297 - if (new_off != (jsoff_t)~0) { 298 - memcpy(&ctx->new_mem[new_off], &ctx->js->mem[old_off], total); 299 - fwd_add(&ctx->fwd, old_off, new_off); 300 - mark_set(ctx, old_off); 301 - return gc_mkval(type, new_off); 302 - } 411 + jsoff_t new_off = gc_copy_bigint(ctx, old_off); 412 + if (new_off != (jsoff_t)~0) return gc_mkval(type, new_off); 303 413 break; 304 414 } 305 415 default: break; ··· 316 426 jsoff_t new_off = fwd_lookup(&ctx->fwd, old_off); 317 427 if (new_off != (jsoff_t)~0) return new_off; 318 428 319 - new_off = gc_copy_object(ctx, old_off); 429 + new_off = gc_reserve_object(ctx, old_off); 320 430 return (new_off != (jsoff_t)~0) ? new_off : old_off; 321 431 } 322 432 ··· 345 455 346 456 size_t bitmap_size = (js->brk / 4 + 7) / 8 + 1; 347 457 uint8_t *mark_bits = (uint8_t *)calloc(1, bitmap_size); 348 - if (!mark_bits) return 0; 458 + if (!mark_bits) { 459 + ANT_GC_FREE(new_mem); 460 + return 0; 461 + } 462 + 463 + size_t estimated_objs = js->brk / 64; 464 + if (estimated_objs < 256) estimated_objs = 256; 349 465 350 466 gc_ctx_t ctx; 351 467 ctx.js = js; ··· 353 469 ctx.new_brk = 0; 354 470 ctx.new_size = (jsoff_t)new_size; 355 471 ctx.mark_bits = mark_bits; 356 - fwd_init(&ctx.fwd); 472 + 473 + if (!fwd_init(&ctx.fwd, estimated_objs)) { 474 + ANT_GC_FREE(new_mem); 475 + free(mark_bits); 476 + return 0; 477 + } 478 + 479 + if (!work_init(&ctx.work, estimated_objs / 4 < 64 ? 64 : estimated_objs / 4)) { 480 + fwd_free(&ctx.fwd); 481 + ANT_GC_FREE(new_mem); 482 + free(mark_bits); 483 + return 0; 484 + } 357 485 358 486 if (js->brk > 0) { 359 487 jsoff_t header_at_0 = gc_loadoff(js->mem, 0); 360 - if ((header_at_0 & 3) == T_OBJ) gc_copy_object(&ctx, 0); 488 + if ((header_at_0 & 3) == T_OBJ) gc_reserve_object(&ctx, 0); 361 489 } 362 490 363 491 jsoff_t scope_off = (jsoff_t)gc_vdata(js->scope); 364 492 if (scope_off < js->brk) { 365 - jsoff_t new_scope = gc_copy_object(&ctx, scope_off); 493 + jsoff_t new_scope = gc_reserve_object(&ctx, scope_off); 366 494 js->scope = gc_mkval(T_OBJ, new_scope); 367 495 } 368 496 ··· 372 500 js->thrown_value = gc_update_val(&ctx, js->thrown_value); 373 501 js->tval = gc_update_val(&ctx, js->tval); 374 502 js_gc_update_roots(js, gc_fwd_off_callback, gc_fwd_val_callback, &ctx); 503 + 504 + gc_drain_work_queue(&ctx); 375 505 376 506 uint8_t *old_mem = js->mem; 377 507 js->mem = new_mem; ··· 396 526 } 397 527 398 528 free(mark_bits); 529 + work_free(&ctx.work); 399 530 fwd_free(&ctx.fwd); 400 531 ANT_GC_COLLECT(); 401 532