From 06893493a5fb681cf50b533fd486b0b94fd7818b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ralph=20K=C3=BCpper?= Date: Sat, 29 Aug 2026 16:07:31 +0000 Subject: [PATCH] perf(runtime): preserve shared shape index across delete Clone the validated slot index when delete forks a shared keys array, leaving sibling objects accelerated while retaining the move path for owned sources. Add an end-to-end sibling regression and a dedicated A/B benchmark for #9006. --- benchmarks/bench_shared_shape_delete.ts | 539 ++++++++++++++++++ .../9067-preserve-shared-delete-index.md | 13 + .../perry-runtime/src/object/delete_rest.rs | 1 + crates/perry-runtime/src/object/shapes.rs | 1 + .../src/object/shapes_slot_list.rs | 102 +++- 5 files changed, 653 insertions(+), 3 deletions(-) create mode 100644 benchmarks/bench_shared_shape_delete.ts create mode 100644 changelog.d/9067-preserve-shared-delete-index.md diff --git a/benchmarks/bench_shared_shape_delete.ts b/benchmarks/bench_shared_shape_delete.ts new file mode 100644 index 0000000000..ae5d373ac2 --- /dev/null +++ b/benchmarks/bench_shared_shape_delete.ts @@ -0,0 +1,539 @@ +// Benchmark: one object deletes from a shared shape, then an untouched sibling +// performs a dynamic lookup. The delete forks the shared keys array; it must +// copy the slot index to that private fork rather than taking the source index +// away from every sibling (#9006). +// +// The literal factory guarantees that every receiver uses Perry's shape cache +// and shares one 500-key array. The rotating `Object.hasOwn` probe goes +// straight through the wide-key index, without a field inline cache hiding a +// missing source accelerator. +const KEY_COUNT = 500; +const OBJECT_COUNT = 5000; +const KEY_PREFIX = "shared_shape_delete_key_payload_"; +const keys: string[] = []; +for (let i = 0; i < KEY_COUNT; i++) keys.push(KEY_PREFIX + i); + +function makeObject(): Record { + return { + shared_shape_delete_key_payload_0: 0, + shared_shape_delete_key_payload_1: 1, + shared_shape_delete_key_payload_2: 2, + shared_shape_delete_key_payload_3: 3, + shared_shape_delete_key_payload_4: 4, + shared_shape_delete_key_payload_5: 5, + shared_shape_delete_key_payload_6: 6, + shared_shape_delete_key_payload_7: 7, + shared_shape_delete_key_payload_8: 8, + shared_shape_delete_key_payload_9: 9, + shared_shape_delete_key_payload_10: 10, + shared_shape_delete_key_payload_11: 11, + shared_shape_delete_key_payload_12: 12, + shared_shape_delete_key_payload_13: 13, + shared_shape_delete_key_payload_14: 14, + shared_shape_delete_key_payload_15: 15, + shared_shape_delete_key_payload_16: 16, + shared_shape_delete_key_payload_17: 17, + shared_shape_delete_key_payload_18: 18, + shared_shape_delete_key_payload_19: 19, + shared_shape_delete_key_payload_20: 20, + shared_shape_delete_key_payload_21: 21, + shared_shape_delete_key_payload_22: 22, + shared_shape_delete_key_payload_23: 23, + shared_shape_delete_key_payload_24: 24, + shared_shape_delete_key_payload_25: 25, + shared_shape_delete_key_payload_26: 26, + shared_shape_delete_key_payload_27: 27, + shared_shape_delete_key_payload_28: 28, + shared_shape_delete_key_payload_29: 29, + shared_shape_delete_key_payload_30: 30, + shared_shape_delete_key_payload_31: 31, + shared_shape_delete_key_payload_32: 32, + shared_shape_delete_key_payload_33: 33, + shared_shape_delete_key_payload_34: 34, + shared_shape_delete_key_payload_35: 35, + shared_shape_delete_key_payload_36: 36, + shared_shape_delete_key_payload_37: 37, + shared_shape_delete_key_payload_38: 38, + shared_shape_delete_key_payload_39: 39, + shared_shape_delete_key_payload_40: 40, + shared_shape_delete_key_payload_41: 41, + shared_shape_delete_key_payload_42: 42, + shared_shape_delete_key_payload_43: 43, + shared_shape_delete_key_payload_44: 44, + shared_shape_delete_key_payload_45: 45, + shared_shape_delete_key_payload_46: 46, + shared_shape_delete_key_payload_47: 47, + shared_shape_delete_key_payload_48: 48, + shared_shape_delete_key_payload_49: 49, + shared_shape_delete_key_payload_50: 50, + shared_shape_delete_key_payload_51: 51, + shared_shape_delete_key_payload_52: 52, + shared_shape_delete_key_payload_53: 53, + shared_shape_delete_key_payload_54: 54, + shared_shape_delete_key_payload_55: 55, + shared_shape_delete_key_payload_56: 56, + shared_shape_delete_key_payload_57: 57, + shared_shape_delete_key_payload_58: 58, + shared_shape_delete_key_payload_59: 59, + shared_shape_delete_key_payload_60: 60, + shared_shape_delete_key_payload_61: 61, + shared_shape_delete_key_payload_62: 62, + shared_shape_delete_key_payload_63: 63, + shared_shape_delete_key_payload_64: 64, + shared_shape_delete_key_payload_65: 65, + shared_shape_delete_key_payload_66: 66, + shared_shape_delete_key_payload_67: 67, + shared_shape_delete_key_payload_68: 68, + shared_shape_delete_key_payload_69: 69, + shared_shape_delete_key_payload_70: 70, + shared_shape_delete_key_payload_71: 71, + shared_shape_delete_key_payload_72: 72, + shared_shape_delete_key_payload_73: 73, + shared_shape_delete_key_payload_74: 74, + shared_shape_delete_key_payload_75: 75, + shared_shape_delete_key_payload_76: 76, + shared_shape_delete_key_payload_77: 77, + shared_shape_delete_key_payload_78: 78, + shared_shape_delete_key_payload_79: 79, + shared_shape_delete_key_payload_80: 80, + shared_shape_delete_key_payload_81: 81, + shared_shape_delete_key_payload_82: 82, + shared_shape_delete_key_payload_83: 83, + shared_shape_delete_key_payload_84: 84, + shared_shape_delete_key_payload_85: 85, + shared_shape_delete_key_payload_86: 86, + shared_shape_delete_key_payload_87: 87, + shared_shape_delete_key_payload_88: 88, + shared_shape_delete_key_payload_89: 89, + shared_shape_delete_key_payload_90: 90, + shared_shape_delete_key_payload_91: 91, + shared_shape_delete_key_payload_92: 92, + shared_shape_delete_key_payload_93: 93, + shared_shape_delete_key_payload_94: 94, + shared_shape_delete_key_payload_95: 95, + shared_shape_delete_key_payload_96: 96, + shared_shape_delete_key_payload_97: 97, + shared_shape_delete_key_payload_98: 98, + shared_shape_delete_key_payload_99: 99, + shared_shape_delete_key_payload_100: 100, + shared_shape_delete_key_payload_101: 101, + shared_shape_delete_key_payload_102: 102, + shared_shape_delete_key_payload_103: 103, + shared_shape_delete_key_payload_104: 104, + shared_shape_delete_key_payload_105: 105, + shared_shape_delete_key_payload_106: 106, + shared_shape_delete_key_payload_107: 107, + shared_shape_delete_key_payload_108: 108, + shared_shape_delete_key_payload_109: 109, + shared_shape_delete_key_payload_110: 110, + shared_shape_delete_key_payload_111: 111, + shared_shape_delete_key_payload_112: 112, + shared_shape_delete_key_payload_113: 113, + shared_shape_delete_key_payload_114: 114, + shared_shape_delete_key_payload_115: 115, + shared_shape_delete_key_payload_116: 116, + shared_shape_delete_key_payload_117: 117, + shared_shape_delete_key_payload_118: 118, + shared_shape_delete_key_payload_119: 119, + shared_shape_delete_key_payload_120: 120, + shared_shape_delete_key_payload_121: 121, + shared_shape_delete_key_payload_122: 122, + shared_shape_delete_key_payload_123: 123, + shared_shape_delete_key_payload_124: 124, + shared_shape_delete_key_payload_125: 125, + shared_shape_delete_key_payload_126: 126, + shared_shape_delete_key_payload_127: 127, + shared_shape_delete_key_payload_128: 128, + shared_shape_delete_key_payload_129: 129, + shared_shape_delete_key_payload_130: 130, + shared_shape_delete_key_payload_131: 131, + shared_shape_delete_key_payload_132: 132, + shared_shape_delete_key_payload_133: 133, + shared_shape_delete_key_payload_134: 134, + shared_shape_delete_key_payload_135: 135, + shared_shape_delete_key_payload_136: 136, + shared_shape_delete_key_payload_137: 137, + shared_shape_delete_key_payload_138: 138, + shared_shape_delete_key_payload_139: 139, + shared_shape_delete_key_payload_140: 140, + shared_shape_delete_key_payload_141: 141, + shared_shape_delete_key_payload_142: 142, + shared_shape_delete_key_payload_143: 143, + shared_shape_delete_key_payload_144: 144, + shared_shape_delete_key_payload_145: 145, + shared_shape_delete_key_payload_146: 146, + shared_shape_delete_key_payload_147: 147, + shared_shape_delete_key_payload_148: 148, + shared_shape_delete_key_payload_149: 149, + shared_shape_delete_key_payload_150: 150, + shared_shape_delete_key_payload_151: 151, + shared_shape_delete_key_payload_152: 152, + shared_shape_delete_key_payload_153: 153, + shared_shape_delete_key_payload_154: 154, + shared_shape_delete_key_payload_155: 155, + shared_shape_delete_key_payload_156: 156, + shared_shape_delete_key_payload_157: 157, + shared_shape_delete_key_payload_158: 158, + shared_shape_delete_key_payload_159: 159, + shared_shape_delete_key_payload_160: 160, + shared_shape_delete_key_payload_161: 161, + shared_shape_delete_key_payload_162: 162, + shared_shape_delete_key_payload_163: 163, + shared_shape_delete_key_payload_164: 164, + shared_shape_delete_key_payload_165: 165, + shared_shape_delete_key_payload_166: 166, + shared_shape_delete_key_payload_167: 167, + shared_shape_delete_key_payload_168: 168, + shared_shape_delete_key_payload_169: 169, + shared_shape_delete_key_payload_170: 170, + shared_shape_delete_key_payload_171: 171, + shared_shape_delete_key_payload_172: 172, + shared_shape_delete_key_payload_173: 173, + shared_shape_delete_key_payload_174: 174, + shared_shape_delete_key_payload_175: 175, + shared_shape_delete_key_payload_176: 176, + shared_shape_delete_key_payload_177: 177, + shared_shape_delete_key_payload_178: 178, + shared_shape_delete_key_payload_179: 179, + shared_shape_delete_key_payload_180: 180, + shared_shape_delete_key_payload_181: 181, + shared_shape_delete_key_payload_182: 182, + shared_shape_delete_key_payload_183: 183, + shared_shape_delete_key_payload_184: 184, + shared_shape_delete_key_payload_185: 185, + shared_shape_delete_key_payload_186: 186, + shared_shape_delete_key_payload_187: 187, + shared_shape_delete_key_payload_188: 188, + shared_shape_delete_key_payload_189: 189, + shared_shape_delete_key_payload_190: 190, + shared_shape_delete_key_payload_191: 191, + shared_shape_delete_key_payload_192: 192, + shared_shape_delete_key_payload_193: 193, + shared_shape_delete_key_payload_194: 194, + shared_shape_delete_key_payload_195: 195, + shared_shape_delete_key_payload_196: 196, + shared_shape_delete_key_payload_197: 197, + shared_shape_delete_key_payload_198: 198, + shared_shape_delete_key_payload_199: 199, + shared_shape_delete_key_payload_200: 200, + shared_shape_delete_key_payload_201: 201, + shared_shape_delete_key_payload_202: 202, + shared_shape_delete_key_payload_203: 203, + shared_shape_delete_key_payload_204: 204, + shared_shape_delete_key_payload_205: 205, + shared_shape_delete_key_payload_206: 206, + shared_shape_delete_key_payload_207: 207, + shared_shape_delete_key_payload_208: 208, + shared_shape_delete_key_payload_209: 209, + shared_shape_delete_key_payload_210: 210, + shared_shape_delete_key_payload_211: 211, + shared_shape_delete_key_payload_212: 212, + shared_shape_delete_key_payload_213: 213, + shared_shape_delete_key_payload_214: 214, + shared_shape_delete_key_payload_215: 215, + shared_shape_delete_key_payload_216: 216, + shared_shape_delete_key_payload_217: 217, + shared_shape_delete_key_payload_218: 218, + shared_shape_delete_key_payload_219: 219, + shared_shape_delete_key_payload_220: 220, + shared_shape_delete_key_payload_221: 221, + shared_shape_delete_key_payload_222: 222, + shared_shape_delete_key_payload_223: 223, + shared_shape_delete_key_payload_224: 224, + shared_shape_delete_key_payload_225: 225, + shared_shape_delete_key_payload_226: 226, + shared_shape_delete_key_payload_227: 227, + shared_shape_delete_key_payload_228: 228, + shared_shape_delete_key_payload_229: 229, + shared_shape_delete_key_payload_230: 230, + shared_shape_delete_key_payload_231: 231, + shared_shape_delete_key_payload_232: 232, + shared_shape_delete_key_payload_233: 233, + shared_shape_delete_key_payload_234: 234, + shared_shape_delete_key_payload_235: 235, + shared_shape_delete_key_payload_236: 236, + shared_shape_delete_key_payload_237: 237, + shared_shape_delete_key_payload_238: 238, + shared_shape_delete_key_payload_239: 239, + shared_shape_delete_key_payload_240: 240, + shared_shape_delete_key_payload_241: 241, + shared_shape_delete_key_payload_242: 242, + shared_shape_delete_key_payload_243: 243, + shared_shape_delete_key_payload_244: 244, + shared_shape_delete_key_payload_245: 245, + shared_shape_delete_key_payload_246: 246, + shared_shape_delete_key_payload_247: 247, + shared_shape_delete_key_payload_248: 248, + shared_shape_delete_key_payload_249: 249, + shared_shape_delete_key_payload_250: 250, + shared_shape_delete_key_payload_251: 251, + shared_shape_delete_key_payload_252: 252, + shared_shape_delete_key_payload_253: 253, + shared_shape_delete_key_payload_254: 254, + shared_shape_delete_key_payload_255: 255, + shared_shape_delete_key_payload_256: 256, + shared_shape_delete_key_payload_257: 257, + shared_shape_delete_key_payload_258: 258, + shared_shape_delete_key_payload_259: 259, + shared_shape_delete_key_payload_260: 260, + shared_shape_delete_key_payload_261: 261, + shared_shape_delete_key_payload_262: 262, + shared_shape_delete_key_payload_263: 263, + shared_shape_delete_key_payload_264: 264, + shared_shape_delete_key_payload_265: 265, + shared_shape_delete_key_payload_266: 266, + shared_shape_delete_key_payload_267: 267, + shared_shape_delete_key_payload_268: 268, + shared_shape_delete_key_payload_269: 269, + shared_shape_delete_key_payload_270: 270, + shared_shape_delete_key_payload_271: 271, + shared_shape_delete_key_payload_272: 272, + shared_shape_delete_key_payload_273: 273, + shared_shape_delete_key_payload_274: 274, + shared_shape_delete_key_payload_275: 275, + shared_shape_delete_key_payload_276: 276, + shared_shape_delete_key_payload_277: 277, + shared_shape_delete_key_payload_278: 278, + shared_shape_delete_key_payload_279: 279, + shared_shape_delete_key_payload_280: 280, + shared_shape_delete_key_payload_281: 281, + shared_shape_delete_key_payload_282: 282, + shared_shape_delete_key_payload_283: 283, + shared_shape_delete_key_payload_284: 284, + shared_shape_delete_key_payload_285: 285, + shared_shape_delete_key_payload_286: 286, + shared_shape_delete_key_payload_287: 287, + shared_shape_delete_key_payload_288: 288, + shared_shape_delete_key_payload_289: 289, + shared_shape_delete_key_payload_290: 290, + shared_shape_delete_key_payload_291: 291, + shared_shape_delete_key_payload_292: 292, + shared_shape_delete_key_payload_293: 293, + shared_shape_delete_key_payload_294: 294, + shared_shape_delete_key_payload_295: 295, + shared_shape_delete_key_payload_296: 296, + shared_shape_delete_key_payload_297: 297, + shared_shape_delete_key_payload_298: 298, + shared_shape_delete_key_payload_299: 299, + shared_shape_delete_key_payload_300: 300, + shared_shape_delete_key_payload_301: 301, + shared_shape_delete_key_payload_302: 302, + shared_shape_delete_key_payload_303: 303, + shared_shape_delete_key_payload_304: 304, + shared_shape_delete_key_payload_305: 305, + shared_shape_delete_key_payload_306: 306, + shared_shape_delete_key_payload_307: 307, + shared_shape_delete_key_payload_308: 308, + shared_shape_delete_key_payload_309: 309, + shared_shape_delete_key_payload_310: 310, + shared_shape_delete_key_payload_311: 311, + shared_shape_delete_key_payload_312: 312, + shared_shape_delete_key_payload_313: 313, + shared_shape_delete_key_payload_314: 314, + shared_shape_delete_key_payload_315: 315, + shared_shape_delete_key_payload_316: 316, + shared_shape_delete_key_payload_317: 317, + shared_shape_delete_key_payload_318: 318, + shared_shape_delete_key_payload_319: 319, + shared_shape_delete_key_payload_320: 320, + shared_shape_delete_key_payload_321: 321, + shared_shape_delete_key_payload_322: 322, + shared_shape_delete_key_payload_323: 323, + shared_shape_delete_key_payload_324: 324, + shared_shape_delete_key_payload_325: 325, + shared_shape_delete_key_payload_326: 326, + shared_shape_delete_key_payload_327: 327, + shared_shape_delete_key_payload_328: 328, + shared_shape_delete_key_payload_329: 329, + shared_shape_delete_key_payload_330: 330, + shared_shape_delete_key_payload_331: 331, + shared_shape_delete_key_payload_332: 332, + shared_shape_delete_key_payload_333: 333, + shared_shape_delete_key_payload_334: 334, + shared_shape_delete_key_payload_335: 335, + shared_shape_delete_key_payload_336: 336, + shared_shape_delete_key_payload_337: 337, + shared_shape_delete_key_payload_338: 338, + shared_shape_delete_key_payload_339: 339, + shared_shape_delete_key_payload_340: 340, + shared_shape_delete_key_payload_341: 341, + shared_shape_delete_key_payload_342: 342, + shared_shape_delete_key_payload_343: 343, + shared_shape_delete_key_payload_344: 344, + shared_shape_delete_key_payload_345: 345, + shared_shape_delete_key_payload_346: 346, + shared_shape_delete_key_payload_347: 347, + shared_shape_delete_key_payload_348: 348, + shared_shape_delete_key_payload_349: 349, + shared_shape_delete_key_payload_350: 350, + shared_shape_delete_key_payload_351: 351, + shared_shape_delete_key_payload_352: 352, + shared_shape_delete_key_payload_353: 353, + shared_shape_delete_key_payload_354: 354, + shared_shape_delete_key_payload_355: 355, + shared_shape_delete_key_payload_356: 356, + shared_shape_delete_key_payload_357: 357, + shared_shape_delete_key_payload_358: 358, + shared_shape_delete_key_payload_359: 359, + shared_shape_delete_key_payload_360: 360, + shared_shape_delete_key_payload_361: 361, + shared_shape_delete_key_payload_362: 362, + shared_shape_delete_key_payload_363: 363, + shared_shape_delete_key_payload_364: 364, + shared_shape_delete_key_payload_365: 365, + shared_shape_delete_key_payload_366: 366, + shared_shape_delete_key_payload_367: 367, + shared_shape_delete_key_payload_368: 368, + shared_shape_delete_key_payload_369: 369, + shared_shape_delete_key_payload_370: 370, + shared_shape_delete_key_payload_371: 371, + shared_shape_delete_key_payload_372: 372, + shared_shape_delete_key_payload_373: 373, + shared_shape_delete_key_payload_374: 374, + shared_shape_delete_key_payload_375: 375, + shared_shape_delete_key_payload_376: 376, + shared_shape_delete_key_payload_377: 377, + shared_shape_delete_key_payload_378: 378, + shared_shape_delete_key_payload_379: 379, + shared_shape_delete_key_payload_380: 380, + shared_shape_delete_key_payload_381: 381, + shared_shape_delete_key_payload_382: 382, + shared_shape_delete_key_payload_383: 383, + shared_shape_delete_key_payload_384: 384, + shared_shape_delete_key_payload_385: 385, + shared_shape_delete_key_payload_386: 386, + shared_shape_delete_key_payload_387: 387, + shared_shape_delete_key_payload_388: 388, + shared_shape_delete_key_payload_389: 389, + shared_shape_delete_key_payload_390: 390, + shared_shape_delete_key_payload_391: 391, + shared_shape_delete_key_payload_392: 392, + shared_shape_delete_key_payload_393: 393, + shared_shape_delete_key_payload_394: 394, + shared_shape_delete_key_payload_395: 395, + shared_shape_delete_key_payload_396: 396, + shared_shape_delete_key_payload_397: 397, + shared_shape_delete_key_payload_398: 398, + shared_shape_delete_key_payload_399: 399, + shared_shape_delete_key_payload_400: 400, + shared_shape_delete_key_payload_401: 401, + shared_shape_delete_key_payload_402: 402, + shared_shape_delete_key_payload_403: 403, + shared_shape_delete_key_payload_404: 404, + shared_shape_delete_key_payload_405: 405, + shared_shape_delete_key_payload_406: 406, + shared_shape_delete_key_payload_407: 407, + shared_shape_delete_key_payload_408: 408, + shared_shape_delete_key_payload_409: 409, + shared_shape_delete_key_payload_410: 410, + shared_shape_delete_key_payload_411: 411, + shared_shape_delete_key_payload_412: 412, + shared_shape_delete_key_payload_413: 413, + shared_shape_delete_key_payload_414: 414, + shared_shape_delete_key_payload_415: 415, + shared_shape_delete_key_payload_416: 416, + shared_shape_delete_key_payload_417: 417, + shared_shape_delete_key_payload_418: 418, + shared_shape_delete_key_payload_419: 419, + shared_shape_delete_key_payload_420: 420, + shared_shape_delete_key_payload_421: 421, + shared_shape_delete_key_payload_422: 422, + shared_shape_delete_key_payload_423: 423, + shared_shape_delete_key_payload_424: 424, + shared_shape_delete_key_payload_425: 425, + shared_shape_delete_key_payload_426: 426, + shared_shape_delete_key_payload_427: 427, + shared_shape_delete_key_payload_428: 428, + shared_shape_delete_key_payload_429: 429, + shared_shape_delete_key_payload_430: 430, + shared_shape_delete_key_payload_431: 431, + shared_shape_delete_key_payload_432: 432, + shared_shape_delete_key_payload_433: 433, + shared_shape_delete_key_payload_434: 434, + shared_shape_delete_key_payload_435: 435, + shared_shape_delete_key_payload_436: 436, + shared_shape_delete_key_payload_437: 437, + shared_shape_delete_key_payload_438: 438, + shared_shape_delete_key_payload_439: 439, + shared_shape_delete_key_payload_440: 440, + shared_shape_delete_key_payload_441: 441, + shared_shape_delete_key_payload_442: 442, + shared_shape_delete_key_payload_443: 443, + shared_shape_delete_key_payload_444: 444, + shared_shape_delete_key_payload_445: 445, + shared_shape_delete_key_payload_446: 446, + shared_shape_delete_key_payload_447: 447, + shared_shape_delete_key_payload_448: 448, + shared_shape_delete_key_payload_449: 449, + shared_shape_delete_key_payload_450: 450, + shared_shape_delete_key_payload_451: 451, + shared_shape_delete_key_payload_452: 452, + shared_shape_delete_key_payload_453: 453, + shared_shape_delete_key_payload_454: 454, + shared_shape_delete_key_payload_455: 455, + shared_shape_delete_key_payload_456: 456, + shared_shape_delete_key_payload_457: 457, + shared_shape_delete_key_payload_458: 458, + shared_shape_delete_key_payload_459: 459, + shared_shape_delete_key_payload_460: 460, + shared_shape_delete_key_payload_461: 461, + shared_shape_delete_key_payload_462: 462, + shared_shape_delete_key_payload_463: 463, + shared_shape_delete_key_payload_464: 464, + shared_shape_delete_key_payload_465: 465, + shared_shape_delete_key_payload_466: 466, + shared_shape_delete_key_payload_467: 467, + shared_shape_delete_key_payload_468: 468, + shared_shape_delete_key_payload_469: 469, + shared_shape_delete_key_payload_470: 470, + shared_shape_delete_key_payload_471: 471, + shared_shape_delete_key_payload_472: 472, + shared_shape_delete_key_payload_473: 473, + shared_shape_delete_key_payload_474: 474, + shared_shape_delete_key_payload_475: 475, + shared_shape_delete_key_payload_476: 476, + shared_shape_delete_key_payload_477: 477, + shared_shape_delete_key_payload_478: 478, + shared_shape_delete_key_payload_479: 479, + shared_shape_delete_key_payload_480: 480, + shared_shape_delete_key_payload_481: 481, + shared_shape_delete_key_payload_482: 482, + shared_shape_delete_key_payload_483: 483, + shared_shape_delete_key_payload_484: 484, + shared_shape_delete_key_payload_485: 485, + shared_shape_delete_key_payload_486: 486, + shared_shape_delete_key_payload_487: 487, + shared_shape_delete_key_payload_488: 488, + shared_shape_delete_key_payload_489: 489, + shared_shape_delete_key_payload_490: 490, + shared_shape_delete_key_payload_491: 491, + shared_shape_delete_key_payload_492: 492, + shared_shape_delete_key_payload_493: 493, + shared_shape_delete_key_payload_494: 494, + shared_shape_delete_key_payload_495: 495, + shared_shape_delete_key_payload_496: 496, + shared_shape_delete_key_payload_497: 497, + shared_shape_delete_key_payload_498: 498, + shared_shape_delete_key_payload_499: 499, + }; +} + +const objects: Record[] = []; +for (let i = 0; i <= OBJECT_COUNT; i++) objects.push(makeObject()); + +const victim = keys[100]; +// Build the shared source index before the first delete through the same +// cache-independent presence path used in the measured loop. +if (!Object.hasOwn(objects[0], keys[499])) + throw new Error("shared-shape benchmark setup failed"); + +let checksum = 0; +const start = Date.now(); +for (let i = 0; i < OBJECT_COUNT; i++) { + delete objects[i][victim]; + const probe = keys[250 + (i % 250)]; + if (Object.hasOwn(objects[i + 1], probe)) checksum += i; +} +console.log( + "shared_shape_delete_ms=" + (Date.now() - start) + " chk=" + (checksum % 97), +); diff --git a/changelog.d/9067-preserve-shared-delete-index.md b/changelog.d/9067-preserve-shared-delete-index.md new file mode 100644 index 0000000000..325ecaba2d --- /dev/null +++ b/changelog.d/9067-preserve-shared-delete-index.md @@ -0,0 +1,13 @@ +Deleting a property no longer strands shape-index accelerators on sibling +objects that share the same keys array. + +The delete path now clones a shared source's validated slot index onto the +deleting object's private, compacted keys array while leaving the original +index in place for siblings. Owned sources retain the cheaper move behavior. +This avoids forcing every untouched sibling to decode and hash the full key +set again on its next indexed lookup. + +On the new 500-key shared-sibling benchmark, 15 interleaved A/B pairs pinned +to one CPU reduced the median measured loop from 171 to 70 ms (**−59.1%**); +the minimum improved from 111 to 43 ms (−61.3%). Every arm produced the same +checksum. diff --git a/crates/perry-runtime/src/object/delete_rest.rs b/crates/perry-runtime/src/object/delete_rest.rs index 60e8a5efe8..60427f30f8 100644 --- a/crates/perry-runtime/src/object/delete_rest.rs +++ b/crates/perry-runtime/src/object/delete_rest.rs @@ -474,6 +474,7 @@ pub extern "C" fn js_object_delete_field( keys_cloned as usize, i as u32, key_count as u32, + !keys_owned, ); // `set_object_keys_array` publishes the cloned edge while preserving // the predecessor's semantic generation and object kind. diff --git a/crates/perry-runtime/src/object/shapes.rs b/crates/perry-runtime/src/object/shapes.rs index d34145c86d..701164ae9c 100644 --- a/crates/perry-runtime/src/object/shapes.rs +++ b/crates/perry-runtime/src/object/shapes.rs @@ -41,6 +41,7 @@ pub(crate) use shapes_slot_list::{ shape_index_migrate_after_delete, shape_index_shift_in_place, SlotList, }; +#[derive(Clone)] pub(crate) struct ShapeIndex { /// Key count covered by `slots`. Longer live array ⟹ catch up /// incrementally (append-only while shared); shorter ⟹ a delete diff --git a/crates/perry-runtime/src/object/shapes_slot_list.rs b/crates/perry-runtime/src/object/shapes_slot_list.rs index 693c34f7be..691c4ff60c 100644 --- a/crates/perry-runtime/src/object/shapes_slot_list.rs +++ b/crates/perry-runtime/src/object/shapes_slot_list.rs @@ -159,8 +159,15 @@ pub(crate) fn shape_index_shift_in_place( /// Safe against a mistake by construction: [`shape_slot_lookup`] re-validates /// the stored key against the requested bytes before returning a slot, so an /// index that is wrong produces a MISS and the caller's own fallback, never a -/// wrong property. Only a fully-built index is carried over; a partially built -/// one is dropped and rebuilt as before. +/// wrong property. Only a fully-built index is carried over. A partial owned +/// index is dropped with its dying source; a partial shared index remains in +/// place and the clone rebuilds as before. +/// +/// A shared source remains live on sibling objects, so its index is cloned +/// before shifting. An owned source is about to die and its index can be moved. +/// This mirrors the keys-array ownership rule itself: forking a shared array is +/// a genuine shape transition, while replacing an owned array transfers its +/// identity. /// /// Returns whether the index was actually carried over: the delete tail uses /// that to skip the `shape_drop` that would otherwise discard it immediately. @@ -170,12 +177,18 @@ pub(crate) fn shape_index_migrate_after_delete( new_keys_id: usize, removed_slot: u32, old_key_count: u32, + old_keys_shared: bool, ) -> bool { if old_keys_id == 0 || new_keys_id == 0 || old_keys_id == new_keys_id { return false; } let mut inner = crate::state::state().shapes.inner.borrow_mut(); - let Some(mut index) = inner.indices.remove(&old_keys_id) else { + let source_index = if old_keys_shared { + inner.indices.get(&old_keys_id).cloned() + } else { + inner.indices.remove(&old_keys_id) + }; + let Some(mut index) = source_index else { return false; }; if index.indexed_len < old_key_count { @@ -358,3 +371,86 @@ pub(crate) fn shape_id_owns_keys_slot(shape_id: u32, slot: *mut u64) -> bool { .get(&shape_id) .is_some_and(|record| std::ptr::addr_of!(record.keys) as *mut u64 == slot) } + +#[cfg(test)] +mod tests { + use super::super::*; + + /// #9006: deleting from one object must copy the shared shape accelerator + /// to its private keys-array clone, not move it away from untouched siblings. + #[test] + fn shared_delete_preserves_the_sibling_shape_index() { + let _lock = crate::gc::global_side_table_test_lock(); + unsafe { + const KEY_COUNT: usize = 40; + let mut packed = Vec::new(); + for i in 0..KEY_COUNT { + packed.extend_from_slice(format!("shared9006_{i:02}").as_bytes()); + packed.push(0); + } + let deleting = crate::object::js_object_alloc_with_shape( + 0x9006_0001, + KEY_COUNT as u32, + packed.as_ptr(), + packed.len() as u32, + ); + let sibling = crate::object::js_object_alloc_with_shape( + 0x9006_0001, + KEY_COUNT as u32, + packed.as_ptr(), + packed.len() as u32, + ); + let shared_keys = crate::object::object_keys_array(deleting); + assert_eq!(shared_keys, crate::object::object_keys_array(sibling)); + let keys_gc = crate::value::addr_class::try_read_gc_header(shared_keys as usize) + .expect("test premise: shared keys must be a live GC allocation"); + assert_ne!( + keys_gc.gc_flags & crate::gc::GC_FLAG_SHAPE_SHARED, + 0, + "test premise: the source keys array must be shared" + ); + + let survivor = b"shared9006_39"; + let survivor_hash = crate::object::key_bytes_hash(survivor.as_ptr(), survivor.len()); + assert_eq!( + shape_slot_lookup(shared_keys, survivor, survivor_hash, KEY_COUNT as u32, true), + Some(39), + "test premise: build the shared source index" + ); + + let victim = b"shared9006_10"; + let victim_key = + crate::string::js_string_from_bytes(victim.as_ptr(), victim.len() as u32); + assert_eq!( + crate::object::js_object_delete_field(deleting, victim_key), + 1 + ); + let private_keys = crate::object::object_keys_array(deleting); + assert_ne!(private_keys, shared_keys); + assert_eq!(crate::object::object_keys_array(sibling), shared_keys); + + assert_eq!( + shape_slot_lookup( + shared_keys, + survivor, + survivor_hash, + KEY_COUNT as u32, + false, + ), + Some(39), + "deleting a sibling stole the shared source index" + ); + assert_eq!( + shape_slot_lookup( + private_keys, + survivor, + survivor_hash, + (KEY_COUNT - 1) as u32, + false, + ), + Some(38), + "the deleting object did not receive the shifted index" + ); + } + } +}