diff --git a/docs/profiling.md b/docs/profiling.md index 22c65e3f..43e18911 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -51,6 +51,7 @@ Trace serialization happens after Context and Runtime teardown. | Heap population | Object, shape, variable-reference, Context and bytecode node counts; lifecycle states; pending jobs | Logical node counts are not allocation counts or byte totals | | Owned storage | Arena slots/free indices/zero queue, object property slots, dense array elements, ordinary ArrayBuffer bytes | `used_bytes` measures initialized inline storage and `capacity_bytes` its reserved capacity; nested allocations and allocator headers are excluded | | Bytecode | Unique instruction slices, deduplicated by their shared storage identity | Rc headers, nested operands, constants and debug data are excluded | +| Static property keys | `bytecode_property_keys`: linked-name count and deduplicated constant-indexed Atom slice bytes, including unused slots | Rc headers and the separate owning references in `auxiliary_atoms` are excluded; no table is allocated for functions without static names | | Atoms and strings | Live table-backed atom count; immediate integers excluded | Full string storage/counts are unavailable because strings can be shared with atoms, bytecode and embedder values | | Shared buffers | SharedArrayBuffer wrapper count | Shared backing bytes are unavailable, avoiding duplication across wrappers/contexts/runtimes | | Allocation events | Actual arena Vec backing-storage allocation, growth and release; stable storage identity; sequence; old/new capacity bytes | `coverage=partial`, `scope=arena-slots-backing-storage`; successful safe Vec capacity transitions only. An `R` does not prove a libc `realloc` call, nor physical relocation | diff --git a/docs/reports/static-property-atoms.json b/docs/reports/static-property-atoms.json new file mode 100644 index 00000000..e4b2ebd4 --- /dev/null +++ b/docs/reports/static-property-atoms.json @@ -0,0 +1,816 @@ +{ + "before_commit": "4e2e69bca89a2f287ea5dc99c9557f993eeb1176", + "after_commit": "a9ddf42ad32cb515335b5db9628897b17bbab0c8", + "binaries": { + "before": { + "qjs": "3fbfd2a4e279d95b4bde6ed4635173da8ba982e76d6d4a0ad88e3056ec410f2f", + "compile-probe": "1087d03de7e497da12dabd685e9783aa281001776e4c82d1dee66a6d6c6e6c82" + }, + "after": { + "qjs": "b2221938a52901a7f682bcfeb99b9fdd0c994cda8c650832440be061443f0437", + "compile-probe": "415e3ad5cf2231adf68f1fab111eec88cadd07f231ecd92d1c8fdcc96a16da70" + } + }, + "workloads": { + "empty_fixed": { + "path": "/home/eric/Documents/Sources/PocketLab/quickjs-oxide/target/cpu-investigation/frame-pointers/empty_loop-fixed.js", + "sha256": "712718c7d9f15dde0f3be565c47ddc4ab1b79b9aade5ba031c2e17fb7ac44e6b" + }, + "prop_fixed": { + "path": "/home/eric/Documents/Sources/PocketLab/quickjs-oxide/target/cpu-investigation/frame-pointers/prop_read-fixed.js", + "sha256": "661603fba02fbf5437c927517107863bb9e27a183028cf64f93bed95d30488e0" + }, + "richards_fixed": { + "path": "/home/eric/Documents/Sources/PocketLab/js-engine-benchmark/dist/quickjs-oxide/richards-fixed-investigation.js", + "sha256": "cf295e5d11938844ff3b8dbe354fae237f08e7f3f3e72a86bc4d6766fb3a27d5" + }, + "richards": { + "path": "/home/eric/Documents/Sources/PocketLab/js-engine-benchmark/dist/quickjs-oxide/richards.js", + "sha256": "3a6a75ed44921a21ccb5d651c4fa40e1168cc398497f5783f70f520b5880f816" + } + }, + "compile_probe_source_sha256": "1a871dac971626629e5b9f6b06359f226bc732833281d84f1921b7b23657084e", + "summary": { + "empty_fixed": { + "before": { + "median_ms": 1945.534529, + "samples_ms": [ + 1989.331808, + 1945.534529, + 1854.448811, + 1946.867875, + 1893.906603 + ], + "compile_publish_median_ms": 0.0552905, + "context_median_ms": 2.2550845, + "map_names": null, + "map_bytes": null, + "functions": 2, + "atoms": 382, + "arena_used": 252216 + }, + "after": { + "median_ms": 1890.326117, + "samples_ms": [ + 1864.892664, + 1890.326117, + 1934.948222, + 1871.206642, + 1925.39786 + ], + "compile_publish_median_ms": 0.056944, + "context_median_ms": 2.290869, + "map_names": 1, + "map_bytes": 32, + "functions": 2, + "atoms": 383, + "arena_used": 252216 + }, + "wall_reduction_percent": 2.8376989036723477 + }, + "prop_fixed": { + "before": { + "median_ms": 10813.373911, + "samples_ms": [ + 11049.347796, + 10699.26893, + 10813.373911, + 11261.891072, + 10700.410408 + ], + "compile_publish_median_ms": 0.09409000000000001, + "context_median_ms": 2.2492685000000003, + "map_names": null, + "map_bytes": null, + "functions": 2, + "atoms": 384, + "arena_used": 252216 + }, + "after": { + "median_ms": 7240.064694, + "samples_ms": [ + 7240.064694, + 7362.439896, + 7198.156985, + 7221.014359, + 7263.503723 + ], + "compile_publish_median_ms": 0.101119, + "context_median_ms": 2.2708705, + "map_names": 10, + "map_bytes": 176, + "functions": 2, + "atoms": 389, + "arena_used": 252216 + }, + "wall_reduction_percent": 33.04527565966271 + }, + "richards_fixed": { + "before": { + "median_ms": 2117.465147, + "samples_ms": [ + 2090.059783, + 2103.731818, + 2117.465147, + 2160.685765, + 2131.310029 + ], + "compile_publish_median_ms": 12.1141335, + "context_median_ms": 2.2512325, + "map_names": null, + "map_bytes": null, + "functions": 61, + "atoms": 465, + "arena_used": 305552 + }, + "after": { + "median_ms": 1477.229404, + "samples_ms": [ + 1473.398739, + 1477.229404, + 1481.965481, + 1471.377542, + 1499.824982 + ], + "compile_publish_median_ms": 12.079653, + "context_median_ms": 2.287642, + "map_names": 359, + "map_bytes": 6736, + "functions": 61, + "atoms": 512, + "arena_used": 305552 + }, + "wall_reduction_percent": 30.235951883651 + }, + "richards": { + "before": { + "median_ms": 8460.680816, + "samples_ms": [ + 8478.643044, + 8371.49954, + 8460.680816, + 8495.951089, + 8340.438413 + ], + "scores": [ + 16.7, + 16.9, + 16.7, + 16.7, + 17.0 + ], + "median_score": 16.7, + "compile_publish_median_ms": 12.6966225, + "context_median_ms": 2.2457520000000004, + "map_names": null, + "map_bytes": null, + "functions": 64, + "atoms": 471, + "arena_used": 308264 + }, + "after": { + "median_ms": 6163.680439, + "samples_ms": [ + 6193.651653, + 6119.404663, + 6153.146716, + 6186.433644, + 6163.680439 + ], + "scores": [ + 24.0, + 24.3, + 24.1, + 24.1, + 24.2 + ], + "median_score": 24.1, + "compile_publish_median_ms": 12.5859965, + "context_median_ms": 2.305301, + "map_names": 364, + "map_bytes": 6912, + "functions": 64, + "atoms": 518, + "arena_used": 308264 + }, + "wall_reduction_percent": 27.14911987527222 + } + }, + "schema": "static-property-atoms-comparison-v1", + "date": "2026-09-09", + "machine": { + "host": "eric-83am / Herdr PocketLab", + "cpu": "AMD Ryzen 7 7840HS", + "kernel": "6.18.44-1-lts", + "build_rust": "1.94.1", + "validation_rust": "1.88.0" + }, + "build": { + "command": "CARGO_PROFILE_RELEASE_DEBUG=1 CARGO_PROFILE_RELEASE_STRIP=none CARGO_TARGET_DIR=target/cpu-profile QUICKJS_OXIDE_BUILD_COMMIT=$(git rev-parse HEAD) cargo build --locked --release -p quickjs-oxide-cli --no-default-features --features profiling", + "diagnostics": "compiled but disabled; no diagnostic source probes; no forced frame pointers", + "compile_probe": "same /tmp/static-atoms-compile-probe.rs compiled by rustc --edition=2024 -C opt-level=3 -C debuginfo=1 against each exact engine/host rlib before switching release builds" + }, + "method": { + "wall_clock": "Python time.perf_counter_ns around whole subprocess; timeout 90 seconds", + "wall_repetitions": 5, + "order": "serial; before/after for even repetitions, after/before for odd repetitions; no concurrent builds during timing", + "compile_probe": "3 processes per workload/version, each 20 fresh Runtime/Context iterations; first 2 iterations per process marked warmup and excluded from medians", + "compile_timer": "Rust Instant around Context::compile: parsing, compilation and publication together; snapshot and output excluded", + "memory_boundary": "compiled bytecode root retained, script not executed; map category unavailable in old binary because feature did not exist; retained mapping counts/bytes measured in after binary", + "workload_revision": "ahaoboy/js-engine-benchmark@2034d98fc8c5f8044e186267593f5d5ea5232caf", + "workload_bodies": "unchanged prior investigation scripts, stored outside repository" + }, + "validation": { + "workspace_tests": { + "passed": 2998, + "failed": 0, + "ignored": 1, + "test_binaries": 13, + "command": "cargo +1.88.0 test --locked --workspace --all-targets --features profiling" + }, + "binary_object_boundary": { + "status": "passed", + "isolation_canaries": 690, + "all_canaries_rejected": true, + "command": "QUICKJS_OXIDE_BOUNDARY_JOBS=12 ./scripts/checks/check-binary-object-boundary.sh", + "host": "eric-83am / PocketLab (Herdr)", + "revision": "a9ddf42ad32cb515335b5db9628897b17bbab0c8", + "log_sha256": "7abf1bc6d9854823ce4bd47d926a9def883f8c90af675ba9ac6d30e5d93d31aa", + "log_location": "target/static-atoms-validation/binary-object-boundary.log (local); target/static-atoms-boundary.log (remote)", + "archive_note": "Final boundary log retained separately; it postdates the raw measurement archive." + }, + "fmt": "passed", + "strict_clippy": { + "production_profiling": "passed", + "workspace_default_lib_bins": "passed", + "workspace_test262_host_lib_bins": "passed" + }, + "source_layout": "passed (418 reachable Rust files, 65 directory READMEs)", + "rust_only": "passed", + "test262": { + "before_file_sha256": "c0c664ab177a37060aa865b90d23fc737d131d26e0cc68ce737492e55f0dc352", + "after_file_sha256": "29301a4afe0d1d18ac9cedff60bf90f1fff6b4d95d0ea233bdbbc2899259b400", + "outcome_count": 102037, + "changed_outcomes": 0, + "new_failures": 0, + "outcome_rows_sha256": "fa99d3349bb4b61f30ba57d7c7f275df64f19691edefc50b96b53887339fa8c3", + "before_header": "# quickjs-oxide Test262 outcome vector v5 engine_semantics_sha256=d4ecc6f3572e631b62311c4e71c660af5f638efd949be2a907135d4c6a9820e9", + "after_header": "# quickjs-oxide Test262 outcome vector v5 engine_semantics_sha256=1bfa4ffbd2b3081315865d7f17c22d319ef21f4dc245f325489a52535852cd72", + "frozen_gate": "Both runs exit 1 because the full-file receipt header fingerprint differs from the frozen baseline; no baseline updated." + } + }, + "archive": { + "file": "quickjs-oxide-static-atoms-pocketlab-2026-09-09.tar.gz", + "bytes": 42235911, + "sha256": "e3261ee85fab36999888fbf5c3cb3207713896e42f20d1a2aa3f334dd1aace5c", + "verified_members": 164 + }, + "timing_columns": [ + "case", + "repetition", + "version", + "wall_ns", + "exit_code", + "stdout", + "stderr", + "harness_score" + ], + "compile_columns": [ + "case", + "repetition", + "version", + "iteration", + "warmup", + "context_ns", + "compile_publish_ns", + "functions", + "atoms", + "map_names", + "map_bytes", + "arena_used" + ], + "timing_samples": [ + ["empty_fixed",0,"before",1989331808,0,"10000000\n","",null], + ["empty_fixed",0,"after",1864892664,0,"10000000\n","",null], + ["empty_fixed",1,"after",1890326117,0,"10000000\n","",null], + ["empty_fixed",1,"before",1945534529,0,"10000000\n","",null], + ["empty_fixed",2,"before",1854448811,0,"10000000\n","",null], + ["empty_fixed",2,"after",1934948222,0,"10000000\n","",null], + ["empty_fixed",3,"after",1871206642,0,"10000000\n","",null], + ["empty_fixed",3,"before",1946867875,0,"10000000\n","",null], + ["empty_fixed",4,"before",1893906603,0,"10000000\n","",null], + ["empty_fixed",4,"after",1925397860,0,"10000000\n","",null], + ["prop_fixed",0,"before",11049347796,0,"20000000\n50000000\n","",null], + ["prop_fixed",0,"after",7240064694,0,"20000000\n50000000\n","",null], + ["prop_fixed",1,"after",7362439896,0,"20000000\n50000000\n","",null], + ["prop_fixed",1,"before",10699268930,0,"20000000\n50000000\n","",null], + ["prop_fixed",2,"before",10813373911,0,"20000000\n50000000\n","",null], + ["prop_fixed",2,"after",7198156985,0,"20000000\n50000000\n","",null], + ["prop_fixed",3,"after",7221014359,0,"20000000\n50000000\n","",null], + ["prop_fixed",3,"before",11261891072,0,"20000000\n50000000\n","",null], + ["prop_fixed",4,"before",10700410408,0,"20000000\n50000000\n","",null], + ["prop_fixed",4,"after",7263503723,0,"20000000\n50000000\n","",null], + ["richards_fixed",0,"before",2090059783,0,"richards-fixed:10\n","",null], + ["richards_fixed",0,"after",1473398739,0,"richards-fixed:10\n","",null], + ["richards_fixed",1,"after",1477229404,0,"richards-fixed:10\n","",null], + ["richards_fixed",1,"before",2103731818,0,"richards-fixed:10\n","",null], + ["richards_fixed",2,"before",2117465147,0,"richards-fixed:10\n","",null], + ["richards_fixed",2,"after",1481965481,0,"richards-fixed:10\n","",null], + ["richards_fixed",3,"after",1471377542,0,"richards-fixed:10\n","",null], + ["richards_fixed",3,"before",2160685765,0,"richards-fixed:10\n","",null], + ["richards_fixed",4,"before",2131310029,0,"richards-fixed:10\n","",null], + ["richards_fixed",4,"after",1499824982,0,"richards-fixed:10\n","",null], + ["richards",0,"before",8478643044,0,"Richards: 16.7\n----\nScore: 16.7\n","",16.7], + ["richards",0,"after",6193651653,0,"Richards: 24.0\n----\nScore: 24.0\n","",24.0], + ["richards",1,"after",6119404663,0,"Richards: 24.3\n----\nScore: 24.3\n","",24.3], + ["richards",1,"before",8371499540,0,"Richards: 16.9\n----\nScore: 16.9\n","",16.9], + ["richards",2,"before",8460680816,0,"Richards: 16.7\n----\nScore: 16.7\n","",16.7], + ["richards",2,"after",6153146716,0,"Richards: 24.1\n----\nScore: 24.1\n","",24.1], + ["richards",3,"after",6186433644,0,"Richards: 24.1\n----\nScore: 24.1\n","",24.1], + ["richards",3,"before",8495951089,0,"Richards: 16.7\n----\nScore: 16.7\n","",16.7], + ["richards",4,"before",8340438413,0,"Richards: 17.0\n----\nScore: 17.0\n","",17.0], + ["richards",4,"after",6163680439,0,"Richards: 24.2\n----\nScore: 24.2\n","",24.2] + ], + "compile_samples": [ + ["empty_fixed",0,"before",0,true,3923518,168152,2,382,null,null,252216], + ["empty_fixed",0,"before",1,true,2399812,65796,2,382,null,null,252216], + ["empty_fixed",0,"before",2,false,2229325,56458,2,382,null,null,252216], + ["empty_fixed",0,"before",3,false,3890595,89531,2,382,null,null,252216], + ["empty_fixed",0,"before",4,false,2225578,54585,2,382,null,null,252216], + ["empty_fixed",0,"before",5,false,2233403,55206,2,382,null,null,252216], + ["empty_fixed",0,"before",6,false,2239616,55155,2,382,null,null,252216], + ["empty_fixed",0,"before",7,false,2264522,69834,2,382,null,null,252216], + ["empty_fixed",0,"before",8,false,2250065,54825,2,382,null,null,252216], + ["empty_fixed",0,"before",9,false,2258292,55686,2,382,null,null,252216], + ["empty_fixed",0,"before",10,false,2271666,55285,2,382,null,null,252216], + ["empty_fixed",0,"before",11,false,2253501,55707,2,382,null,null,252216], + ["empty_fixed",0,"before",12,false,2256648,54284,2,382,null,null,252216], + ["empty_fixed",0,"before",13,false,2258913,55106,2,382,null,null,252216], + ["empty_fixed",0,"before",14,false,2266387,55486,2,382,null,null,252216], + ["empty_fixed",0,"before",15,false,2270624,65585,2,382,null,null,252216], + ["empty_fixed",0,"before",16,false,2292287,53933,2,382,null,null,252216], + ["empty_fixed",0,"before",17,false,2262498,55767,2,382,null,null,252216], + ["empty_fixed",0,"before",18,false,2282157,54925,2,382,null,null,252216], + ["empty_fixed",0,"before",19,false,2256397,55206,2,382,null,null,252216], + ["empty_fixed",0,"after",0,true,3263644,179513,2,383,1,32,252216], + ["empty_fixed",0,"after",1,true,2451842,61418,2,383,1,32,252216], + ["empty_fixed",0,"after",2,false,2281826,57640,2,383,1,32,252216], + ["empty_fixed",0,"after",3,false,2301112,56979,2,383,1,32,252216], + ["empty_fixed",0,"after",4,false,2292888,56909,2,383,1,32,252216], + ["empty_fixed",0,"after",5,false,2297697,57440,2,383,1,32,252216], + ["empty_fixed",0,"after",6,false,2298357,55797,2,383,1,32,252216], + ["empty_fixed",0,"after",7,false,2318115,57540,2,383,1,32,252216], + ["empty_fixed",0,"after",8,false,2317254,63973,2,383,1,32,252216], + ["empty_fixed",0,"after",9,false,2302185,56579,2,383,1,32,252216], + ["empty_fixed",0,"after",10,false,2293809,56428,2,383,1,32,252216], + ["empty_fixed",0,"after",11,false,2307957,59313,2,383,1,32,252216], + ["empty_fixed",0,"after",12,false,2309328,56849,2,383,1,32,252216], + ["empty_fixed",0,"after",13,false,2297566,56869,2,383,1,32,252216], + ["empty_fixed",0,"after",14,false,2303478,56137,2,383,1,32,252216], + ["empty_fixed",0,"after",15,false,2292738,55857,2,383,1,32,252216], + ["empty_fixed",0,"after",16,false,2319067,56669,2,383,1,32,252216], + ["empty_fixed",0,"after",17,false,2307635,57359,2,383,1,32,252216], + ["empty_fixed",0,"after",18,false,2312615,57360,2,383,1,32,252216], + ["empty_fixed",0,"after",19,false,2294900,55597,2,383,1,32,252216], + ["empty_fixed",1,"after",0,true,2610265,113918,2,383,1,32,252216], + ["empty_fixed",1,"after",1,true,2477661,62330,2,383,1,32,252216], + ["empty_fixed",1,"after",2,false,2792874,128847,2,383,1,32,252216], + ["empty_fixed",1,"after",3,false,2334808,61247,2,383,1,32,252216], + ["empty_fixed",1,"after",4,false,2286295,59053,2,383,1,32,252216], + ["empty_fixed",1,"after",5,false,2729242,63712,2,383,1,32,252216], + ["empty_fixed",1,"after",6,false,2234947,57290,2,383,1,32,252216], + ["empty_fixed",1,"after",7,false,2243372,56378,2,383,1,32,252216], + ["empty_fixed",1,"after",8,false,2272197,60807,2,383,1,32,252216], + ["empty_fixed",1,"after",9,false,2254604,55316,2,383,1,32,252216], + ["empty_fixed",1,"after",10,false,2244766,55747,2,383,1,32,252216], + ["empty_fixed",1,"after",11,false,3151319,129147,2,383,1,32,252216], + ["empty_fixed",1,"after",12,false,2441502,105812,2,383,1,32,252216], + ["empty_fixed",1,"after",13,false,3103348,81937,2,383,1,32,252216], + ["empty_fixed",1,"after",14,false,2270925,59364,2,383,1,32,252216], + ["empty_fixed",1,"after",15,false,2288669,57120,2,383,1,32,252216], + ["empty_fixed",1,"after",16,false,2275735,55897,2,383,1,32,252216], + ["empty_fixed",1,"after",17,false,2279381,65225,2,383,1,32,252216], + ["empty_fixed",1,"after",18,false,2289000,57230,2,383,1,32,252216], + ["empty_fixed",1,"after",19,false,2293929,57510,2,383,1,32,252216], + ["empty_fixed",1,"before",0,true,2617087,187158,2,382,null,null,252216], + ["empty_fixed",1,"before",1,true,2417486,59434,2,382,null,null,252216], + ["empty_fixed",1,"before",2,false,2272868,57821,2,382,null,null,252216], + ["empty_fixed",1,"before",3,false,2251568,55557,2,382,null,null,252216], + ["empty_fixed",1,"before",4,false,2241660,54785,2,382,null,null,252216], + ["empty_fixed",1,"before",5,false,2250747,54083,2,382,null,null,252216], + ["empty_fixed",1,"before",6,false,2323515,60827,2,382,null,null,252216], + ["empty_fixed",1,"before",7,false,2274833,71156,2,382,null,null,252216], + ["empty_fixed",1,"before",8,false,2258723,55175,2,382,null,null,252216], + ["empty_fixed",1,"before",9,false,2246839,53512,2,382,null,null,252216], + ["empty_fixed",1,"before",10,false,2250806,55055,2,382,null,null,252216], + ["empty_fixed",1,"before",11,false,2266186,55195,2,382,null,null,252216], + ["empty_fixed",1,"before",12,false,2261217,54815,2,382,null,null,252216], + ["empty_fixed",1,"before",13,false,2251187,53422,2,382,null,null,252216], + ["empty_fixed",1,"before",14,false,2256398,60606,2,382,null,null,252216], + ["empty_fixed",1,"before",15,false,2268912,57800,2,382,null,null,252216], + ["empty_fixed",1,"before",16,false,2271666,62610,2,382,null,null,252216], + ["empty_fixed",1,"before",17,false,2258441,55035,2,382,null,null,252216], + ["empty_fixed",1,"before",18,false,2257480,54074,2,382,null,null,252216], + ["empty_fixed",1,"before",19,false,2254513,54715,2,382,null,null,252216], + ["empty_fixed",2,"before",0,true,2577121,115161,2,382,null,null,252216], + ["empty_fixed",2,"before",1,true,2989019,65645,2,382,null,null,252216], + ["empty_fixed",2,"before",2,false,2282897,59022,2,382,null,null,252216], + ["empty_fixed",2,"before",3,false,2263812,59464,2,382,null,null,252216], + ["empty_fixed",2,"before",4,false,2250916,56689,2,382,null,null,252216], + ["empty_fixed",2,"before",5,false,2240888,55185,2,382,null,null,252216], + ["empty_fixed",2,"before",6,false,2232692,55867,2,382,null,null,252216], + ["empty_fixed",2,"before",7,false,2217864,54014,2,382,null,null,252216], + ["empty_fixed",2,"before",8,false,2259082,54935,2,382,null,null,252216], + ["empty_fixed",2,"before",9,false,2252881,55516,2,382,null,null,252216], + ["empty_fixed",2,"before",10,false,2253262,64113,2,382,null,null,252216], + ["empty_fixed",2,"before",11,false,2237331,55296,2,382,null,null,252216], + ["empty_fixed",2,"before",12,false,2240387,55416,2,382,null,null,252216], + ["empty_fixed",2,"before",13,false,2246248,55336,2,382,null,null,252216], + ["empty_fixed",2,"before",14,false,2243423,55336,2,382,null,null,252216], + ["empty_fixed",2,"before",15,false,2232752,54044,2,382,null,null,252216], + ["empty_fixed",2,"before",16,false,2250676,55035,2,382,null,null,252216], + ["empty_fixed",2,"before",17,false,2255656,56157,2,382,null,null,252216], + ["empty_fixed",2,"before",18,false,2238663,55667,2,382,null,null,252216], + ["empty_fixed",2,"before",19,false,2230477,54574,2,382,null,null,252216], + ["empty_fixed",2,"after",0,true,3508814,163833,2,383,1,32,252216], + ["empty_fixed",2,"after",1,true,2438757,60616,2,383,1,32,252216], + ["empty_fixed",2,"after",2,false,2296123,57470,2,383,1,32,252216], + ["empty_fixed",2,"after",3,false,2293437,59665,2,383,1,32,252216], + ["empty_fixed",2,"after",4,false,2270664,60576,2,383,1,32,252216], + ["empty_fixed",2,"after",5,false,2256528,54634,2,383,1,32,252216], + ["empty_fixed",2,"after",6,false,2280393,57009,2,383,1,32,252216], + ["empty_fixed",2,"after",7,false,2281505,55306,2,383,1,32,252216], + ["empty_fixed",2,"after",8,false,2293739,54735,2,383,1,32,252216], + ["empty_fixed",2,"after",9,false,2247451,54254,2,383,1,32,252216], + ["empty_fixed",2,"after",10,false,2272197,55877,2,383,1,32,252216], + ["empty_fixed",2,"after",11,false,2262119,55226,2,383,1,32,252216], + ["empty_fixed",2,"after",12,false,2277168,56739,2,383,1,32,252216], + ["empty_fixed",2,"after",13,false,2261878,55175,2,383,1,32,252216], + ["empty_fixed",2,"after",14,false,2273560,55476,2,383,1,32,252216], + ["empty_fixed",2,"after",15,false,2278210,54574,2,383,1,32,252216], + ["empty_fixed",2,"after",16,false,2284782,55125,2,383,1,32,252216], + ["empty_fixed",2,"after",17,false,2268210,54444,2,383,1,32,252216], + ["empty_fixed",2,"after",18,false,2287127,58141,2,383,1,32,252216], + ["empty_fixed",2,"after",19,false,2269643,55506,2,383,1,32,252216], + ["prop_fixed",0,"before",0,true,2558375,204962,2,384,null,null,252216], + ["prop_fixed",0,"before",1,true,2414450,99931,2,384,null,null,252216], + ["prop_fixed",0,"before",2,false,2249664,97787,2,384,null,null,252216], + ["prop_fixed",0,"before",3,false,2247050,95793,2,384,null,null,252216], + ["prop_fixed",0,"before",4,false,2251799,93910,2,384,null,null,252216], + ["prop_fixed",0,"before",5,false,2240447,92837,2,384,null,null,252216], + ["prop_fixed",0,"before",6,false,2249334,98348,2,384,null,null,252216], + ["prop_fixed",0,"before",7,false,2246529,94221,2,384,null,null,252216], + ["prop_fixed",0,"before",8,false,2266627,98569,2,384,null,null,252216], + ["prop_fixed",0,"before",9,false,2243683,102446,2,384,null,null,252216], + ["prop_fixed",0,"before",10,false,2236980,92878,2,384,null,null,252216], + ["prop_fixed",0,"before",11,false,2265355,93529,2,384,null,null,252216], + ["prop_fixed",0,"before",12,false,2256588,93709,2,384,null,null,252216], + ["prop_fixed",0,"before",13,false,2245676,93439,2,384,null,null,252216], + ["prop_fixed",0,"before",14,false,2238514,94010,2,384,null,null,252216], + ["prop_fixed",0,"before",15,false,2249815,93569,2,384,null,null,252216], + ["prop_fixed",0,"before",16,false,2259704,95643,2,384,null,null,252216], + ["prop_fixed",0,"before",17,false,2253522,94210,2,384,null,null,252216], + ["prop_fixed",0,"before",18,false,2237993,92487,2,384,null,null,252216], + ["prop_fixed",0,"before",19,false,2239705,92477,2,384,null,null,252216], + ["prop_fixed",0,"after",0,true,3570802,215352,2,389,10,176,252216], + ["prop_fixed",0,"after",1,true,2489994,107155,2,389,10,176,252216], + ["prop_fixed",0,"after",2,false,2291434,102316,2,389,10,176,252216], + ["prop_fixed",0,"after",3,false,2302978,102696,2,389,10,176,252216], + ["prop_fixed",0,"after",4,false,2287777,100753,2,389,10,176,252216], + ["prop_fixed",0,"after",5,false,3208390,110691,2,389,10,176,252216], + ["prop_fixed",0,"after",6,false,2254453,100753,2,389,10,176,252216], + ["prop_fixed",0,"after",7,false,2262949,100092,2,389,10,176,252216], + ["prop_fixed",0,"after",8,false,2274392,101323,2,389,10,176,252216], + ["prop_fixed",0,"after",9,false,2267990,127003,2,389,10,176,252216], + ["prop_fixed",0,"after",10,false,2264653,99350,2,389,10,176,252216], + ["prop_fixed",0,"after",11,false,2277748,99961,2,389,10,176,252216], + ["prop_fixed",0,"after",12,false,2279692,99761,2,389,10,176,252216], + ["prop_fixed",0,"after",13,false,2280935,100172,2,389,10,176,252216], + ["prop_fixed",0,"after",14,false,2276295,99139,2,389,10,176,252216], + ["prop_fixed",0,"after",15,false,2286285,100613,2,389,10,176,252216], + ["prop_fixed",0,"after",16,false,2284181,100091,2,389,10,176,252216], + ["prop_fixed",0,"after",17,false,2277127,100963,2,389,10,176,252216], + ["prop_fixed",0,"after",18,false,2279271,99801,2,389,10,176,252216], + ["prop_fixed",0,"after",19,false,2266888,98137,2,389,10,176,252216], + ["prop_fixed",1,"after",0,true,3476082,161148,2,389,10,176,252216], + ["prop_fixed",1,"after",1,true,2438476,106814,2,389,10,176,252216], + ["prop_fixed",1,"after",2,false,2260935,101864,2,389,10,176,252216], + ["prop_fixed",1,"after",3,false,2246338,101374,2,389,10,176,252216], + ["prop_fixed",1,"after",4,false,2250727,117304,2,389,10,176,252216], + ["prop_fixed",1,"after",5,false,2269402,100693,2,389,10,176,252216], + ["prop_fixed",1,"after",6,false,2269703,101684,2,389,10,176,252216], + ["prop_fixed",1,"after",7,false,2276215,118197,2,389,10,176,252216], + ["prop_fixed",1,"after",8,false,2271126,102396,2,389,10,176,252216], + ["prop_fixed",1,"after",9,false,2258722,99500,2,389,10,176,252216], + ["prop_fixed",1,"after",10,false,2266217,100211,2,389,10,176,252216], + ["prop_fixed",1,"after",11,false,2289331,103468,2,389,10,176,252216], + ["prop_fixed",1,"after",12,false,2264793,101905,2,389,10,176,252216], + ["prop_fixed",1,"after",13,false,2269552,107797,2,389,10,176,252216], + ["prop_fixed",1,"after",14,false,2260436,99781,2,389,10,176,252216], + ["prop_fixed",1,"after",15,false,2276726,101955,2,389,10,176,252216], + ["prop_fixed",1,"after",16,false,2275134,101344,2,389,10,176,252216], + ["prop_fixed",1,"after",17,false,2270615,100392,2,389,10,176,252216], + ["prop_fixed",1,"after",18,false,2258000,100071,2,389,10,176,252216], + ["prop_fixed",1,"after",19,false,2265484,101054,2,389,10,176,252216], + ["prop_fixed",1,"before",0,true,2660560,213900,2,384,null,null,252216], + ["prop_fixed",1,"before",1,true,2455187,107044,2,384,null,null,252216], + ["prop_fixed",1,"before",2,false,3763461,132344,2,384,null,null,252216], + ["prop_fixed",1,"before",3,false,2404270,128797,2,384,null,null,252216], + ["prop_fixed",1,"before",4,false,2936929,107246,2,384,null,null,252216], + ["prop_fixed",1,"before",5,false,2238043,96905,2,384,null,null,252216], + ["prop_fixed",1,"before",6,false,2213485,92627,2,384,null,null,252216], + ["prop_fixed",1,"before",7,false,2221170,93879,2,384,null,null,252216], + ["prop_fixed",1,"before",8,false,2227593,93128,2,384,null,null,252216], + ["prop_fixed",1,"before",9,false,2236800,93209,2,384,null,null,252216], + ["prop_fixed",1,"before",10,false,2232431,93038,2,384,null,null,252216], + ["prop_fixed",1,"before",11,false,2235959,93809,2,384,null,null,252216], + ["prop_fixed",1,"before",12,false,2238673,93549,2,384,null,null,252216], + ["prop_fixed",1,"before",13,false,2262038,95312,2,384,null,null,252216], + ["prop_fixed",1,"before",14,false,2249203,124559,2,384,null,null,252216], + ["prop_fixed",1,"before",15,false,2242731,96876,2,384,null,null,252216], + ["prop_fixed",1,"before",16,false,2279261,96324,2,384,null,null,252216], + ["prop_fixed",1,"before",17,false,2250656,94812,2,384,null,null,252216], + ["prop_fixed",1,"before",18,false,2248112,93399,2,384,null,null,252216], + ["prop_fixed",1,"before",19,false,2229245,93519,2,384,null,null,252216], + ["prop_fixed",2,"before",0,true,3108439,156610,2,384,null,null,252216], + ["prop_fixed",2,"before",1,true,2402937,101284,2,384,null,null,252216], + ["prop_fixed",2,"before",2,false,2261247,96916,2,384,null,null,252216], + ["prop_fixed",2,"before",3,false,2240908,94952,2,384,null,null,252216], + ["prop_fixed",2,"before",4,false,2238172,93719,2,384,null,null,252216], + ["prop_fixed",2,"before",5,false,2249864,92527,2,384,null,null,252216], + ["prop_fixed",2,"before",6,false,2256548,93830,2,384,null,null,252216], + ["prop_fixed",2,"before",7,false,2256407,94591,2,384,null,null,252216], + ["prop_fixed",2,"before",8,false,2255125,94541,2,384,null,null,252216], + ["prop_fixed",2,"before",9,false,2245286,93138,2,384,null,null,252216], + ["prop_fixed",2,"before",10,false,2257300,95072,2,384,null,null,252216], + ["prop_fixed",2,"before",11,false,2268220,96524,2,384,null,null,252216], + ["prop_fixed",2,"before",12,false,2250005,94811,2,384,null,null,252216], + ["prop_fixed",2,"before",13,false,2240557,93358,2,384,null,null,252216], + ["prop_fixed",2,"before",14,false,2236438,93109,2,384,null,null,252216], + ["prop_fixed",2,"before",15,false,2252589,94170,2,384,null,null,252216], + ["prop_fixed",2,"before",16,false,2249474,93750,2,384,null,null,252216], + ["prop_fixed",2,"before",17,false,2261167,104500,2,384,null,null,252216], + ["prop_fixed",2,"before",18,false,2238091,93118,2,384,null,null,252216], + ["prop_fixed",2,"before",19,false,2268080,94601,2,384,null,null,252216], + ["prop_fixed",2,"after",0,true,3535253,224730,2,389,10,176,252216], + ["prop_fixed",2,"after",1,true,2483873,108597,2,389,10,176,252216], + ["prop_fixed",2,"after",2,false,2295021,105862,2,389,10,176,252216], + ["prop_fixed",2,"after",3,false,2285773,105452,2,389,10,176,252216], + ["prop_fixed",2,"after",4,false,2266927,103578,2,389,10,176,252216], + ["prop_fixed",2,"after",5,false,3326406,107826,2,389,10,176,252216], + ["prop_fixed",2,"after",6,false,2245957,103198,2,389,10,176,252216], + ["prop_fixed",2,"after",7,false,2252570,101204,2,389,10,176,252216], + ["prop_fixed",2,"after",8,false,2249475,100081,2,389,10,176,252216], + ["prop_fixed",2,"after",9,false,2252499,112846,2,389,10,176,252216], + ["prop_fixed",2,"after",10,false,2252390,99580,2,389,10,176,252216], + ["prop_fixed",2,"after",11,false,2264012,100803,2,389,10,176,252216], + ["prop_fixed",2,"after",12,false,2263942,99961,2,389,10,176,252216], + ["prop_fixed",2,"after",13,false,2281245,99510,2,389,10,176,252216], + ["prop_fixed",2,"after",14,false,2270415,99471,2,389,10,176,252216], + ["prop_fixed",2,"after",15,false,2278269,101354,2,389,10,176,252216], + ["prop_fixed",2,"after",16,false,2284330,101444,2,389,10,176,252216], + ["prop_fixed",2,"after",17,false,2288399,99921,2,389,10,176,252216], + ["prop_fixed",2,"after",18,false,2283430,101344,2,389,10,176,252216], + ["prop_fixed",2,"after",19,false,2278268,101184,2,389,10,176,252216], + ["richards_fixed",0,"before",0,true,2635472,13469712,61,465,null,null,305552], + ["richards_fixed",0,"before",1,true,2301223,9588788,61,465,null,null,305552], + ["richards_fixed",0,"before",2,false,2249644,9347234,61,465,null,null,305552], + ["richards_fixed",0,"before",3,false,2260375,12216976,61,465,null,null,305552], + ["richards_fixed",0,"before",4,false,2248241,12008668,61,465,null,null,305552], + ["richards_fixed",0,"before",5,false,2314789,12133957,61,465,null,null,305552], + ["richards_fixed",0,"before",6,false,2235809,12168553,61,465,null,null,305552], + ["richards_fixed",0,"before",7,false,2260285,12109230,61,465,null,null,305552], + ["richards_fixed",0,"before",8,false,2252880,12069904,61,465,null,null,305552], + ["richards_fixed",0,"before",9,false,2256678,12115291,61,465,null,null,305552], + ["richards_fixed",0,"before",10,false,2245056,12024548,61,465,null,null,305552], + ["richards_fixed",0,"before",11,false,2253862,12045217,61,465,null,null,305552], + ["richards_fixed",0,"before",12,false,2252771,12039536,61,465,null,null,305552], + ["richards_fixed",0,"before",13,false,2250526,12042512,61,465,null,null,305552], + ["richards_fixed",0,"before",14,false,2230818,12042813,61,465,null,null,305552], + ["richards_fixed",0,"before",15,false,2256278,12090673,61,465,null,null,305552], + ["richards_fixed",0,"before",16,false,2248022,12035449,61,465,null,null,305552], + ["richards_fixed",0,"before",17,false,2246929,12024999,61,465,null,null,305552], + ["richards_fixed",0,"before",18,false,2249675,12112345,61,465,null,null,305552], + ["richards_fixed",0,"before",19,false,2258151,12108949,61,465,null,null,305552], + ["richards_fixed",0,"after",0,true,3301007,11738249,61,512,359,6736,305552], + ["richards_fixed",0,"after",1,true,2342262,12269085,61,512,359,6736,305552], + ["richards_fixed",0,"after",2,false,2297346,12231363,61,512,359,6736,305552], + ["richards_fixed",0,"after",3,false,3807004,12093980,61,512,359,6736,305552], + ["richards_fixed",0,"after",4,false,2291705,12068611,61,512,359,6736,305552], + ["richards_fixed",0,"after",5,false,2266999,12168342,61,512,359,6736,305552], + ["richards_fixed",0,"after",6,false,2363402,12255769,61,512,359,6736,305552], + ["richards_fixed",0,"after",7,false,2279862,12081136,61,512,359,6736,305552], + ["richards_fixed",0,"after",8,false,2289992,12175816,61,512,359,6736,305552], + ["richards_fixed",0,"after",9,false,2288498,12062630,61,512,359,6736,305552], + ["richards_fixed",0,"after",10,false,2277377,12087188,61,512,359,6736,305552], + ["richards_fixed",0,"after",11,false,2280203,12065946,61,512,359,6736,305552], + ["richards_fixed",0,"after",12,false,2287928,12083941,61,512,359,6736,305552], + ["richards_fixed",0,"after",13,false,2279311,12064985,61,512,359,6736,305552], + ["richards_fixed",0,"after",14,false,2283740,12160828,61,512,359,6736,305552], + ["richards_fixed",0,"after",15,false,2327473,12069213,61,512,359,6736,305552], + ["richards_fixed",0,"after",16,false,2281405,12087187,61,512,359,6736,305552], + ["richards_fixed",0,"after",17,false,2286345,12091535,61,512,359,6736,305552], + ["richards_fixed",0,"after",18,false,2284100,12111714,61,512,359,6736,305552], + ["richards_fixed",0,"after",19,false,2294621,12092617,61,512,359,6736,305552], + ["richards_fixed",1,"after",0,true,2578114,12510557,61,512,359,6736,305552], + ["richards_fixed",1,"after",1,true,2353663,12229088,61,512,359,6736,305552], + ["richards_fixed",1,"after",2,false,2267689,12176288,61,512,359,6736,305552], + ["richards_fixed",1,"after",3,false,2278469,12093249,61,512,359,6736,305552], + ["richards_fixed",1,"after",4,false,2278429,12002926,61,512,359,6736,305552], + ["richards_fixed",1,"after",5,false,2268481,12766887,61,512,359,6736,305552], + ["richards_fixed",1,"after",6,false,2405082,12443359,61,512,359,6736,305552], + ["richards_fixed",1,"after",7,false,2937450,13345754,61,512,359,6736,305552], + ["richards_fixed",1,"after",8,false,2365396,12379928,61,512,359,6736,305552], + ["richards_fixed",1,"after",9,false,2372029,12415114,61,512,359,6736,305552], + ["richards_fixed",1,"after",10,false,2364113,12271400,61,512,359,6736,305552], + ["richards_fixed",1,"after",11,false,2345087,12417819,61,512,359,6736,305552], + ["richards_fixed",1,"after",12,false,2367910,12354408,61,512,359,6736,305552], + ["richards_fixed",1,"after",13,false,2367339,12310825,61,512,359,6736,305552], + ["richards_fixed",1,"after",14,false,2341181,12086556,61,512,359,6736,305552], + ["richards_fixed",1,"after",15,false,2284421,12013286,61,512,359,6736,305552], + ["richards_fixed",1,"after",16,false,2275133,13834721,61,512,359,6736,305552], + ["richards_fixed",1,"after",17,false,2385244,12054425,61,512,359,6736,305552], + ["richards_fixed",1,"after",18,false,2263540,12078170,61,512,359,6736,305552], + ["richards_fixed",1,"after",19,false,2282397,12044405,61,512,359,6736,305552], + ["richards_fixed",1,"before",0,true,3464679,12635385,61,465,null,null,305552], + ["richards_fixed",1,"before",1,true,2318497,12339058,61,465,null,null,305552], + ["richards_fixed",1,"before",2,false,2238424,12345711,61,465,null,null,305552], + ["richards_fixed",1,"before",3,false,2235527,12112976,61,465,null,null,305552], + ["richards_fixed",1,"before",4,false,2230197,12141121,61,465,null,null,305552], + ["richards_fixed",1,"before",5,false,2250667,12132313,61,465,null,null,305552], + ["richards_fixed",1,"before",6,false,2239094,12121582,61,465,null,null,305552], + ["richards_fixed",1,"before",7,false,2237520,12110903,61,465,null,null,305552], + ["richards_fixed",1,"before",8,false,2235828,12104521,61,465,null,null,305552], + ["richards_fixed",1,"before",9,false,2227863,10320507,61,465,null,null,305552], + ["richards_fixed",1,"before",10,false,2260204,9466824,61,465,null,null,305552], + ["richards_fixed",1,"before",11,false,2257239,9498143,61,465,null,null,305552], + ["richards_fixed",1,"before",12,false,2267869,9820440,61,465,null,null,305552], + ["richards_fixed",1,"before",13,false,2251458,9671696,61,465,null,null,305552], + ["richards_fixed",1,"before",14,false,2268841,9393082,61,465,null,null,305552], + ["richards_fixed",1,"before",15,false,2244655,9803778,61,465,null,null,305552], + ["richards_fixed",1,"before",16,false,2252790,9581523,61,465,null,null,305552], + ["richards_fixed",1,"before",17,false,2265916,9540774,61,465,null,null,305552], + ["richards_fixed",1,"before",18,false,2263812,9967251,61,465,null,null,305552], + ["richards_fixed",1,"before",19,false,2248402,12208158,61,465,null,null,305552], + ["richards_fixed",2,"before",0,true,2548907,13799815,61,465,null,null,305552], + ["richards_fixed",2,"before",1,true,2327723,12454360,61,465,null,null,305552], + ["richards_fixed",2,"before",2,false,2250826,12444371,61,465,null,null,305552], + ["richards_fixed",2,"before",3,false,2240187,12261179,61,465,null,null,305552], + ["richards_fixed",2,"before",4,false,2260806,12228217,61,465,null,null,305552], + ["richards_fixed",2,"before",5,false,2284070,12283392,61,465,null,null,305552], + ["richards_fixed",2,"before",6,false,2267118,12278334,61,465,null,null,305552], + ["richards_fixed",2,"before",7,false,2247891,12214150,61,465,null,null,305552], + ["richards_fixed",2,"before",8,false,2251708,12221504,61,465,null,null,305552], + ["richards_fixed",2,"before",9,false,2269331,12237223,61,465,null,null,305552], + ["richards_fixed",2,"before",10,false,2251007,12215543,61,465,null,null,305552], + ["richards_fixed",2,"before",11,false,3802545,12787998,61,465,null,null,305552], + ["richards_fixed",2,"before",12,false,2261988,12290717,61,465,null,null,305552], + ["richards_fixed",2,"before",13,false,2244264,12358726,61,465,null,null,305552], + ["richards_fixed",2,"before",14,false,2246559,12219971,61,465,null,null,305552], + ["richards_fixed",2,"before",15,false,2237451,12205102,61,465,null,null,305552], + ["richards_fixed",2,"before",16,false,2264122,12202738,61,465,null,null,305552], + ["richards_fixed",2,"before",17,false,2262479,12195925,61,465,null,null,305552], + ["richards_fixed",2,"before",18,false,2250936,12467023,61,465,null,null,305552], + ["richards_fixed",2,"before",19,false,2380164,12509504,61,465,null,null,305552], + ["richards_fixed",2,"after",0,true,3176908,11955315,61,512,359,6736,305552], + ["richards_fixed",2,"after",1,true,2878617,12370498,61,512,359,6736,305552], + ["richards_fixed",2,"after",2,false,2228935,9791042,61,512,359,6736,305552], + ["richards_fixed",2,"after",3,false,2338574,9382803,61,512,359,6736,305552], + ["richards_fixed",2,"after",4,false,2277738,9344119,61,512,359,6736,305552], + ["richards_fixed",2,"after",5,false,2287537,9335442,61,512,359,6736,305552], + ["richards_fixed",2,"after",6,false,2275894,9323859,61,512,359,6736,305552], + ["richards_fixed",2,"after",7,false,2323236,9319281,61,512,359,6736,305552], + ["richards_fixed",2,"after",8,false,2294389,9397902,61,512,359,6736,305552], + ["richards_fixed",2,"after",9,false,2288548,9300785,61,512,359,6736,305552], + ["richards_fixed",2,"after",10,false,2283490,9291557,61,512,359,6736,305552], + ["richards_fixed",2,"after",11,false,2277057,9757429,61,512,359,6736,305552], + ["richards_fixed",2,"after",12,false,2287858,9524944,61,512,359,6736,305552], + ["richards_fixed",2,"after",13,false,2292015,9386861,61,512,359,6736,305552], + ["richards_fixed",2,"after",14,false,2287747,9413009,61,512,359,6736,305552], + ["richards_fixed",2,"after",15,false,2266467,9419843,61,512,359,6736,305552], + ["richards_fixed",2,"after",16,false,2279551,12088319,61,512,359,6736,305552], + ["richards_fixed",2,"after",17,false,2284060,12050196,61,512,359,6736,305552], + ["richards_fixed",2,"after",18,false,2276826,12064804,61,512,359,6736,305552], + ["richards_fixed",2,"after",19,false,2291824,12070074,61,512,359,6736,305552], + ["richards",0,"before",0,true,2797633,13330586,64,471,null,null,308264], + ["richards",0,"before",1,true,2343483,13962635,64,471,null,null,308264], + ["richards",0,"before",2,false,2240958,12842301,64,471,null,null,308264], + ["richards",0,"before",3,false,2241358,12728053,64,471,null,null,308264], + ["richards",0,"before",4,false,2235878,12753001,64,471,null,null,308264], + ["richards",0,"before",5,false,2249774,12758972,64,471,null,null,308264], + ["richards",0,"before",6,false,2252029,14473693,64,471,null,null,308264], + ["richards",0,"before",7,false,2337101,13273747,64,471,null,null,308264], + ["richards",0,"before",8,false,2250265,12819168,64,471,null,null,308264], + ["richards",0,"before",9,false,2255265,12845348,64,471,null,null,308264], + ["richards",0,"before",10,false,2259523,12761286,64,471,null,null,308264], + ["richards",0,"before",11,false,2255004,12820449,64,471,null,null,308264], + ["richards",0,"before",12,false,2250757,12814258,64,471,null,null,308264], + ["richards",0,"before",13,false,2251678,12924319,64,471,null,null,308264], + ["richards",0,"before",14,false,2256728,12788117,64,471,null,null,308264], + ["richards",0,"before",15,false,2268571,12751627,64,471,null,null,308264], + ["richards",0,"before",16,false,2239706,12779130,64,471,null,null,308264], + ["richards",0,"before",17,false,2243182,12780924,64,471,null,null,308264], + ["richards",0,"before",18,false,2229195,12746318,64,471,null,null,308264], + ["richards",0,"before",19,false,2249374,12832913,64,471,null,null,308264], + ["richards",0,"after",0,true,2668947,12527147,64,518,364,6912,308264], + ["richards",0,"after",1,true,2319559,9826791,64,518,364,6912,308264], + ["richards",0,"after",2,false,2279071,11256237,64,518,364,6912,308264], + ["richards",0,"after",3,false,2301463,9680202,64,518,364,6912,308264], + ["richards",0,"after",4,false,2286855,9651446,64,518,364,6912,308264], + ["richards",0,"after",5,false,2289722,9684759,64,518,364,6912,308264], + ["richards",0,"after",6,false,2286836,9897726,64,518,364,6912,308264], + ["richards",0,"after",7,false,2351389,9684951,64,518,364,6912,308264], + ["richards",0,"after",8,false,2302475,9688848,64,518,364,6912,308264], + ["richards",0,"after",9,false,2283890,10168214,64,518,364,6912,308264], + ["richards",0,"after",10,false,2314889,9743461,64,518,364,6912,308264], + ["richards",0,"after",11,false,2305321,9710650,64,518,364,6912,308264], + ["richards",0,"after",12,false,2306433,9733824,64,518,364,6912,308264], + ["richards",0,"after",13,false,2288048,10100886,64,518,364,6912,308264], + ["richards",0,"after",14,false,2298448,9786854,64,518,364,6912,308264], + ["richards",0,"after",15,false,2302245,9980115,64,518,364,6912,308264], + ["richards",0,"after",16,false,2305281,9919018,64,518,364,6912,308264], + ["richards",0,"after",17,false,2301984,9699209,64,518,364,6912,308264], + ["richards",0,"after",18,false,2293468,9698075,64,518,364,6912,308264], + ["richards",0,"after",19,false,2347392,9857190,64,518,364,6912,308264], + ["richards",1,"after",0,true,2574055,13076820,64,518,364,6912,308264], + ["richards",1,"after",1,true,2356008,14512446,64,518,364,6912,308264], + ["richards",1,"after",2,false,2414640,12957461,64,518,364,6912,308264], + ["richards",1,"after",3,false,2358141,12615436,64,518,364,6912,308264], + ["richards",1,"after",4,false,2356870,12803056,64,518,364,6912,308264], + ["richards",1,"after",5,false,2351228,12587274,64,518,364,6912,308264], + ["richards",1,"after",6,false,2344356,12568396,64,518,364,6912,308264], + ["richards",1,"after",7,false,2360305,12649563,64,518,364,6912,308264], + ["richards",1,"after",8,false,2344154,12577875,64,518,364,6912,308264], + ["richards",1,"after",9,false,2358823,12579659,64,518,364,6912,308264], + ["richards",1,"after",10,false,2363391,12616088,64,518,364,6912,308264], + ["richards",1,"after",11,false,2357280,12589398,64,518,364,6912,308264], + ["richards",1,"after",12,false,2347882,12625636,64,518,364,6912,308264], + ["richards",1,"after",13,false,2345137,12639353,64,518,364,6912,308264], + ["richards",1,"after",14,false,2362921,12597391,64,518,364,6912,308264], + ["richards",1,"after",15,false,2356168,12584719,64,518,364,6912,308264], + ["richards",1,"after",16,false,2357991,12579217,64,518,364,6912,308264], + ["richards",1,"after",17,false,2343614,12568358,64,518,364,6912,308264], + ["richards",1,"after",18,false,2327803,12611239,64,518,364,6912,308264], + ["richards",1,"after",19,false,2356279,12608783,64,518,364,6912,308264], + ["richards",1,"before",0,true,2553526,13227107,64,471,null,null,308264], + ["richards",1,"before",1,true,2314739,12899771,64,471,null,null,308264], + ["richards",1,"before",2,false,2237711,12813565,64,471,null,null,308264], + ["richards",1,"before",3,false,2244755,12705971,64,471,null,null,308264], + ["richards",1,"before",4,false,2242410,12726288,64,471,null,null,308264], + ["richards",1,"before",5,false,2243101,12819398,64,471,null,null,308264], + ["richards",1,"before",6,false,2230358,12647898,64,471,null,null,308264], + ["richards",1,"before",7,false,2238363,12690421,64,471,null,null,308264], + ["richards",1,"before",8,false,2243122,12710628,64,471,null,null,308264], + ["richards",1,"before",9,false,2243112,12614436,64,471,null,null,308264], + ["richards",1,"before",10,false,2239415,12612199,64,471,null,null,308264], + ["richards",1,"before",11,false,2239466,12700319,64,471,null,null,308264], + ["richards",1,"before",12,false,2262699,12642368,64,471,null,null,308264], + ["richards",1,"before",13,false,2239135,14403397,64,471,null,null,308264], + ["richards",1,"before",14,false,2267027,12760054,64,471,null,null,308264], + ["richards",1,"before",15,false,2246589,12690359,64,471,null,null,308264], + ["richards",1,"before",16,false,2237100,12629735,64,471,null,null,308264], + ["richards",1,"before",17,false,2242120,12620786,64,471,null,null,308264], + ["richards",1,"before",18,false,2252289,12692926,64,471,null,null,308264], + ["richards",1,"before",19,false,2243863,12686692,64,471,null,null,308264], + ["richards",2,"before",0,true,2518720,13051131,64,471,null,null,308264], + ["richards",2,"before",1,true,2324448,12954585,64,471,null,null,308264], + ["richards",2,"before",2,false,2217764,12552667,64,471,null,null,308264], + ["richards",2,"before",3,false,2242831,12885675,64,471,null,null,308264], + ["richards",2,"before",4,false,2305320,12621849,64,471,null,null,308264], + ["richards",2,"before",5,false,2248912,12625918,64,471,null,null,308264], + ["richards",2,"before",6,false,2249233,12528480,64,471,null,null,308264], + ["richards",2,"before",7,false,2247259,12566234,64,471,null,null,308264], + ["richards",2,"before",8,false,2266296,12534541,64,471,null,null,308264], + ["richards",2,"before",9,false,2240567,12614305,64,471,null,null,308264], + ["richards",2,"before",10,false,2248552,12634723,64,471,null,null,308264], + ["richards",2,"before",11,false,2244795,12628412,64,471,null,null,308264], + ["richards",2,"before",12,false,2261987,12648440,64,471,null,null,308264], + ["richards",2,"before",13,false,2234355,12650625,64,471,null,null,308264], + ["richards",2,"before",14,false,2242591,12540763,64,471,null,null,308264], + ["richards",2,"before",15,false,2253812,12566124,64,471,null,null,308264], + ["richards",2,"before",16,false,2261557,12651685,64,471,null,null,308264], + ["richards",2,"before",17,false,2244915,12582214,64,471,null,null,308264], + ["richards",2,"before",18,false,2247550,12593535,64,471,null,null,308264], + ["richards",2,"before",19,false,2261417,12648920,64,471,null,null,308264], + ["richards",2,"after",0,true,3362182,13143098,64,518,364,6912,308264], + ["richards",2,"after",1,true,2402446,12882757,64,518,364,6912,308264], + ["richards",2,"after",2,false,2287135,12795832,64,518,364,6912,308264], + ["richards",2,"after",3,false,2300501,12591210,64,518,364,6912,308264], + ["richards",2,"after",4,false,2316803,12643510,64,518,364,6912,308264], + ["richards",2,"after",5,false,2290161,14222180,64,518,364,6912,308264], + ["richards",2,"after",6,false,2312294,12648239,64,518,364,6912,308264], + ["richards",2,"after",7,false,2275273,12633332,64,518,364,6912,308264], + ["richards",2,"after",8,false,2289440,12654511,64,518,364,6912,308264], + ["richards",2,"after",9,false,2284842,12616808,64,518,364,6912,308264], + ["richards",2,"after",10,false,2298257,12652918,64,518,364,6912,308264], + ["richards",2,"after",11,false,2277778,12625605,64,518,364,6912,308264], + ["richards",2,"after",12,false,2271707,12621588,64,518,364,6912,308264], + ["richards",2,"after",13,false,2277427,12639883,64,518,364,6912,308264], + ["richards",2,"after",14,false,2283740,12620105,64,518,364,6912,308264], + ["richards",2,"after",15,false,2293578,12632781,64,518,364,6912,308264], + ["richards",2,"after",16,false,2278450,12574678,64,518,364,6912,308264], + ["richards",2,"after",17,false,2312254,13611873,64,518,364,6912,308264], + ["richards",2,"after",18,false,2345638,12053881,64,518,364,6912,308264], + ["richards",2,"after",19,false,2278479,11210979,64,518,364,6912,308264] + ] +} diff --git a/docs/reports/static-property-atoms.md b/docs/reports/static-property-atoms.md new file mode 100644 index 00000000..8f26d8a7 --- /dev/null +++ b/docs/reports/static-property-atoms.md @@ -0,0 +1,80 @@ +# Static property Atom reuse — 2026-09-09 + +静态名称现在在字节码发布时链接成 Runtime 内的 Atom,执行时按常量索引直接复用。缓存的是名称身份,不是属性值或对象中的位置,因此 getter、Proxy 和原型变化仍走正常查询路径。本次基于已完成的数组提前判断优化,关联 [issue #7](https://github.com/pocket-stack/quickjs-oxide/issues/7)。 + +Static names are now linked to runtime-local Atoms at bytecode publication and reused by constant index during execution. This caches name identity, not property values or object offsets; getters, Proxies and prototype changes retain normal lookup behavior. This change builds on the array early-check optimization and addresses [issue #7](https://github.com/pocket-stack/quickjs-oxide/issues/7). + +## Execution results / 执行结果 + +Five independent processes per version/workload, alternating order, diagnostics disabled. / 每版本每工作负载五次独立进程,交替顺序,关闭诊断。 + +| Workload / 工作量 | Before median ms / 修改前 | After median ms / 修改后 | Time reduction / 耗时下降 | +| --- | ---: | ---: | ---: | +| Empty loop ×10 million / 空循环 1,000 万次 | 1945.535 | 1890.326 | 2.84% | +| Property reads ×20 million / 属性读取 2,000 万次 | 10813.374 | 7240.065 | **33.05%** | +| Fixed Richards ×10 / 固定 Richards 十次 | 2117.465 | 1477.229 | **30.24%** | +| Original Richards harness / 原始 Richards harness | 8460.681 | 6163.680 | **27.15%** | + +原始 Richards 中位分数为 **16.7 → 24.1(+44.31%,越高越好)**。分数与完整进程耗时是不同指标,不通过其中一个反推另一个。以上均包含启动、编译、执行和收尾;原始 harness 还包含其 warmup 和计时逻辑。 + +Original Richards median score: **16.7 → 24.1 (+44.31%, higher is better)**. Harness scores and whole-process wall times are distinct metrics; neither is derived from the other. Process times include startup, compilation, execution and teardown, plus warmup/timing logic in the original harness. + +40 次计时运行全部成功且 stderr 为空;固定工作量输出严格校验,保留 Richards 内部断言;原始 harness 的 Richards 与总分两行必须匹配。属性读取五次范围为修改前 **10699.269–11261.891 ms**、修改后 **7198.157–7362.440 ms**。空循环前后范围重叠(1854.449–1989.332 / 1864.893–1934.948 ms),其小幅变化不能当作数值循环优化的证据。 + +All 40 timed runs succeeded with empty stderr, exact fixed-workload output checks and retained Richards assertions. Original harness Richards/aggregate score lines had to match. Property-read ranges were **10699.269–11261.891 ms before**, **7198.157–7362.440 ms after**. Empty-loop ranges overlap (1854.449–1989.332 / 1864.893–1934.948 ms); the small change does not establish a numeric-loop improvement. + +## Compilation and storage / 编译与存储 + +另用相同源码的外部 Rust 探针,分别链接前后引擎库。每个工作负载/版本运行三个进程,每进程创建 20 个独立 Runtime/Context;前两次标为 warmup,剩余共 54 个样本计算中位数。`Instant` 包围 `Context::compile`,包含解析、编译和发布,**不是单独的发布时间**。内存快照在保留编译结果、尚未执行脚本时采集,位于计时范围之外。 + +A separate identical Rust probe links each engine library. Three processes per workload/version create 20 fresh Runtime/Context instances each; the first two iterations are warmups, leaving 54 samples for medians. `Instant` surrounds `Context::compile`, including parsing, compilation and publication, **not publication alone**. Memory snapshots occur outside the timer, with compiled bytecode retained and the script unexecuted. + +| Workload / 工作量 | Compile + publish before ms / 修改前 | After ms / 修改后 | Linked-name entries / 链接名称条目 | Map bytes / 映射字节 | +| --- | ---: | ---: | ---: | ---: | +| Empty loop | 0.055291 | 0.056944 | 1 | 32 | +| Property reads | 0.094090 | 0.101119 | 10 | 176 | +| Fixed Richards ×10 | 12.114134 | 12.079653 | 359 | 6736 | +| Original Richards | 12.696623 | 12.585997 | 364 | 6912 | + +映射以常量索引寻址,只延伸到最大静态名称索引;字节数包含中间空槽。这里 Rust 的安全 Atom 句柄带有表域和代数,共 16 字节,并非 QuickJS 的裸 32 位编号。每个链接条目还在既有 `auxiliary_atoms` 中增加一个 16 字节引用记录:原始 Richards 额外为 **5824 字节**,映射加这部分数组载荷共 **12736 字节**。不含 Rc/allocator 头、Atom 表和字符串存储、VM 帧变化,不能当作总内存增量。 + +The constant-indexed map ends at the highest static-name index; bytes include unused intermediate slots. Rust's checked Atom includes table-domain and generation fields and occupies 16 bytes here, unlike QuickJS's raw 32-bit identifier. Each linked entry also adds a 16-byte reference record to existing `auxiliary_atoms`: **5824 extra bytes** for original Richards, totaling **12736 bytes** of map/reference-array payload. This excludes Rc/allocator headers, Atom-table/string storage and VM-frame changes; it is not total memory overhead. + +原始 Richards 的编译快照中,活跃表内 Atom 数为 **471 → 518**,字节码节点仍为 64,arena 已用内联字节仍为 308264。属性读取为 **384 → 389** 个表内 Atom。所有探针迭代释放编译结果后,都验证字节码节点为零;新版本还验证映射条目及字节为零。该检查不声称 allocator 或 Atom 表容量归还系统。 + +Original Richards compilation snapshots contain **471 → 518** live table-backed Atoms, with 64 bytecode nodes and 308264 used arena inline bytes in both versions. Property reads contain **384 → 389** table-backed Atoms. Every probe iteration verifies zero bytecode nodes after dropping the compiled result; the new version also verifies zero map entries/bytes. This does not claim allocator or Atom-table capacity is returned to the OS. + +编译时间的小幅起伏不能解释为编译器优化;属性读取的编译加发布中位数增加约 7 µs。全部 480 个探针迭代均保留(432 个非 warmup),包括 Context 时间和每次快照数值。 + +Small compilation-time fluctuations are not evidence of a compiler optimization; the property-read compile/publication median increases by about 7 µs. All 480 probe iterations are retained (432 non-warmup), including Context times and every snapshot value. + +## Implementation and validation / 实现与验证 + +- `Instruction::constant_property_key_index` identifies static names, including eval/dynamic-binding/reference operations and unreachable instructions. Publication interns each referenced constant index once. / 统一识别静态名称,包括 eval、动态绑定、引用操作及不可达指令;发布时每个被引用索引只 intern 一次。 +- `FunctionBytecodeData` owns an optional shared map through its metadata; Atom references use the existing `auxiliary_atoms` transaction and GC cleanup. Functions without static names allocate no map. / 字节码保存可选共享映射,Atom 引用接入现有事务、回滚和 GC 清理,无静态名称的函数不分配映射。 +- Normal calls and resumed activations carry the same map. Production `constant_property_key` performs a lookup and existing `PropertyKey` reference promotion, with no string-intern fallback. Only synthetic, unpublished unit-test hosts retain a `cfg(test)` fallback. / 普通调用和恢复帧复用映射;生产路径直接查表并提升引用,无字符串 intern 回退;只有未发布的合成单元测试 host 保留测试专用回退。 +- Original String constants and serialized representation remain unchanged; binary reading reaches the same runtime publication boundary. The heap checks mapping coverage, string kinds, bounds and owned-reference multiplicity. / 保留字符串常量和序列化形式,二进制读取经过同一发布边界;堆检查覆盖、字符串类型、边界与引用所有权数量。 +- **2998 Rust tests passed, 0 failed, one existing ignored**, across 13 binaries with Rust 1.88 and profiling enabled. Tests cover release/rollback, runtime isolation, numeric spellings/lone surrogates, getters/Proxies/prototype changes, resumed generators and malformed mappings. / 13 个测试二进制共 2998 项通过,0 失败,1 项原有忽略,覆盖释放、回滚、Runtime 隔离、特殊字符串、动态查询行为和损坏映射。 +- Formatting, source layout, Rust-only checks and strict production Clippy passed. Clippy covered profiling, default workspace and `test262-host` library/binary configurations. / 格式、源码布局、Rust-only 和三种配置的生产目标 strict Clippy 通过。 +- Binary-object production boundary passed; **all 690 isolation canaries were rejected** on the remote host. Its final log hash is recorded in the JSON and retained separately from the earlier measurement archive. / 远端字节码生产边界检查通过,690 个隔离 canary 全部被拒绝;最终日志哈希见 JSON,日志单独保留于测量归档之外。 +- Full Test262 ran separately on both revisions: **all 102037 outcome rows identical, zero changed rows/new failures**. Both retained 79982 passes, 7 parse failures, 43 runtime failures, 3530 unsupported and 18475 skipped. The frozen full-file receipt gate still exits 1 because the source fingerprint changes the header; no baseline was updated. / 前后分别完整运行 Test262,102037 行结果完全一致,零变化和新增失败;既有分类保持不变。冻结完整文件校验仍因源码指纹改变报告头而失败,没有更新基线。 + +## Reproduction and evidence / 复现与证据 + +Before / 修改前: `4e2e69bca89a2f287ea5dc99c9557f993eeb1176` (`perf/array-own-key-early-check`). After / 修改后: `a9ddf42ad32cb515335b5db9628897b17bbab0c8`. Later report commits do not change measured runtime code. / 后续报告提交不改变所测运行时代码。 + +Heavy builds/tests/measurements ran serially through Herdr on `eric-83am` / PocketLab: AMD Ryzen 7 7840HS, Linux 6.18.44-1-lts. Both release binaries used Rust 1.94.1 and the same command below; performance diagnostics were disabled. Workloads were reused unchanged from `ahaoboy/js-engine-benchmark@2034d98fc8c5f8044e186267593f5d5ea5232caf`. / 构建、测试和测量均在远端执行;两版本采用同样的构建参数与未改动工作负载。 + +```sh +CARGO_PROFILE_RELEASE_DEBUG=1 CARGO_PROFILE_RELEASE_STRIP=none \ +CARGO_TARGET_DIR=target/cpu-profile \ +QUICKJS_OXIDE_BUILD_COMMIT=$(git rev-parse HEAD) \ +cargo build --locked --release -p quickjs-oxide-cli \ + --no-default-features --features profiling +``` + +Whole-process timing uses Python `time.perf_counter_ns()` with a 90-second timeout. No builds run concurrently with measurements. CPU frequency/system isolation are not controlled; these results do not establish gains for every workload, and CPU stacks were not re-sampled in this round. / 进程计时采用单调时钟、90 秒超时,期间不并行构建;未锁定频率或完全隔离系统,未在本轮重采 CPU 栈,不将结果推广到所有工作负载。 + +[Machine-readable evidence / 机器可读证据](static-property-atoms.json) includes every timing/probe sample, outputs, exact commands, workload/binary hashes and Test262 row comparison. Outcome-row SHA-256: `fa99d3349bb4b61f30ba57d7c7f275df64f19691edefc50b96b53887339fa8c3`. + +Raw archive / 原始包: `quickjs-oxide-static-atoms-pocketlab-2026-09-09.tar.gz`, **42235911 bytes**, SHA-256 **`e3261ee85fab36999888fbf5c3cb3207713896e42f20d1a2aa3f334dd1aace5c`**. All 164 manifest members were verified remotely; the downloaded archive hash also matches. It contains matching before/after executables, probe/runner source, build/test logs, complete Test262 vectors and raw measurements. It is retained in local and remote workspace `target/`, not attached to GitHub; external benchmark source is not included. / 164 个清单成员逐一验证,本地下载哈希一致;原始包保留在本地与远端,不作为 GitHub 附件,不包含外部 benchmark 源码。 diff --git a/src/engine/code/bytecode.rs b/src/engine/code/bytecode.rs index a45e44e9..95141123 100644 --- a/src/engine/code/bytecode.rs +++ b/src/engine/code/bytecode.rs @@ -716,6 +716,36 @@ pub enum Instruction { } impl Instruction { + /// String constant used as a static name by the VM's property-key bridge. + /// Publication links these operands once, including unreachable code. + #[must_use] + pub(crate) const fn constant_property_key_index(&self) -> Option { + match self { + Self::SetName(index) + | Self::ThrowReadOnly(index) + | Self::ThrowRedeclaration(index) + | Self::GetField(index) + | Self::GetField2(index) + | Self::PutField(index) + | Self::DefineField(index) + | Self::DefineMethod { key: index, .. } + | Self::DefineClass { name: index, .. } + | Self::HasEvalVariable { name: index, .. } + | Self::GetEvalVariable { name: index, .. } + | Self::PutEvalVariable { name: index, .. } + | Self::DeleteEvalVariable { name: index, .. } + | Self::DefineEvalVariable { name: index, .. } + | Self::HasDynamicBinding { name: index, .. } + | Self::GetDynamicBinding { name: index, .. } + | Self::PutDynamicBinding { name: index, .. } + | Self::DeleteDynamicBinding { name: index, .. } + | Self::GetRefValue(index) + | Self::GetRefValueUndef(index) + | Self::PutRefValue(index) => Some(*index), + _ => None, + } + } + #[must_use] pub const fn stack_effect(&self) -> (usize, usize) { match self { @@ -995,28 +1025,8 @@ impl DetachedBytecode { "detached bytecode cannot encode authenticated private names", )); } - if let Instruction::SetName(index) - | Instruction::ThrowReadOnly(index) - | Instruction::ThrowRedeclaration(index) - | Instruction::GetField(index) - | Instruction::GetField2(index) - | Instruction::PutField(index) - | Instruction::DefineField(index) - | Instruction::DefineMethod { key: index, .. } - | Instruction::DefineClass { name: index, .. } - | Instruction::HasEvalVariable { name: index, .. } - | Instruction::GetEvalVariable { name: index, .. } - | Instruction::PutEvalVariable { name: index, .. } - | Instruction::DeleteEvalVariable { name: index, .. } - | Instruction::DefineEvalVariable { name: index, .. } - | Instruction::HasDynamicBinding { name: index, .. } - | Instruction::GetDynamicBinding { name: index, .. } - | Instruction::PutDynamicBinding { name: index, .. } - | Instruction::DeleteDynamicBinding { name: index, .. } - | Instruction::GetRefValue(index) - | Instruction::GetRefValueUndef(index) - | Instruction::PutRefValue(index) = instruction - && !self.constant(*index).is_some_and(TestConstant::is_string) + if let Some(index) = instruction.constant_property_key_index() + && !self.constant(index).is_some_and(TestConstant::is_string) { return Err(Error::internal( "string-key opcode referenced a non-string constant", diff --git a/src/engine/code/bytecode_publish.rs b/src/engine/code/bytecode_publish.rs index ce1dc1b6..8c1979ee 100644 --- a/src/engine/code/bytecode_publish.rs +++ b/src/engine/code/bytecode_publish.rs @@ -36,6 +36,39 @@ use crate::engine::value::{JsString, Value}; pub(crate) use private_elements::prepare_private_binding_publication; use std::collections::{HashMap, HashSet}; +/// Link each distinct static-name constant index once. The caller owns the +/// atom transaction: any later failure releases `auxiliary_atoms`, while +/// successful publication transfers those references to the bytecode node. +pub(crate) fn link_constant_property_keys( + state: &mut RuntimeState, + code: &[Instruction], + constants: &[BytecodeConstant], + auxiliary_atoms: &mut Vec, +) -> Result, RuntimeError> { + let mut keys = Vec::new(); + for instruction in code { + let Some(index) = instruction.constant_property_key_index() else { + continue; + }; + let index = usize::try_from(index) + .map_err(|_| RuntimeError::Invariant("static name index did not fit usize"))?; + let Some(BytecodeConstant::Value(RawValue::String(name))) = constants.get(index) else { + return Err(RuntimeError::Invariant( + "static name did not reference a string constant", + )); + }; + if keys.len() <= index { + keys.resize(index + 1, Atom::NULL); + } + if keys[index].is_null() { + let atom = state.atoms.intern_property_key_js_string(name)?; + auxiliary_atoms.push(atom); + keys[index] = atom; + } + } + Ok(keys) +} + /// Intern every semantically retained direct-eval binding name while keeping /// the parent publication routine's atom transaction authoritative. The /// caller releases `auxiliary_atoms` on any later failure and transfers the diff --git a/src/engine/code/runtime.rs b/src/engine/code/runtime.rs index 9a7f43d0..d4d18d7c 100644 --- a/src/engine/code/runtime.rs +++ b/src/engine/code/runtime.rs @@ -117,6 +117,7 @@ impl Runtime { let mut unlinked_debug = function.debug; let mut linked_debug = None; let mut auxiliary_atoms = Vec::new(); + let mut property_key_atoms = Vec::new(); let id = { let mut state = self.0.state.borrow_mut(); let linking = (|| -> Result<(), RuntimeError> { @@ -143,6 +144,12 @@ impl Runtime { linked_constants[index] = BytecodeConstant::Value(RawValue::String(canonical)); } + property_key_atoms = bytecode_publish::link_constant_property_keys( + &mut state, + &function.code, + &linked_constants, + &mut auxiliary_atoms, + )?; if let Some(debug) = unlinked_debug.take() { let filename = state.atoms.intern_property_key_js_string(&debug.filename)?; @@ -221,6 +228,8 @@ impl Runtime { let bytecode = FunctionBytecodeData { code: function.code.into(), constants: linked_constants.into(), + property_key_atoms: (!property_key_atoms.is_empty()) + .then(|| property_key_atoms.into()), realm, metadata: function.metadata, parameter_environment: function.parameter_environment, @@ -361,6 +370,7 @@ impl Runtime { root, code: bytecode.code.clone(), constants: bytecode.constants.clone(), + property_key_atoms: bytecode.property_key_atoms.clone(), argument_definitions: bytecode.argument_definitions.clone(), local_definitions: bytecode.local_definitions.clone(), closure_variables: bytecode.closure_variables.clone(), @@ -521,6 +531,7 @@ pub(crate) struct PublishedFunctionSnapshot { pub(crate) root: FunctionBytecodeRef, pub(crate) code: Rc<[crate::engine::code::bytecode::Instruction]>, pub(crate) constants: Rc<[BytecodeConstant]>, + pub(crate) property_key_atoms: Option>, pub(crate) argument_definitions: Rc<[VariableDefinition]>, pub(crate) local_definitions: Rc<[VariableDefinition]>, pub(crate) closure_variables: Rc<[ClosureVariable]>, diff --git a/src/engine/heap/allocation.rs b/src/engine/heap/allocation.rs index bfa8066c..41153111 100644 --- a/src/engine/heap/allocation.rs +++ b/src/engine/heap/allocation.rs @@ -651,6 +651,51 @@ impl Heap { for atom in bytecode.auxiliary_atoms.iter().copied() { *owned_name_atoms.entry(atom).or_default() += 1; } + for instruction in bytecode.code.iter() { + let Some(index) = instruction.constant_property_key_index() else { + continue; + }; + let atom = usize::try_from(index).ok().and_then(|index| { + bytecode + .property_key_atoms + .as_ref() + .and_then(|keys| keys.get(index)) + }); + if atom.is_none_or(|atom| atom.is_null()) { + return Err(HeapError::Invariant( + "static name opcode has no linked property key", + )); + } + } + if let Some(keys) = &bytecode.property_key_atoms { + if keys.len() > bytecode.constants.len() { + return Err(HeapError::Invariant( + "static name table exceeds the constant pool", + )); + } + for (index, atom) in keys.iter().copied().enumerate() { + if atom.is_null() { + continue; + } + if !matches!( + bytecode.constants[index], + BytecodeConstant::Value(RawValue::String(_)) + ) { + return Err(HeapError::Invariant( + "linked property key does not reference a string constant", + )); + } + let count = owned_name_atoms.get_mut(&atom).ok_or(HeapError::Invariant( + "static name atom is not owned by bytecode metadata", + ))?; + if *count == 0 { + return Err(HeapError::Invariant( + "static name atom ownership multiplicity is too small", + )); + } + *count -= 1; + } + } if let Some(debug) = &bytecode.debug { if debug.filename.is_null() { return Err(HeapError::Invariant( diff --git a/src/engine/heap/code_records.rs b/src/engine/heap/code_records.rs index 89965ac2..17dd83d7 100644 --- a/src/engine/heap/code_records.rs +++ b/src/engine/heap/code_records.rs @@ -114,6 +114,10 @@ impl Default for PublishedPrivateBindings { pub struct FunctionBytecodeData { pub code: Rc<[Instruction]>, pub constants: Rc<[BytecodeConstant]>, + /// Constant-indexed static names linked by the runtime publisher. Null + /// entries are unused; functions without static names have no table. + /// These identities borrow the references owned by `auxiliary_atoms`. + pub property_key_atoms: Option>, pub realm: ContextId, pub metadata: FunctionMetadata, pub parameter_environment: Option, diff --git a/src/engine/heap/profiling.rs b/src/engine/heap/profiling.rs index 6ffdd9d3..065b6b50 100644 --- a/src/engine/heap/profiling.rs +++ b/src/engine/heap/profiling.rs @@ -119,6 +119,10 @@ impl Heap { let mut code = storage("bytecode_instructions", 0, 0, 1); code.basis = "deduplicated-Rc-slice-inline-bytes; excludes Rc headers and nested operands"; let mut seen_code = HashSet::new(); + let mut property_keys = storage("bytecode_property_keys", 0, 0, 1); + property_keys.count = Some(0); + property_keys.basis = "linked-name-count; deduplicated map slice bytes including unused slots; excludes Rc headers and auxiliary atom references"; + let mut seen_property_keys = HashSet::new(); for slot in &self.slots { let node = match &slot.state { SlotState::Live(node) @@ -166,11 +170,31 @@ impl Heap { size_of::(), ); } + if let Some(keys) = &data.property_key_atoms + && seen_property_keys.insert(Rc::as_ptr(keys)) + { + add_storage( + &mut property_keys, + keys.len(), + keys.len(), + size_of::(), + ); + *property_keys.count.as_mut().unwrap() -= + keys.iter().filter(|atom| atom.is_null()).count(); + } } _ => {} } } - result.extend([properties, arrays, elements, buffers, shared_buffers, code]); + result.extend([ + properties, + arrays, + elements, + buffers, + shared_buffers, + code, + property_keys, + ]); result } } diff --git a/src/engine/heap/runtime/mod.rs b/src/engine/heap/runtime/mod.rs index 2efb96e6..893636f1 100644 --- a/src/engine/heap/runtime/mod.rs +++ b/src/engine/heap/runtime/mod.rs @@ -504,6 +504,9 @@ impl Drop for RuntimeInner { #[cfg(test)] mod tests; +#[cfg(test)] +mod static_property_key_tests; + use crate::engine::vm::frames::*; use crate::engine::object::operations::*; diff --git a/src/engine/heap/runtime/static_property_key_tests.rs b/src/engine/heap/runtime/static_property_key_tests.rs new file mode 100644 index 00000000..51055637 --- /dev/null +++ b/src/engine/heap/runtime/static_property_key_tests.rs @@ -0,0 +1,225 @@ +use super::Runtime; +use crate::engine::code::bytecode::Instruction; +use crate::engine::code::function::metadata::FunctionMetadata; +use crate::engine::code::function::{UnlinkedConstant, UnlinkedFunction}; +use crate::engine::value::{JsString, Value}; + +fn repeated_static_name_draft() -> UnlinkedFunction { + UnlinkedFunction::fixture( + vec![ + Instruction::Object, + Instruction::GetField(0), + Instruction::Drop, + Instruction::Object, + Instruction::GetField(0), + Instruction::Drop, + Instruction::PushConst(1), + Instruction::Return, + ], + vec![ + UnlinkedConstant::primitive(Value::String(JsString::from_static( + "static_key_lifetime_probe", + ))) + .unwrap(), + UnlinkedConstant::primitive(Value::String(JsString::from_static( + "ordinary_constant_stays_a_string", + ))) + .unwrap(), + ], + FunctionMetadata { + max_stack: 1, + strict: true, + ..FunctionMetadata::default() + }, + ) +} + +#[test] +fn static_property_keys_reuse_one_owned_atom_and_release_with_bytecode() { + let runtime = Runtime::new(); + let mut context = runtime.new_context(); + let baseline_atoms = runtime.test_atom_count(); + let bytecode = runtime + .publish_unlinked_function(context.realm, repeated_static_name_draft()) + .unwrap(); + let atom = { + let state = runtime.0.state.borrow(); + let data = state + .heap + .function_bytecode(bytecode.bytecode_id()) + .unwrap(); + let keys = data.property_key_atoms.as_ref().unwrap(); + assert_eq!( + keys.len(), + 1, + "ordinary trailing constants need no map slot" + ); + assert_eq!(data.auxiliary_atoms.as_ref(), keys.as_ref()); + assert_eq!(state.atoms.resolve(keys[0]).unwrap().ref_count, Some(1)); + keys[0] + }; + assert_eq!(runtime.test_atom_count(), baseline_atoms + 1); + #[cfg(feature = "profiling")] + { + let snapshot = runtime.memory_snapshot(); + let keys = snapshot + .categories + .iter() + .find(|entry| entry.name == "bytecode_property_keys") + .unwrap(); + assert_eq!(keys.count, Some(1)); + assert_eq!( + keys.used_bytes, + Some(size_of::()) + ); + assert_eq!(snapshot, runtime.memory_snapshot()); + } + for _ in 0..3 { + assert_eq!( + context.execute(&bytecode).unwrap(), + Value::String(JsString::from_static("ordinary_constant_stays_a_string")) + ); + assert_eq!( + runtime + .0 + .state + .borrow() + .atoms + .resolve(atom) + .unwrap() + .ref_count, + Some(1) + ); + } + drop(bytecode); + runtime.run_gc().unwrap(); + assert!(runtime.0.state.borrow().atoms.resolve(atom).is_err()); + assert_eq!(runtime.test_atom_count(), baseline_atoms); +} + +#[test] +fn static_property_keys_are_runtime_local_and_failed_publication_rolls_back() { + let first = Runtime::new(); + let mut first_context = first.new_context(); + let second = Runtime::new(); + let second_context = second.new_context(); + let expired = first.new_context(); + let expired_realm = expired.realm; + drop(expired); + first.run_gc().unwrap(); + let baseline = first.test_atom_count(); + assert!( + first + .publish_unlinked_function(expired_realm, repeated_static_name_draft()) + .is_err() + ); + assert_eq!(first.test_atom_count(), baseline); + + let a = first + .publish_unlinked_function(first_context.realm, repeated_static_name_draft()) + .unwrap(); + let b = second + .publish_unlinked_function(second_context.realm, repeated_static_name_draft()) + .unwrap(); + let a_atom = first + .snapshot_function_bytecode(&a) + .unwrap() + .property_key_atoms + .unwrap()[0]; + let b_atom = second + .snapshot_function_bytecode(&b) + .unwrap() + .property_key_atoms + .unwrap()[0]; + assert_ne!(a_atom, b_atom); + assert!(first_context.execute(&b).is_err()); + let mut sibling = first.new_context(); + assert_eq!( + sibling.execute(&a).unwrap(), + Value::String(JsString::from_static("ordinary_constant_stays_a_string")) + ); +} + +#[test] +fn static_property_keys_preserve_numeric_spellings_and_exact_utf16() { + let runtime = Runtime::new(); + let mut context = runtime.new_context(); + let mut names = ["0", "01", "2147483647", "2147483648", "属性"] + .map(JsString::from_static) + .to_vec(); + names.push(JsString::try_from_utf16(vec![0xd800]).unwrap()); + for name in names { + let draft = UnlinkedFunction::fixture( + vec![ + Instruction::Object, + Instruction::PushI32(42), + Instruction::DefineField(0), + Instruction::GetField(0), + Instruction::Return, + ], + vec![UnlinkedConstant::primitive(Value::String(name.clone())).unwrap()], + FunctionMetadata { + max_stack: 2, + strict: true, + ..FunctionMetadata::default() + }, + ); + let bytecode = runtime + .publish_unlinked_function(context.realm, draft) + .unwrap(); + let atom = runtime + .snapshot_function_bytecode(&bytecode) + .unwrap() + .property_key_atoms + .unwrap()[0]; + assert_eq!( + runtime.0.state.borrow().atoms.to_js_string(atom).unwrap(), + name + ); + assert_eq!(context.execute(&bytecode).unwrap(), Value::Int(42)); + } + let plain = context.compile("42").unwrap(); + assert!( + runtime + .snapshot_function_bytecode(&plain) + .unwrap() + .property_key_atoms + .is_none() + ); +} + +#[test] +fn static_property_keys_keep_getters_proxies_and_resumed_frames_observable() { + let runtime = Runtime::new(); + let mut context = runtime.new_context(); + assert_eq!( + context + .eval( + r#" + (() => { + let gets = 0; + const parent = { get observed() { gets++; return this.value; } }; + const target = Object.create(parent); + target.value = 2; + let traps = 0; + const proxy = new Proxy(target, { + get(t, k, r) { traps++; return Reflect.get(t, k, r); } + }); + function* values() { + yield proxy.observed; + yield proxy.observed; + } + const iterator = values(); + const first = iterator.next().value; + target.value = 9; + const second = iterator.next().value; + Object.setPrototypeOf(target, { observed: 20 }); + const third = proxy.observed; + return first === 2 && second === 9 && third === 20 && gets === 2 && traps === 5; + })() + "# + ) + .unwrap(), + Value::Bool(true) + ); +} diff --git a/src/engine/heap/tests.rs b/src/engine/heap/tests.rs index 89ef454e..5204dc8f 100644 --- a/src/engine/heap/tests.rs +++ b/src/engine/heap/tests.rs @@ -134,6 +134,7 @@ fn bytecode( FunctionBytecodeData { code: code.clone(), constants: constants.into(), + property_key_atoms: None, realm, metadata: FunctionMetadata { max_stack: 1, diff --git a/src/engine/heap/tests/bytecode.rs b/src/engine/heap/tests/bytecode.rs index 68fb7935..cc621fd6 100644 --- a/src/engine/heap/tests/bytecode.rs +++ b/src/engine/heap/tests/bytecode.rs @@ -646,6 +646,10 @@ fn bytecode_allocation_accepts_typed_class_heritage() { ))], Vec::new(), ); + let mut atoms = crate::engine::atom::AtomTable::new(); + let name = atoms.intern("Derived").unwrap(); + candidate.property_key_atoms = Some(Rc::from([name])); + candidate.auxiliary_atoms = Box::new([name]); candidate.metadata.max_stack = 2; let function = heap.allocate_function_bytecode(candidate).unwrap(); heap.release_function_bytecode(function).unwrap(); @@ -757,3 +761,63 @@ fn bytecode_allocation_rejects_pattern_access_to_body_lexical() { heap.release_shape(shape).unwrap(); assert_eq!(heap.counts().live, 0); } + +#[test] +fn bytecode_static_names_require_complete_owned_string_mappings() { + let mut heap = Heap::new(); + let shape = empty_shape(&mut heap); + let prototype = leaf(&mut heap, shape); + let realm = heap + .allocate_context(ContextData::new( + prototype, prototype, prototype, prototype, prototype, prototype, prototype, prototype, + )) + .unwrap(); + let code: Rc<[Instruction]> = Rc::from([ + Instruction::Object, + Instruction::GetField(0), + Instruction::Return, + ]); + let name = Atom::from_raw(29); + let candidate = |keys: Option>, owned: Vec, value: RawValue| { + let mut data = bytecode(&code, realm, vec![BytecodeConstant::Value(value)], owned); + data.metadata.max_stack = 1; + data.property_key_atoms = keys; + data + }; + let spelling = || RawValue::String(JsString::from_static("mapped")); + for keys in [None, Some(Rc::from([Atom::NULL]))] { + assert_eq!( + heap.allocate_function_bytecode(candidate(keys, vec![], spelling())), + Err(HeapError::Invariant( + "static name opcode has no linked property key" + )) + ); + } + assert_eq!( + heap.allocate_function_bytecode(candidate(Some(Rc::from([name])), vec![], spelling())), + Err(HeapError::Invariant( + "static name atom is not owned by bytecode metadata" + )) + ); + assert_eq!( + heap.allocate_function_bytecode(candidate( + Some(Rc::from([name])), + vec![name], + RawValue::Int(1) + )), + Err(HeapError::Invariant( + "linked property key does not reference a string constant" + )) + ); + let id = heap + .allocate_function_bytecode(candidate(Some(Rc::from([name])), vec![name], spelling())) + .unwrap(); + assert_eq!( + heap.release_function_bytecode(id).unwrap().atoms, + vec![name] + ); + heap.release_context(realm).unwrap(); + heap.release_object(prototype).unwrap(); + heap.release_shape(shape).unwrap(); + assert_eq!(heap.counts().live, 0); +} diff --git a/src/engine/vm/host_bridge.rs b/src/engine/vm/host_bridge.rs index 0f73bd5f..811b32a6 100644 --- a/src/engine/vm/host_bridge.rs +++ b/src/engine/vm/host_bridge.rs @@ -429,6 +429,7 @@ pub(crate) struct RuntimeVmHost { /// width. `arguments.length` and its dense prefix use this exact count. actual_argument_count: usize, constants: Rc<[BytecodeConstant]>, + property_key_atoms: Option>, argument_definitions: Rc<[VariableDefinition]>, local_definitions: Rc<[VariableDefinition]>, closure_variables: Rc<[ClosureVariable]>, @@ -646,6 +647,7 @@ impl RuntimeVmHost { current_function: None, actual_argument_count: 0, constants: Rc::from([]), + property_key_atoms: None, argument_definitions: Rc::from([]), local_definitions: Rc::from([]), closure_variables: Rc::from([]), @@ -672,6 +674,7 @@ impl RuntimeVmHost { root, code: _, constants, + property_key_atoms, argument_definitions, local_definitions, closure_variables, @@ -704,6 +707,7 @@ impl RuntimeVmHost { current_function: None, actual_argument_count: arguments.len(), constants, + property_key_atoms, argument_definitions, local_definitions, closure_variables, @@ -831,6 +835,7 @@ impl RuntimeVmHost { root, code, constants, + property_key_atoms, argument_definitions, local_definitions, closure_variables, @@ -930,6 +935,7 @@ impl RuntimeVmHost { current_function: Some(current_function), actual_argument_count: data.actual_argument_count, constants, + property_key_atoms, argument_definitions, local_definitions, closure_variables, @@ -1545,27 +1551,40 @@ impl RuntimeVmHost { } fn constant_property_key(&self, index: u32) -> Result { - let name = match usize::try_from(index) + // Synthetic host-only unit tests have no published bytecode owner. + // Production and published-code tests always require the linked table. + #[cfg(test)] + if self.current_bytecode.is_none() { + let name = match usize::try_from(index) + .ok() + .and_then(|index| self.constants.get(index)) + { + Some(BytecodeConstant::Value(RawValue::String(name))) => name.clone(), + Some( + BytecodeConstant::Value(_) + | BytecodeConstant::Function(_) + | BytecodeConstant::RegExp { .. }, + ) => { + return Err(Error::internal( + "field opcode referenced a non-string constant", + )); + } + None => return Err(Error::internal("constant index is out of bounds")), + }; + let key = self + .runtime + .intern_property_key_js_string(&name) + .map_err(|error| Error::internal(error.to_string()))?; + return Ok(key); + } + let atom = usize::try_from(index) .ok() - .and_then(|index| self.constants.get(index)) - { - Some(BytecodeConstant::Value(RawValue::String(name))) => name.clone(), - Some( - BytecodeConstant::Value(_) - | BytecodeConstant::Function(_) - | BytecodeConstant::RegExp { .. }, - ) => { - return Err(Error::internal( - "field opcode referenced a non-string constant", - )); - } - None => return Err(Error::internal("constant index is out of bounds")), - }; - let key = self - .runtime - .intern_property_key_js_string(&name) - .map_err(|error| Error::internal(error.to_string()))?; - Ok(key) + .and_then(|index| self.property_key_atoms.as_ref()?.get(index)) + .copied() + .filter(|atom| !atom.is_null()) + .ok_or_else(|| Error::internal("static name opcode has no linked property key"))?; + PropertyKey::from_borrowed_atom(self.runtime.clone(), atom) + .map_err(|error| Error::internal(error.to_string())) } fn eval_variable_object(&self, source: EvalVariableSource) -> Result { @@ -2133,6 +2152,7 @@ impl Runtime { root, code, constants, + property_key_atoms, argument_definitions, local_definitions, closure_variables, @@ -2182,6 +2202,7 @@ impl Runtime { current_function: Some(callable.as_object().clone()), actual_argument_count: arguments.len(), constants, + property_key_atoms, argument_definitions, local_definitions, closure_variables,