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.

ensure_string_shape_slot fast path

+400 -5
+12 -5
src/descriptors.c
··· 41 41 return false; 42 42 } 43 43 44 + static bool ensure_added_shape_slot_storage(ant_object_t *ptr, uint32_t slot) { 45 + if (!ptr || !ptr->shape) return false; 46 + if (!js_obj_ensure_prop_capacity(ptr, ant_shape_count(ptr->shape))) return false; 47 + if (slot >= ptr->prop_count && !js_obj_ensure_prop_capacity(ptr, slot + 1)) return false; 48 + 49 + return true; 50 + } 51 + 44 52 static inline uint8_t attrs_from_flags(int flags, bool include_writable) { 45 53 uint8_t attrs = 0; 46 54 if (include_writable && (flags & JS_DESC_W)) attrs |= ANT_PROP_ATTR_WRITABLE; ··· 161 169 162 170 int32_t slot = ant_shape_lookup_interned(ptr->shape, interned); 163 171 if (slot < 0) { 164 - ant_value_t key_val = js_mkstr(js, key, klen); 165 - if (is_err(key_val)) return false; 166 - if (is_err(mkprop(js, obj, key_val, js_mkundef(), 0))) return false; 167 - slot = ant_shape_lookup_interned(ptr->shape, interned); 168 - if (slot < 0) return false; 172 + uint32_t added_slot = 0; 173 + if (!ant_shape_add_interned_tr(&ptr->shape, interned, ANT_PROP_ATTR_DEFAULT, &added_slot)) return false; 174 + if (!ensure_added_shape_slot_storage(ptr, added_slot)) return false; 175 + slot = (int32_t)added_slot; 169 176 } 170 177 171 178 if (out_slot) *out_slot = (uint32_t)slot;
+267
tests/bench_property_hotspots.js
··· 1 + function nowMs() { 2 + if (typeof performance !== 'undefined' && performance && typeof performance.now === 'function') { 3 + return performance.now(); 4 + } 5 + return Date.now(); 6 + } 7 + 8 + function parseScale() { 9 + if (typeof process === 'undefined' || !process || !process.argv) return 1; 10 + const raw = Number(process.argv[2]); 11 + return Number.isFinite(raw) && raw > 0 ? raw : 1; 12 + } 13 + 14 + function percentile(sorted, p) { 15 + if (sorted.length === 0) return 0; 16 + if (sorted.length === 1) return sorted[0]; 17 + const pos = (sorted.length - 1) * p; 18 + const base = Math.floor(pos); 19 + const frac = pos - base; 20 + const next = sorted[base + 1]; 21 + if (next === undefined) return sorted[base]; 22 + return sorted[base] + (next - sorted[base]) * frac; 23 + } 24 + 25 + function stableNumericSort(values) { 26 + const out = values.slice(); 27 + for (let i = 1; i < out.length; i++) { 28 + const x = out[i]; 29 + let j = i - 1; 30 + while (j >= 0 && out[j] > x) { 31 + out[j + 1] = out[j]; 32 + j--; 33 + } 34 + out[j + 1] = x; 35 + } 36 + return out; 37 + } 38 + 39 + const SCALE = parseScale(); 40 + const BENCH_WARMUP_RUNS = 2; 41 + const BENCH_SAMPLE_RUNS = 7; 42 + let benchSink = 0; 43 + 44 + function scaledIters(base) { 45 + return Math.max(1, Math.floor(base * SCALE)); 46 + } 47 + 48 + function section(title) { 49 + console.log('\n=== ' + title + ' ==='); 50 + } 51 + 52 + function bench(label, fn, iters) { 53 + const warmupIters = Math.max(1, Math.min(iters, Math.max(10_000, (iters / 4) | 0))); 54 + for (let i = 0; i < BENCH_WARMUP_RUNS; i++) benchSink ^= fn(warmupIters) | 0; 55 + 56 + let result = 0; 57 + const samples = []; 58 + for (let i = 0; i < BENCH_SAMPLE_RUNS; i++) { 59 + const t0 = nowMs(); 60 + const r = fn(iters); 61 + const dt = nowMs() - t0; 62 + if (i === 0) result = r; 63 + samples.push(dt); 64 + } 65 + 66 + const sorted = stableNumericSort(samples); 67 + const min = sorted[0]; 68 + const med = percentile(sorted, 0.5); 69 + const p95 = percentile(sorted, 0.95); 70 + const max = sorted[sorted.length - 1]; 71 + const opsPerMs = med > 0 ? (iters / med).toFixed(2) : 'inf'; 72 + 73 + benchSink ^= result | 0; 74 + console.log( 75 + label + 76 + ': median ' + med.toFixed(2) + 'ms (' + opsPerMs + ' ops/ms)' + 77 + ', p95 ' + p95.toFixed(2) + 'ms' + 78 + ', min ' + min.toFixed(2) + 'ms' + 79 + ', max ' + max.toFixed(2) + 'ms' + 80 + ' result=' + result 81 + ); 82 + } 83 + 84 + function benchCfuncProtoCallApply(n) { 85 + const fn = parseInt; 86 + let sum = 0; 87 + for (let i = 0; i < n; i++) { 88 + if (fn.call) sum++; 89 + if (fn.apply) sum++; 90 + } 91 + return sum; 92 + } 93 + 94 + function benchCfuncProtoBind(n) { 95 + const fn = parseInt; 96 + let sum = 0; 97 + for (let i = 0; i < n; i++) { 98 + if (fn.bind) sum++; 99 + } 100 + return sum; 101 + } 102 + 103 + const DATA_DESC = { 104 + value: 1, 105 + writable: true, 106 + enumerable: true, 107 + configurable: true, 108 + }; 109 + 110 + function benchDefinePropertyFresh(n) { 111 + let sum = 0; 112 + for (let i = 0; i < n; i++) { 113 + const obj = {}; 114 + Object.defineProperty(obj, 'x', DATA_DESC); 115 + sum += obj.x; 116 + } 117 + return sum; 118 + } 119 + 120 + const ACCESSOR_DESC = { 121 + get() { return 1; }, 122 + set(v) { benchSink ^= v | 0; }, 123 + enumerable: true, 124 + configurable: true, 125 + }; 126 + 127 + function benchDefineAccessorFresh(n) { 128 + let sum = 0; 129 + for (let i = 0; i < n; i++) { 130 + const obj = {}; 131 + Object.defineProperty(obj, 'x', ACCESSOR_DESC); 132 + sum += obj.x; 133 + } 134 + return sum; 135 + } 136 + 137 + function benchWithGet(n) { 138 + const scopeObj = { x: 1 }; 139 + let sum = 0; 140 + with (scopeObj) { 141 + for (let i = 0; i < n; i++) sum += x; 142 + } 143 + return sum; 144 + } 145 + 146 + function makeReusableRangeIterable(limit) { 147 + return { 148 + [Symbol.iterator]() { 149 + let i = 0; 150 + const result = { done: false, value: 0 }; 151 + return { 152 + next() { 153 + if (i < limit) { 154 + result.done = false; 155 + result.value = i++; 156 + } else { 157 + result.done = true; 158 + result.value = undefined; 159 + } 160 + return result; 161 + } 162 + }; 163 + } 164 + }; 165 + } 166 + 167 + function benchForOfReusableResult(n) { 168 + let sum = 0; 169 + for (const v of makeReusableRangeIterable(n)) sum += v; 170 + return sum; 171 + } 172 + 173 + function benchArrayLiteralCreate(n) { 174 + let sum = 0; 175 + for (let i = 0; i < n; i++) { 176 + const arr = []; 177 + sum += arr.length; 178 + } 179 + return sum; 180 + } 181 + 182 + function benchObjectLiteralCreate(n) { 183 + let sum = 0; 184 + for (let i = 0; i < n; i++) { 185 + const obj = {}; 186 + if (obj) sum++; 187 + } 188 + return sum; 189 + } 190 + 191 + function benchStringProtoDotRead(n) { 192 + const str = ' hello '; 193 + let sum = 0; 194 + for (let i = 0; i < n; i++) { 195 + if (str.trim) sum++; 196 + if (str.toUpperCase) sum++; 197 + } 198 + return sum; 199 + } 200 + 201 + function benchNumberProtoDotRead(n) { 202 + const num = 123.456; 203 + let sum = 0; 204 + for (let i = 0; i < n; i++) { 205 + if (num.toFixed) sum++; 206 + if (num.toString) sum++; 207 + } 208 + return sum; 209 + } 210 + 211 + function benchComputedMissingShortKey(n) { 212 + const obj = {}; 213 + const key = 'missing'; 214 + let sum = 0; 215 + for (let i = 0; i < n; i++) { 216 + if (obj[key] === undefined) sum++; 217 + } 218 + return sum; 219 + } 220 + 221 + function benchComputedMissingLongKey(n) { 222 + const obj = {}; 223 + const key = 'missing_property_name_that_is_long_enough_to_force_a_heap_alloc_in_sv_getprop_fallback_len_0123456789'; 224 + let sum = 0; 225 + for (let i = 0; i < n; i++) { 226 + if (obj[key] === undefined) sum++; 227 + } 228 + return sum; 229 + } 230 + 231 + console.log('property hotspot benchmark'); 232 + console.log('scale=' + SCALE); 233 + 234 + section('1. lkp_proto T_CFUNC branch re-interns'); 235 + console.log('sanity parseInt.call=' + (typeof parseInt.call)); 236 + bench('cfunc_proto_call_apply', benchCfuncProtoCallApply, scaledIters(2_000_000)); 237 + bench('cfunc_proto_bind', benchCfuncProtoBind, scaledIters(1_500_000)); 238 + 239 + section('2. ensure_string_shape_slot miss path'); 240 + console.log('sanity defineProperty=' + (typeof Object.defineProperty)); 241 + bench('defineProperty_fresh_data', benchDefinePropertyFresh, scaledIters(300_000)); 242 + bench('defineProperty_fresh_accessor', benchDefineAccessorFresh, scaledIters(200_000)); 243 + 244 + section('3. with lookup existence-check plus re-fetch'); 245 + console.log('sanity with=' + benchWithGet(3)); 246 + bench('with_get_var', benchWithGet, scaledIters(1_500_000)); 247 + 248 + section('4. iterator done/value generic reads'); 249 + console.log('sanity for_of=' + benchForOfReusableResult(8)); 250 + bench('for_of_reusable_result', benchForOfReusableResult, scaledIters(600_000)); 251 + 252 + section('5. ctor.prototype lookup on allocation'); 253 + console.log('sanity literals=' + (benchArrayLiteralCreate(3) + benchObjectLiteralCreate(3))); 254 + bench('array_literal_create', benchArrayLiteralCreate, scaledIters(2_000_000)); 255 + bench('object_literal_create', benchObjectLiteralCreate, scaledIters(2_000_000)); 256 + 257 + section('6. primitive proto reads through generic path'); 258 + console.log('sanity primitive=' + (benchStringProtoDotRead(2) + benchNumberProtoDotRead(2))); 259 + bench('string_proto_dot_read', benchStringProtoDotRead, scaledIters(2_000_000)); 260 + bench('number_proto_dot_read', benchNumberProtoDotRead, scaledIters(2_000_000)); 261 + 262 + section('7. computed missing-key reads through sv_getprop_fallback_len'); 263 + console.log('sanity computed=' + (benchComputedMissingShortKey(2) + benchComputedMissingLongKey(2))); 264 + bench('computed_missing_short_key', benchComputedMissingShortKey, scaledIters(2_000_000)); 265 + bench('computed_missing_long_key', benchComputedMissingLongKey, scaledIters(1_000_000)); 266 + 267 + console.log('\nbenchSink=' + benchSink);
+121
tests/test_define_property_shape_slot.cjs
··· 1 + function assert(cond, msg) { 2 + if (!cond) throw new Error(msg); 3 + } 4 + 5 + function assertEq(actual, expected, msg) { 6 + if (actual !== expected) { 7 + throw new Error(msg + " (expected " + expected + ", got " + actual + ")"); 8 + } 9 + } 10 + 11 + console.log("defineProperty shape-slot regression"); 12 + 13 + console.log("\nTest 1: repeated fresh data-property definitions on one object"); 14 + { 15 + const obj = {}; 16 + for (let i = 0; i < 64; i++) { 17 + Object.defineProperty(obj, "p" + i, { 18 + value: i, 19 + writable: true, 20 + enumerable: true, 21 + configurable: true, 22 + }); 23 + } 24 + 25 + let sum = 0; 26 + for (let i = 0; i < 64; i++) sum += obj["p" + i]; 27 + assertEq(sum, (63 * 64) / 2, "all fresh data properties should be readable"); 28 + assertEq(Object.keys(obj).length, 64, "all enumerable fresh properties should be visible"); 29 + } 30 + console.log("PASS"); 31 + 32 + console.log("\nTest 2: fresh non-enumerable property should not leak into keys"); 33 + { 34 + const obj = {}; 35 + Object.defineProperty(obj, "hidden", { 36 + value: 123, 37 + enumerable: false, 38 + configurable: true, 39 + writable: true, 40 + }); 41 + obj.visible = 456; 42 + 43 + assertEq(obj.hidden, 123, "hidden property should be readable"); 44 + assertEq(obj.visible, 456, "normal property should still work"); 45 + assertEq(Object.keys(obj).length, 1, "only visible should appear in keys"); 46 + assertEq(Object.keys(obj)[0], "visible", "visible key should be preserved"); 47 + } 48 + console.log("PASS"); 49 + 50 + console.log("\nTest 3: fresh accessor descriptors should keep getter/setter semantics"); 51 + { 52 + const obj = {}; 53 + let backing = 10; 54 + 55 + Object.defineProperty(obj, "value", { 56 + get() { return backing + 1; }, 57 + set(v) { backing = v * 2; }, 58 + enumerable: true, 59 + configurable: true, 60 + }); 61 + 62 + assertEq(obj.value, 11, "fresh getter should be active"); 63 + obj.value = 7; 64 + assertEq(backing, 14, "fresh setter should be active"); 65 + assertEq(obj.value, 15, "getter should still observe updated backing"); 66 + } 67 + console.log("PASS"); 68 + 69 + console.log("\nTest 4: repeated fresh defineProperty on many objects"); 70 + { 71 + let total = 0; 72 + for (let i = 0; i < 2000; i++) { 73 + const obj = {}; 74 + Object.defineProperty(obj, "x", { 75 + value: i, 76 + writable: true, 77 + enumerable: true, 78 + configurable: true, 79 + }); 80 + Object.defineProperty(obj, "y", { 81 + value: i + 1, 82 + writable: true, 83 + enumerable: false, 84 + configurable: true, 85 + }); 86 + total += obj.x + obj.y; 87 + assertEq(Object.keys(obj).length, 1, "fresh per-object enumerable shape should stay correct"); 88 + } 89 + 90 + assert(total > 0, "loop total should accumulate"); 91 + } 92 + console.log("PASS"); 93 + 94 + console.log("\nTest 5: defining then redefining adjacent fresh slots"); 95 + { 96 + const obj = {}; 97 + Object.defineProperty(obj, "a", { 98 + value: 1, 99 + writable: true, 100 + enumerable: true, 101 + configurable: true, 102 + }); 103 + Object.defineProperty(obj, "b", { 104 + value: 2, 105 + writable: true, 106 + enumerable: true, 107 + configurable: true, 108 + }); 109 + Object.defineProperty(obj, "a", { 110 + value: 10, 111 + writable: true, 112 + enumerable: true, 113 + configurable: true, 114 + }); 115 + 116 + assertEq(obj.a, 10, "redefining earlier property should not corrupt later fresh slot"); 117 + assertEq(obj.b, 2, "later fresh slot should remain intact"); 118 + } 119 + console.log("PASS"); 120 + 121 + console.log("\nAll shape-slot regression tests passed");