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.

optimize lookups remove traverse property chain to find SLOT_VERSION

+141 -16
-1
include/config.h
··· 45 45 SLOT_EXTENSIBLE, 46 46 SLOT_BUFFER, 47 47 SLOT_TARGET_FUNC, 48 - SLOT_VERSION, 49 48 SLOT_NAME, 50 49 SLOT_MAP, 51 50 SLOT_SET,
-1
include/config.h.in
··· 34 34 SLOT_EXTENSIBLE, 35 35 SLOT_BUFFER, 36 36 SLOT_TARGET_FUNC, 37 - SLOT_VERSION, 38 37 SLOT_NAME, 39 38 SLOT_MAP, 40 39 SLOT_SET,
+19 -14
src/ant.c
··· 3060 3060 INTERN_IDX[9] = intern_string("9", 1); 3061 3061 } 3062 3062 3063 - static void increment_version(struct js *js, jsoff_t obj_off) { 3064 - jsval_t version_val = get_slot(js, mkval(T_OBJ, obj_off), SLOT_VERSION); 3065 - uint32_t new_version = (vtype(version_val) == T_NUM) ? (uint32_t)tod(version_val) + 1 : 1; 3066 - set_slot(js, mkval(T_OBJ, obj_off), SLOT_VERSION, tov((double)new_version)); 3063 + static void invalidate_prop_cache(struct js *js, jsoff_t obj_off, jsoff_t prop_off) { 3064 + jsoff_t koff = loadoff(js, prop_off + sizeof(jsoff_t)); 3065 + jsoff_t klen = (loadoff(js, koff) >> 2) - 1; 3066 + 3067 + const char *key = (char *)&js->mem[koff + sizeof(jsoff_t)]; 3068 + const char *interned = intern_string(key, klen); 3069 + if (!interned) return; 3070 + 3071 + uint32_t cache_slot = (((uintptr_t)interned >> 3) ^ obj_off) & (ANT_LIMIT_SIZE_CACHE - 1); 3072 + intern_prop_cache_entry_t *ce = &intern_prop_cache[cache_slot]; 3073 + 3074 + if (ce->obj_off == obj_off && ce->intern_ptr == interned) { 3075 + ce->obj_off = 0; ce->intern_ptr = NULL; 3076 + ce->prop_off = 0; ce->obj_version = 0; 3077 + } 3067 3078 } 3068 3079 3069 3080 static jsval_t mkprop(struct js *js, jsval_t obj, jsval_t k, jsval_t v, jsoff_t flags) { ··· 3084 3095 (void)intern_string(p, klen); 3085 3096 3086 3097 jsoff_t prop_header = (b & ~(3U | FLAGMASK)) | T_PROP | flags; 3087 - jsval_t result = mkentity(js, prop_header, buf, sizeof(buf)); 3088 - 3089 - increment_version(js, head); 3090 - return result; 3098 + return mkentity(js, prop_header, buf, sizeof(buf)); 3091 3099 } 3092 3100 3093 3101 static inline jsval_t mkprop_fast(struct js *js, jsval_t obj, jsval_t k, jsval_t v, jsoff_t flags) { ··· 3330 3338 } 3331 3339 3332 3340 saveval(js, existing + sizeof(jsoff_t) * 2, v); 3333 - increment_version(js, (jsoff_t)vdata(obj)); 3334 3341 if (vtype(obj) != T_ARR || klen == 0 || key[0] < '0' || key[0] > '9') goto done_update; 3335 3342 3336 3343 char *endptr; ··· 3349 3356 jsval_t new_len = tov((double)(update_idx + 1)); 3350 3357 if (len_off != 0) { 3351 3358 saveval(js, len_off + sizeof(jsoff_t) * 2, new_len); 3352 - increment_version(js, (jsoff_t)vdata(obj)); 3353 3359 } else mkprop(js, obj, len_key, new_len, 0); 3354 3360 3355 3361 done_update: ··· 3397 3403 jsval_t new_len = tov((double)(idx + 1)); 3398 3404 if (len_off != 0) { 3399 3405 saveval(js, len_off + sizeof(jsoff_t) * 2, new_len); 3400 - increment_version(js, (jsoff_t)vdata(obj)); 3401 3406 } else mkprop(js, obj, len_key, new_len, 0); 3402 3407 } 3403 3408 ··· 8533 8538 jsoff_t current = loadoff(js, target); 8534 8539 8535 8540 saveoff(js, target, (deleted_next & ~3U) | (current & (FLAGMASK | 3U))); 8536 - increment_version(js, obj_off); 8541 + invalidate_prop_cache(js, obj_off, prop_off); 8537 8542 js->needs_gc = true; 8538 8543 } 8539 8544 ··· 21558 21563 jsoff_t deleted_next = loadoff(js, prop_off) & ~FLAGMASK; 21559 21564 jsoff_t current = loadoff(js, obj_off); 21560 21565 saveoff(js, obj_off, (deleted_next & ~3U) | (current & (FLAGMASK | 3U))); 21561 - increment_version(js, obj_off); 21566 + invalidate_prop_cache(js, obj_off, prop_off); 21562 21567 js->needs_gc = true; 21563 21568 return true; 21564 21569 } ··· 21570 21575 jsoff_t deleted_next = loadoff(js, prop_off) & ~FLAGMASK; 21571 21576 jsoff_t prev_flags = loadoff(js, prev) & FLAGMASK; 21572 21577 saveoff(js, prev, deleted_next | prev_flags); 21573 - increment_version(js, obj_off); 21578 + invalidate_prop_cache(js, obj_off, prop_off); 21574 21579 js->needs_gc = true; 21575 21580 return true; 21576 21581 }
+122
tests/test_property_cache_deletion.cjs
··· 1 + // Test: Property cache invalidation on deletion 2 + 3 + console.log("=== Test 1: Mid-chain deletion ==="); 4 + { 5 + let obj = { a: 1, b: 2, c: 3 }; 6 + console.log("obj.b before:", obj.b); // populate cache 7 + delete obj.b; 8 + console.log("obj.b after delete:", obj.b); // should be undefined 9 + console.log("PASS:", obj.b === undefined); 10 + } 11 + 12 + console.log("\n=== Test 2: First property deletion ==="); 13 + { 14 + let obj = {}; 15 + obj.a = 1; 16 + obj.b = 2; 17 + obj.c = 3; // c is first in chain (newest) 18 + console.log("obj.c before:", obj.c); 19 + delete obj.c; 20 + console.log("obj.c after delete:", obj.c); 21 + console.log("PASS:", obj.c === undefined); 22 + } 23 + 24 + console.log("\n=== Test 3: Last property deletion ==="); 25 + { 26 + let obj = {}; 27 + obj.a = 1; // a is last in chain (oldest) 28 + obj.b = 2; 29 + obj.c = 3; 30 + console.log("obj.a before:", obj.a); 31 + delete obj.a; 32 + console.log("obj.a after delete:", obj.a); 33 + console.log("PASS:", obj.a === undefined); 34 + } 35 + 36 + console.log("\n=== Test 4: Multiple deletions ==="); 37 + { 38 + let obj = { a: 1, b: 2, c: 3, d: 4, e: 5 }; 39 + obj.b; obj.c; obj.d; // populate cache 40 + delete obj.c; 41 + delete obj.b; 42 + delete obj.d; 43 + console.log("PASS:", obj.b === undefined && obj.c === undefined && obj.d === undefined); 44 + console.log("a still exists:", obj.a === 1); 45 + console.log("e still exists:", obj.e === 5); 46 + } 47 + 48 + console.log("\n=== Test 5: Delete and re-add ==="); 49 + { 50 + let obj = { a: 1, b: 2, c: 3 }; 51 + obj.b; // cache 52 + delete obj.b; 53 + console.log("after delete:", obj.b); 54 + obj.b = 999; 55 + console.log("after re-add:", obj.b); 56 + console.log("PASS:", obj.b === 999); 57 + } 58 + 59 + console.log("\n=== Test 6: In-place update doesnt break cache ==="); 60 + { 61 + let obj = { a: 1, b: 2, c: 3 }; 62 + obj.b; // cache 63 + obj.b = 100; // in-place update 64 + obj.b = 200; // another update 65 + console.log("obj.b:", obj.b); 66 + console.log("PASS:", obj.b === 200); 67 + } 68 + 69 + console.log("\n=== Test 7: Many properties stress test ==="); 70 + { 71 + let obj = {}; 72 + for (let i = 0; i < 100; i++) { 73 + obj["prop" + i] = i; 74 + } 75 + // Access some to populate cache 76 + obj.prop50; obj.prop25; obj.prop75; 77 + 78 + // Delete from various positions 79 + delete obj.prop50; 80 + delete obj.prop25; 81 + delete obj.prop75; 82 + delete obj.prop0; // last in chain 83 + delete obj.prop99; // first in chain 84 + 85 + console.log("PASS:", 86 + obj.prop50 === undefined && 87 + obj.prop25 === undefined && 88 + obj.prop75 === undefined && 89 + obj.prop0 === undefined && 90 + obj.prop99 === undefined && 91 + obj.prop1 === 1 && 92 + obj.prop98 === 98 93 + ); 94 + } 95 + 96 + console.log("\n=== Test 8: Delete non-existent (should not crash) ==="); 97 + { 98 + let obj = { a: 1 }; 99 + obj.a; // cache 100 + delete obj.nonexistent; // should be no-op 101 + console.log("PASS:", obj.a === 1); 102 + } 103 + 104 + console.log("\n=== Test 9: Repeated access after delete ==="); 105 + { 106 + let obj = { a: 1, b: 2, c: 3 }; 107 + obj.b; obj.b; obj.b; // multiple cache hits 108 + delete obj.b; 109 + let results = [obj.b, obj.b, obj.b]; // multiple accesses after delete 110 + console.log("PASS:", results.every(x => x === undefined)); 111 + } 112 + 113 + console.log("\n=== Test 10: Array element deletion ==="); 114 + { 115 + let arr = [1, 2, 3, 4, 5]; 116 + arr[2]; // cache 117 + delete arr[2]; 118 + console.log("arr[2]:", arr[2]); 119 + console.log("PASS:", arr[2] === undefined && arr.length === 5); 120 + } 121 + 122 + console.log("\n=== All tests complete ===");