diff --git a/CLAUDE.md b/CLAUDE.md index c33bd8f90f..8584f95506 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.1280 +**Current Version:** 0.5.1281 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index e01b845d3d..edafec72ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5547,7 +5547,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "base64", @@ -5607,14 +5607,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "serde", ] [[package]] name = "perry-audio-miniaudio" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "cc", "libc", @@ -5622,7 +5622,7 @@ dependencies = [ [[package]] name = "perry-codegen" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "inkwell", @@ -5638,7 +5638,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "perry-hir", @@ -5646,7 +5646,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "perry-hir", @@ -5654,7 +5654,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "perry-dispatch", @@ -5663,7 +5663,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "perry-hir", @@ -5671,7 +5671,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "base64", @@ -5683,7 +5683,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "perry-hir", @@ -5691,7 +5691,7 @@ dependencies = [ [[package]] name = "perry-container-compose" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "async-trait", @@ -5720,14 +5720,14 @@ dependencies = [ [[package]] name = "perry-container-e2e" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", ] [[package]] name = "perry-diagnostics" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "serde", "serde_json", @@ -5735,7 +5735,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.1280" +version = "0.5.1281" [[package]] name = "perry-doc-fixture-my-bindings" @@ -5746,7 +5746,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "clap", @@ -5761,7 +5761,7 @@ dependencies = [ [[package]] name = "perry-ext-ads" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "block2", "objc2", @@ -5771,7 +5771,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "argon2", "perry-ffi", @@ -5779,7 +5779,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "reqwest", @@ -5788,7 +5788,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "bcrypt", "perry-ffi", @@ -5796,7 +5796,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "rusqlite", @@ -5804,7 +5804,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "scraper", @@ -5812,7 +5812,7 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "perry-runtime", @@ -5820,7 +5820,7 @@ dependencies = [ [[package]] name = "perry-ext-cron" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "chrono", "cron", @@ -5830,7 +5830,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "chrono", "perry-ffi", @@ -5838,7 +5838,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "rust_decimal", @@ -5846,7 +5846,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "serde_json", @@ -5854,7 +5854,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "rand 0.10.1", @@ -5862,7 +5862,7 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "perry-runtime", @@ -5870,14 +5870,14 @@ dependencies = [ [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "bytes", "http-body-util", @@ -5895,7 +5895,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "bytes", "lazy_static", @@ -5908,7 +5908,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "bytes", "h2", @@ -5932,7 +5932,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "lazy_static", "perry-ffi", @@ -5942,7 +5942,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "base64", "jsonwebtoken", @@ -5953,7 +5953,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "lru", "perry-ffi", @@ -5962,7 +5962,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "chrono", "perry-ffi", @@ -5970,7 +5970,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "bson", "futures-util", @@ -5982,7 +5982,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "chrono", "perry-ffi", @@ -5992,7 +5992,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "nanoid", "perry-ffi", @@ -6001,7 +6001,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "bytes", "perry-ffi", @@ -6014,7 +6014,7 @@ dependencies = [ [[package]] name = "perry-ext-node-forge" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "const-oid 0.9.6", "der 0.7.10", @@ -6033,7 +6033,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "lettre", "perry-ffi", @@ -6043,7 +6043,7 @@ dependencies = [ [[package]] name = "perry-ext-pdf" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "printpdf", @@ -6051,7 +6051,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "sqlx", @@ -6060,7 +6060,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "governor", "perry-ffi", @@ -6068,7 +6068,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "fast_image_resize", "image", @@ -6078,14 +6078,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "lazy_static", "perry-ffi", @@ -6094,7 +6094,7 @@ dependencies = [ [[package]] name = "perry-ext-undici" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "perry-runtime", @@ -6103,7 +6103,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "uuid", @@ -6111,7 +6111,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ffi", "regex", @@ -6121,7 +6121,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "futures-util", "lazy_static", @@ -6134,7 +6134,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "brotli", "flate2", @@ -6144,7 +6144,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "dashmap", "once_cell", @@ -6153,7 +6153,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "perry-api-manifest", @@ -6171,7 +6171,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "perry-diagnostics", @@ -6183,7 +6183,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "base64", @@ -6225,14 +6225,14 @@ dependencies = [ [[package]] name = "perry-runtime-static" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-runtime", ] [[package]] name = "perry-stdlib" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "aes 0.8.4", "aes 0.9.1", @@ -6327,14 +6327,14 @@ dependencies = [ [[package]] name = "perry-stdlib-static" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-stdlib", ] [[package]] name = "perry-transform" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "perry-hir", @@ -6343,14 +6343,14 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ui-model", ] [[package]] name = "perry-ui-android" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "base64", "itoa", @@ -6367,7 +6367,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "rand 0.10.1", "serde", @@ -6377,7 +6377,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "base64", "cairo-rs 0.22.0", @@ -6400,7 +6400,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "base64", "block2", @@ -6416,7 +6416,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "base64", "block2", @@ -6431,7 +6431,7 @@ dependencies = [ [[package]] name = "perry-ui-model" -version = "0.5.1280" +version = "0.5.1281" [[package]] name = "perry-ui-test" @@ -6442,11 +6442,11 @@ dependencies = [ [[package]] name = "perry-ui-testkit" -version = "0.5.1280" +version = "0.5.1281" [[package]] name = "perry-ui-tvos" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "base64", "block2", @@ -6462,7 +6462,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "base64", "block2", @@ -6478,7 +6478,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "block2", "libc", @@ -6491,7 +6491,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "base64", "libc", @@ -6508,14 +6508,14 @@ dependencies = [ [[package]] name = "perry-ui-windows-winui" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "perry-ui-windows", ] [[package]] name = "perry-updater" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "anyhow", "base64", @@ -6531,7 +6531,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.1280" +version = "0.5.1281" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index d0ce6d9a07..9718eed30d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -315,7 +315,7 @@ codegen-units = 16 codegen-units = 16 [workspace.package] -version = "0.5.1280" +version = "0.5.1281" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/changelog.d/7488-restore-raw-handle-gates.md b/changelog.d/7488-restore-raw-handle-gates.md new file mode 100644 index 0000000000..4a28285731 --- /dev/null +++ b/changelog.d/7488-restore-raw-handle-gates.md @@ -0,0 +1,27 @@ +### Gates: raw-handle debt and file-size regressions from the #7467 batch restored + +The #7467/#7470/#7471/#7474 review batch was merged against a stale +`origin/main` (`git fetch origin ` does not update `origin/main`), so +its gate check ran green while true `main` went red: the raw-handle total hit +1010 vs the 999 baseline, `array/indexing.rs` (7 > 6) and +`object/descriptors.rs` (17 > 12) exceeded their ceilings, +`object/field_get_set/enumeration.rs` carried 5 bare reads with no ceiling, +and `array/indexing.rs` crossed the 2000-line cap (2005). + +Restored without weakening anything: + +- **Eleven canonical pairs converted to `across_*`** (descriptors ×5, + indexing ×1, regex/exec ×1, regex/match_string ×2, regex.rs ×1, map.rs ×1) + — total back to exactly 999, descriptors at its 12 ceiling, indexing at 6. + Ceilings ratcheted down on every touched module. +- **`enumeration.rs` listed under a new, documented exception** in the + `raw_handle_debt_files.txt` header: a loop whose collection window is a + user-visible trap call (here the Proxy values/entries interleave — ownKeys, + then per-key gOPD + get) re-reads every handle per iteration; that re-read + IS the #7341 discipline and has no `across_*` form. Listing such a module is + legitimate only when the same change converts enough pairs elsewhere that + the global baseline does not rise — which this change does. +- **`array/indexing.rs` split**: the four bulk-fill entry points + (`js_array_fill_f64_{const,iota}[_len]_extend`) and their keepalive anchors + moved to `array/fill_extend.rs` (all reached by emitted code only; symbol + names unchanged). 1757 + 262 lines, both under the cap. diff --git a/crates/perry-runtime/src/array/fill_extend.rs b/crates/perry-runtime/src/array/fill_extend.rs new file mode 100644 index 0000000000..e014ea18db --- /dev/null +++ b/crates/perry-runtime/src/array/fill_extend.rs @@ -0,0 +1,262 @@ +//! Bulk numeric dense fills for compiler-proven trivial loops +//! (`js_array_fill_f64_{const,iota}[_len]_extend`). +//! +//! Split out of `indexing.rs` (which owns the per-element get/set paths these +//! fall back to) to keep that file under the 2000-line cap. Every entry point +//! here is `#[no_mangle] extern "C"` and reached by emitted code only — the +//! declarations live in `perry-codegen/src/runtime_decls/arrays.rs` and the +//! loop-pattern lowering in `perry-codegen/src/stmt/loops.rs`. + +use super::header::{array_numeric_layout, NumericArrayLayout}; +use super::indexing::note_array_index_write; +use super::*; +use std::ptr; + +/// Bulk numeric dense fill for compiler-proven trivial loops: +/// `for (let i = 0; i < end; i++) arr[i] = constant`. +/// +/// This keeps JavaScript semantics for frozen/sealed/descriptor arrays by +/// falling back to the ordinary extending setter. The fast path is restricted +/// to dense writes from index 0 through `end - 1`, so the resulting live +/// payload is entirely numeric and can be marked raw-f64 / pointer-free once. +#[no_mangle] +pub extern "C" fn js_array_fill_f64_const_extend( + arr: *mut ArrayHeader, + end: u32, + value: f64, +) -> *mut ArrayHeader { + if end == 0 { + return clean_arr_ptr_mut(arr); + } + let arr = clean_arr_ptr_mut(arr); + if arr.is_null() { + let new_arr = js_array_alloc(end); + return js_array_fill_f64_const_extend(new_arr, end, value); + } + note_array_index_write(arr as usize); + let flags = array_object_flags(arr); + if flags + & (crate::gc::OBJ_FLAG_FROZEN + | crate::gc::OBJ_FLAG_SEALED + | crate::gc::OBJ_FLAG_NO_EXTEND + | crate::gc::OBJ_FLAG_ARRAY_DESCRIPTORS) + != 0 + || crate::buffer::is_registered_buffer(arr as usize) + || crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() + { + let mut out = arr; + for i in 0..end { + out = js_array_set_f64_extend(out, i, value); + } + return out; + } + + let Some(number) = value_bits_to_number(value.to_bits()) else { + let mut out = arr; + for i in 0..end { + out = js_array_set_f64_extend(out, i, value); + } + return out; + }; + + unsafe { + let old_length = (*arr).length; + let raw_before = array_numeric_layout(arr) == Some(NumericArrayLayout::RawF64); + if old_length > end && !raw_before { + let mut fallback = arr; + for i in 0..end { + fallback = js_array_set_f64_extend(fallback, i, value); + } + return fallback; + } + + let mut out = if end > (*arr).capacity { + js_array_grow(arr, end) + } else { + arr + }; + out = clean_arr_ptr_mut(out); + if out.is_null() || end > (*out).capacity { + let mut fallback = arr; + for i in 0..end { + fallback = js_array_set_f64_extend(fallback, i, value); + } + return fallback; + } + let elements_ptr = (out as *mut u8).add(std::mem::size_of::()) as *mut f64; + for i in 0..end as usize { + // GC_STORE_AUDIT(POINTER_FREE): bulk numeric fill writes raw f64s only. + ptr::write(elements_ptr.add(i), number); + } + if (*out).length < end { + (*out).length = end; + } + set_array_numeric_layout(out, NumericArrayLayout::RawF64); + out + } +} + +/// Bulk numeric dense fill for loops bounded by current array length: +/// `for (let i = 0; i < arr.length; i++) arr[i] = constant`. +/// +/// If the receiver is exotic (frozen/sealed/descriptors/etc.), the fallback +/// re-reads `.length` on every iteration so it preserves the source loop's +/// observable behavior when setters mutate array length. +#[no_mangle] +pub extern "C" fn js_array_fill_f64_const_len_extend( + arr: *mut ArrayHeader, + value: f64, +) -> *mut ArrayHeader { + let arr = clean_arr_ptr_mut(arr); + let end = js_array_length(arr); + if end == 0 || arr.is_null() { + return arr; + } + note_array_index_write(arr as usize); + let flags = array_object_flags(arr); + if flags + & (crate::gc::OBJ_FLAG_FROZEN + | crate::gc::OBJ_FLAG_SEALED + | crate::gc::OBJ_FLAG_NO_EXTEND + | crate::gc::OBJ_FLAG_ARRAY_DESCRIPTORS) + != 0 + || crate::buffer::is_registered_buffer(arr as usize) + || crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() + { + let mut out = arr; + let mut i = 0; + while i < js_array_length(out) { + out = js_array_set_f64_extend(out, i, value); + i += 1; + } + return out; + } + js_array_fill_f64_const_extend(arr, end, value) +} + +/// Bulk numeric dense fill for compiler-proven trivial loops: +/// `for (let i = 0; i < end; i++) arr[i] = i`. +#[no_mangle] +pub extern "C" fn js_array_fill_f64_iota_extend( + arr: *mut ArrayHeader, + end: u32, +) -> *mut ArrayHeader { + if end == 0 { + return clean_arr_ptr_mut(arr); + } + let arr = clean_arr_ptr_mut(arr); + if arr.is_null() { + let new_arr = js_array_alloc(end); + return js_array_fill_f64_iota_extend(new_arr, end); + } + note_array_index_write(arr as usize); + let flags = array_object_flags(arr); + if flags + & (crate::gc::OBJ_FLAG_FROZEN + | crate::gc::OBJ_FLAG_SEALED + | crate::gc::OBJ_FLAG_NO_EXTEND + | crate::gc::OBJ_FLAG_ARRAY_DESCRIPTORS) + != 0 + || crate::buffer::is_registered_buffer(arr as usize) + || crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() + { + let mut out = arr; + for i in 0..end { + out = js_array_set_f64_extend(out, i, i as f64); + } + return out; + } + + unsafe { + let old_length = (*arr).length; + let raw_before = array_numeric_layout(arr) == Some(NumericArrayLayout::RawF64); + if old_length > end && !raw_before { + let mut fallback = arr; + for i in 0..end { + fallback = js_array_set_f64_extend(fallback, i, i as f64); + } + return fallback; + } + + let mut out = if end > (*arr).capacity { + js_array_grow(arr, end) + } else { + arr + }; + out = clean_arr_ptr_mut(out); + if out.is_null() || end > (*out).capacity { + let mut fallback = arr; + for i in 0..end { + fallback = js_array_set_f64_extend(fallback, i, i as f64); + } + return fallback; + } + let elements_ptr = (out as *mut u8).add(std::mem::size_of::()) as *mut f64; + for i in 0..end as usize { + // GC_STORE_AUDIT(POINTER_FREE): bulk iota fill writes raw f64s only. + ptr::write(elements_ptr.add(i), i as f64); + } + if (*out).length < end { + (*out).length = end; + } + set_array_numeric_layout(out, NumericArrayLayout::RawF64); + out + } +} + +/// Bulk numeric dense fill for loops bounded by current array length: +/// `for (let i = 0; i < arr.length; i++) arr[i] = i`. +/// +/// If the receiver is exotic (frozen/sealed/descriptors/etc.), the fallback +/// re-reads `.length` on every iteration so it preserves the source loop's +/// observable behavior when setters mutate array length. +#[no_mangle] +pub extern "C" fn js_array_fill_f64_iota_len_extend(arr: *mut ArrayHeader) -> *mut ArrayHeader { + let arr = clean_arr_ptr_mut(arr); + let end = js_array_length(arr); + if end == 0 || arr.is_null() { + return arr; + } + note_array_index_write(arr as usize); + let flags = array_object_flags(arr); + if flags + & (crate::gc::OBJ_FLAG_FROZEN + | crate::gc::OBJ_FLAG_SEALED + | crate::gc::OBJ_FLAG_NO_EXTEND + | crate::gc::OBJ_FLAG_ARRAY_DESCRIPTORS) + != 0 + || crate::buffer::is_registered_buffer(arr as usize) + || crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() + { + let mut out = arr; + let mut i = 0; + while i < js_array_length(out) { + out = js_array_set_f64_extend(out, i, i as f64); + i += 1; + } + return out; + } + js_array_fill_f64_iota_extend(arr, end) +} + +#[cfg(feature = "keepalive-anchors")] +#[used] +static KEEP_ARRAY_FILL_F64_CONST_EXTEND: extern "C" fn( + *mut ArrayHeader, + u32, + f64, +) -> *mut ArrayHeader = js_array_fill_f64_const_extend; +#[cfg(feature = "keepalive-anchors")] +#[used] +static KEEP_ARRAY_FILL_F64_IOTA_EXTEND: extern "C" fn(*mut ArrayHeader, u32) -> *mut ArrayHeader = + js_array_fill_f64_iota_extend; +#[cfg(feature = "keepalive-anchors")] +#[used] +static KEEP_ARRAY_FILL_F64_CONST_LEN_EXTEND: extern "C" fn( + *mut ArrayHeader, + f64, +) -> *mut ArrayHeader = js_array_fill_f64_const_len_extend; +#[cfg(feature = "keepalive-anchors")] +#[used] +static KEEP_ARRAY_FILL_F64_IOTA_LEN_EXTEND: extern "C" fn(*mut ArrayHeader) -> *mut ArrayHeader = + js_array_fill_f64_iota_len_extend; diff --git a/crates/perry-runtime/src/array/indexing.rs b/crates/perry-runtime/src/array/indexing.rs index dd8df3f78a..e8d2ba4c1f 100644 --- a/crates/perry-runtime/src/array/indexing.rs +++ b/crates/perry-runtime/src/array/indexing.rs @@ -1226,233 +1226,6 @@ pub extern "C" fn js_array_set_f64_extend( } } -/// Bulk numeric dense fill for compiler-proven trivial loops: -/// `for (let i = 0; i < end; i++) arr[i] = constant`. -/// -/// This keeps JavaScript semantics for frozen/sealed/descriptor arrays by -/// falling back to the ordinary extending setter. The fast path is restricted -/// to dense writes from index 0 through `end - 1`, so the resulting live -/// payload is entirely numeric and can be marked raw-f64 / pointer-free once. -#[no_mangle] -pub extern "C" fn js_array_fill_f64_const_extend( - arr: *mut ArrayHeader, - end: u32, - value: f64, -) -> *mut ArrayHeader { - if end == 0 { - return clean_arr_ptr_mut(arr); - } - let arr = clean_arr_ptr_mut(arr); - if arr.is_null() { - let new_arr = js_array_alloc(end); - return js_array_fill_f64_const_extend(new_arr, end, value); - } - note_array_index_write(arr as usize); - let flags = array_object_flags(arr); - if flags - & (crate::gc::OBJ_FLAG_FROZEN - | crate::gc::OBJ_FLAG_SEALED - | crate::gc::OBJ_FLAG_NO_EXTEND - | crate::gc::OBJ_FLAG_ARRAY_DESCRIPTORS) - != 0 - || crate::buffer::is_registered_buffer(arr as usize) - || crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() - { - let mut out = arr; - for i in 0..end { - out = js_array_set_f64_extend(out, i, value); - } - return out; - } - - let Some(number) = value_bits_to_number(value.to_bits()) else { - let mut out = arr; - for i in 0..end { - out = js_array_set_f64_extend(out, i, value); - } - return out; - }; - - unsafe { - let old_length = (*arr).length; - let raw_before = array_numeric_layout(arr) == Some(NumericArrayLayout::RawF64); - if old_length > end && !raw_before { - let mut fallback = arr; - for i in 0..end { - fallback = js_array_set_f64_extend(fallback, i, value); - } - return fallback; - } - - let mut out = if end > (*arr).capacity { - js_array_grow(arr, end) - } else { - arr - }; - out = clean_arr_ptr_mut(out); - if out.is_null() || end > (*out).capacity { - let mut fallback = arr; - for i in 0..end { - fallback = js_array_set_f64_extend(fallback, i, value); - } - return fallback; - } - let elements_ptr = (out as *mut u8).add(std::mem::size_of::()) as *mut f64; - for i in 0..end as usize { - // GC_STORE_AUDIT(POINTER_FREE): bulk numeric fill writes raw f64s only. - ptr::write(elements_ptr.add(i), number); - } - if (*out).length < end { - (*out).length = end; - } - set_array_numeric_layout(out, NumericArrayLayout::RawF64); - out - } -} - -/// Bulk numeric dense fill for loops bounded by current array length: -/// `for (let i = 0; i < arr.length; i++) arr[i] = constant`. -/// -/// If the receiver is exotic (frozen/sealed/descriptors/etc.), the fallback -/// re-reads `.length` on every iteration so it preserves the source loop's -/// observable behavior when setters mutate array length. -#[no_mangle] -pub extern "C" fn js_array_fill_f64_const_len_extend( - arr: *mut ArrayHeader, - value: f64, -) -> *mut ArrayHeader { - let arr = clean_arr_ptr_mut(arr); - let end = js_array_length(arr); - if end == 0 || arr.is_null() { - return arr; - } - note_array_index_write(arr as usize); - let flags = array_object_flags(arr); - if flags - & (crate::gc::OBJ_FLAG_FROZEN - | crate::gc::OBJ_FLAG_SEALED - | crate::gc::OBJ_FLAG_NO_EXTEND - | crate::gc::OBJ_FLAG_ARRAY_DESCRIPTORS) - != 0 - || crate::buffer::is_registered_buffer(arr as usize) - || crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() - { - let mut out = arr; - let mut i = 0; - while i < js_array_length(out) { - out = js_array_set_f64_extend(out, i, value); - i += 1; - } - return out; - } - js_array_fill_f64_const_extend(arr, end, value) -} - -/// Bulk numeric dense fill for compiler-proven trivial loops: -/// `for (let i = 0; i < end; i++) arr[i] = i`. -#[no_mangle] -pub extern "C" fn js_array_fill_f64_iota_extend( - arr: *mut ArrayHeader, - end: u32, -) -> *mut ArrayHeader { - if end == 0 { - return clean_arr_ptr_mut(arr); - } - let arr = clean_arr_ptr_mut(arr); - if arr.is_null() { - let new_arr = js_array_alloc(end); - return js_array_fill_f64_iota_extend(new_arr, end); - } - note_array_index_write(arr as usize); - let flags = array_object_flags(arr); - if flags - & (crate::gc::OBJ_FLAG_FROZEN - | crate::gc::OBJ_FLAG_SEALED - | crate::gc::OBJ_FLAG_NO_EXTEND - | crate::gc::OBJ_FLAG_ARRAY_DESCRIPTORS) - != 0 - || crate::buffer::is_registered_buffer(arr as usize) - || crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() - { - let mut out = arr; - for i in 0..end { - out = js_array_set_f64_extend(out, i, i as f64); - } - return out; - } - - unsafe { - let old_length = (*arr).length; - let raw_before = array_numeric_layout(arr) == Some(NumericArrayLayout::RawF64); - if old_length > end && !raw_before { - let mut fallback = arr; - for i in 0..end { - fallback = js_array_set_f64_extend(fallback, i, i as f64); - } - return fallback; - } - - let mut out = if end > (*arr).capacity { - js_array_grow(arr, end) - } else { - arr - }; - out = clean_arr_ptr_mut(out); - if out.is_null() || end > (*out).capacity { - let mut fallback = arr; - for i in 0..end { - fallback = js_array_set_f64_extend(fallback, i, i as f64); - } - return fallback; - } - let elements_ptr = (out as *mut u8).add(std::mem::size_of::()) as *mut f64; - for i in 0..end as usize { - // GC_STORE_AUDIT(POINTER_FREE): bulk iota fill writes raw f64s only. - ptr::write(elements_ptr.add(i), i as f64); - } - if (*out).length < end { - (*out).length = end; - } - set_array_numeric_layout(out, NumericArrayLayout::RawF64); - out - } -} - -/// Bulk numeric dense fill for loops bounded by current array length: -/// `for (let i = 0; i < arr.length; i++) arr[i] = i`. -/// -/// If the receiver is exotic (frozen/sealed/descriptors/etc.), the fallback -/// re-reads `.length` on every iteration so it preserves the source loop's -/// observable behavior when setters mutate array length. -#[no_mangle] -pub extern "C" fn js_array_fill_f64_iota_len_extend(arr: *mut ArrayHeader) -> *mut ArrayHeader { - let arr = clean_arr_ptr_mut(arr); - let end = js_array_length(arr); - if end == 0 || arr.is_null() { - return arr; - } - note_array_index_write(arr as usize); - let flags = array_object_flags(arr); - if flags - & (crate::gc::OBJ_FLAG_FROZEN - | crate::gc::OBJ_FLAG_SEALED - | crate::gc::OBJ_FLAG_NO_EXTEND - | crate::gc::OBJ_FLAG_ARRAY_DESCRIPTORS) - != 0 - || crate::buffer::is_registered_buffer(arr as usize) - || crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() - { - let mut out = arr; - let mut i = 0; - while i < js_array_length(out) { - out = js_array_set_f64_extend(out, i, i as f64); - i += 1; - } - return out; - } - js_array_fill_f64_iota_extend(arr, end) -} - /// Try to perform `arr[i] = arr[i] + delta` over a dense numeric window. /// /// This is intentionally transactional: the first pass validates the actual @@ -1548,27 +1321,6 @@ pub extern "C" fn js_array_numeric_range_add_len(receiver: f64, start: f64, delt array_numeric_range_add_impl(receiver, start, None, delta) } -#[cfg(feature = "keepalive-anchors")] -#[used] -static KEEP_ARRAY_FILL_F64_CONST_EXTEND: extern "C" fn( - *mut ArrayHeader, - u32, - f64, -) -> *mut ArrayHeader = js_array_fill_f64_const_extend; -#[cfg(feature = "keepalive-anchors")] -#[used] -static KEEP_ARRAY_FILL_F64_IOTA_EXTEND: extern "C" fn(*mut ArrayHeader, u32) -> *mut ArrayHeader = - js_array_fill_f64_iota_extend; -#[cfg(feature = "keepalive-anchors")] -#[used] -static KEEP_ARRAY_FILL_F64_CONST_LEN_EXTEND: extern "C" fn( - *mut ArrayHeader, - f64, -) -> *mut ArrayHeader = js_array_fill_f64_const_len_extend; -#[cfg(feature = "keepalive-anchors")] -#[used] -static KEEP_ARRAY_FILL_F64_IOTA_LEN_EXTEND: extern "C" fn(*mut ArrayHeader) -> *mut ArrayHeader = - js_array_fill_f64_iota_len_extend; #[cfg(feature = "keepalive-anchors")] #[used] static KEEP_ARRAY_NUMERIC_RANGE_ADD: extern "C" fn(f64, f64, f64, f64) -> i64 = @@ -1750,11 +1502,11 @@ pub extern "C" fn js_array_get_index_or_string(arr: *const ArrayHeader, idx: f64 // that evacuates the receiver; `arr` is a bare Rust local. let scope = crate::gc::RuntimeHandleScope::new(); let arr_handle = scope.root_raw_const_ptr(arr); - let key_ptr = crate::string::js_string_from_bytes(key.as_ptr(), key.len() as u32); - return array_get_property_by_key( - arr_handle.get_raw_const_ptr::(), - key_ptr, - ); + // Allocating key build + receiver re-read as one combinator (#7341). + let (key_ptr, arr_now) = arr_handle.across_const::(|| { + crate::string::js_string_from_bytes(key.as_ptr(), key.len() as u32) + }); + return array_get_property_by_key(arr_now, key_ptr); } } diff --git a/crates/perry-runtime/src/array/mod.rs b/crates/perry-runtime/src/array/mod.rs index c8243e3cba..1b5998905f 100644 --- a/crates/perry-runtime/src/array/mod.rs +++ b/crates/perry-runtime/src/array/mod.rs @@ -1,6 +1,7 @@ //! Array representation for Perry — split into topical sub-modules. mod alloc; mod concat_reverse; +mod fill_extend; mod flat_clone; mod from_concat; mod generic; diff --git a/crates/perry-runtime/src/map.rs b/crates/perry-runtime/src/map.rs index b43a9d51f6..27308ee082 100644 --- a/crates/perry-runtime/src/map.rs +++ b/crates/perry-runtime/src/map.rs @@ -1959,8 +1959,9 @@ pub extern "C" fn js_map_entries(map: *const MapHeader) -> *mut crate::array::Ar // to MIN_ARRAY_CAPACITY), then write key/value/length directly. // Skips the two `js_array_push_f64` calls per pair (each does // its own bounds + capacity check). - let pair = crate::array::js_array_alloc(2); - let map = map_handle.get_raw_const_ptr::(); + // Allocating pair array + map re-read as one combinator (#7341). + let (pair, map) = + map_handle.across_const::(|| crate::array::js_array_alloc(2)); let entries = entries_ptr(map); let key = ptr::read(entries.add(i * 2)); let value = ptr::read(entries.add(i * 2 + 1)); diff --git a/crates/perry-runtime/src/object/descriptors.rs b/crates/perry-runtime/src/object/descriptors.rs index 546eba82d1..b54f3929bf 100644 --- a/crates/perry-runtime/src/object/descriptors.rs +++ b/crates/perry-runtime/src/object/descriptors.rs @@ -284,8 +284,10 @@ pub extern "C" fn js_object_get_own_property_descriptor(obj_value: f64, key_valu // `TypedArrayHeader` after it. let scope = crate::gc::RuntimeHandleScope::new(); let addr_handle = scope.root_raw_mut_ptr(addr as *mut u8); - let key_str = crate::builtins::js_string_coerce(key_value); - let addr = addr_handle.get_raw_mut_ptr::() as usize; + // Allocating coercion + re-read as one combinator (#7341). + let (key_str, addr_ptr) = + addr_handle.across_mut::(|| crate::builtins::js_string_coerce(key_value)); + let addr = addr_ptr as usize; if key_str.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } @@ -491,8 +493,10 @@ pub extern "C" fn js_object_get_own_property_descriptor(obj_value: f64, key_valu // the `func_ptr` read) *after* the GC-capable coercion below. let scope = crate::gc::RuntimeHandleScope::new(); let ptr_handle = scope.root_raw_mut_ptr(ptr as *mut u8); - let key_str = crate::builtins::js_string_coerce(key_value); - let ptr = ptr_handle.get_raw_mut_ptr::() as usize; + // Allocating coercion + re-read as one combinator (#7341). + let (key_str, ptr_raw) = ptr_handle + .across_mut::(|| crate::builtins::js_string_coerce(key_value)); + let ptr = ptr_raw as usize; if key_str.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); } @@ -649,8 +653,10 @@ pub extern "C" fn js_object_get_own_property_descriptor(obj_value: f64, key_valu } else { let scope = crate::gc::RuntimeHandleScope::new(); let obj_handle = scope.root_raw_mut_ptr(obj); - let key_str = crate::builtins::js_string_coerce(key_value); - (obj_handle.get_raw_mut_ptr::(), key_str) + // Allocating coercion + re-read as one combinator (#7341). + let (key_str, obj_now) = obj_handle + .across_mut::(|| crate::builtins::js_string_coerce(key_value)); + (obj_now, key_str) }; if key_str.is_null() { return f64::from_bits(crate::value::TAG_UNDEFINED); @@ -1432,13 +1438,12 @@ unsafe fn proxy_get_own_property_descriptors(obj_value: f64) -> f64 { desc_handle.get_nanbox_f64(), ); } else { - let key_str = crate::builtins::js_string_coerce(key_handle.get_nanbox_f64()); + // Allocating coercion + receiver re-read as one combinator (#7341). + let (key_str, result_ptr) = result_handle.across_mut::(|| { + crate::builtins::js_string_coerce(key_handle.get_nanbox_f64()) + }); if !key_str.is_null() { - js_object_set_field_by_name( - result_handle.get_raw_mut_ptr::(), - key_str, - desc_handle.get_nanbox_f64(), - ); + js_object_set_field_by_name(result_ptr, key_str, desc_handle.get_nanbox_f64()); } } } @@ -1523,13 +1528,12 @@ pub extern "C" fn js_object_get_own_property_descriptors(obj_value: f64) -> f64 continue; } desc_handle.set_nanbox_f64(desc); - let key_str = crate::builtins::js_string_coerce(key_handle.get_nanbox_f64()); + // Allocating coercion + receiver re-read as one combinator (#7341). + let (key_str, result_ptr) = result_handle.across_mut::(|| { + crate::builtins::js_string_coerce(key_handle.get_nanbox_f64()) + }); if !key_str.is_null() { - js_object_set_field_by_name( - result_handle.get_raw_mut_ptr::(), - key_str, - desc_handle.get_nanbox_f64(), - ); + js_object_set_field_by_name(result_ptr, key_str, desc_handle.get_nanbox_f64()); } } } diff --git a/crates/perry-runtime/src/regex.rs b/crates/perry-runtime/src/regex.rs index 0d4ba4856a..202ecc2d33 100644 --- a/crates/perry-runtime/src/regex.rs +++ b/crates/perry-runtime/src/regex.rs @@ -1371,8 +1371,9 @@ pub extern "C" fn js_string_split_regex_n( let arr = crate::array::js_array_alloc(1); let scope = crate::gc::RuntimeHandleScope::new(); let arr_handle = scope.root_raw_mut_ptr(arr); - let str_ptr = js_string_from_str(&str_data) as u64; - let arr = arr_handle.get_raw_mut_ptr::(); + // Allocating string + array re-read as one combinator (#7341). + let (str_ptr, arr) = + arr_handle.across_mut::(|| js_string_from_str(&str_data) as u64); unsafe { (*arr).length = 1; let nanboxed = STRING_TAG | (str_ptr & POINTER_MASK); diff --git a/crates/perry-runtime/src/regex/exec.rs b/crates/perry-runtime/src/regex/exec.rs index 406448fb12..c18c701949 100644 --- a/crates/perry-runtime/src/regex/exec.rs +++ b/crates/perry-runtime/src/regex/exec.rs @@ -182,9 +182,10 @@ pub extern "C" fn js_regexp_exec( for (i, cap) in caps.iter().enumerate() { if let Some(m) = cap { - let str_ptr = js_string_from_str(m.as_str()); - let nanboxed = js_nanbox_string(str_ptr as i64); - let arr = arr_handle.get_raw_mut_ptr::(); + // Allocating string + array re-read as one combinator (#7341). + let (nanboxed, arr) = arr_handle.across_mut::(|| { + js_nanbox_string(js_string_from_str(m.as_str()) as i64) + }); // GC_STORE_AUDIT(INIT): fresh exec-array slot; layout is // noted per store and the exact layout/barrier rebuild // below the loop covers a mid-loop tenuring (#6386). diff --git a/crates/perry-runtime/src/regex/match_string.rs b/crates/perry-runtime/src/regex/match_string.rs index d4cea24273..b125afdbd0 100644 --- a/crates/perry-runtime/src/regex/match_string.rs +++ b/crates/perry-runtime/src/regex/match_string.rs @@ -201,9 +201,10 @@ pub extern "C" fn js_string_match( (*arr_handle.get_raw_mut_ptr::()).length = matches.len() as u32; for (i, m) in matches.iter().enumerate() { - let str_ptr = js_string_from_str(m); - let nanboxed = js_nanbox_string(str_ptr as i64); - let arr = arr_handle.get_raw_mut_ptr::(); + // Allocating string + array re-read as one combinator (#7341). + let (nanboxed, arr) = arr_handle.across_mut::(|| { + js_nanbox_string(js_string_from_str(m) as i64) + }); // GC_STORE_AUDIT(BARRIERED): regex global match array slot uses the shared array slot-store helper. crate::array::store_array_slot(arr, i, nanboxed.to_bits()); } @@ -221,9 +222,10 @@ pub extern "C" fn js_string_match( for (i, cap) in caps.iter().enumerate() { if let Some(m) = cap { - let str_ptr = js_string_from_str(m.as_str()); - let nanboxed = js_nanbox_string(str_ptr as i64); - let arr = arr_handle.get_raw_mut_ptr::(); + // Allocating string + array re-read as one combinator (#7341). + let (nanboxed, arr) = arr_handle.across_mut::(|| { + js_nanbox_string(js_string_from_str(m.as_str()) as i64) + }); // GC_STORE_AUDIT(INIT): fresh match-array slot; layout is // noted per store and the exact layout/barrier rebuild // below the loop covers a mid-loop tenuring (#6386). diff --git a/scripts/raw_handle_debt_files.txt b/scripts/raw_handle_debt_files.txt index b16a9e2f4c..7bcc88b64a 100644 --- a/scripts/raw_handle_debt_files.txt +++ b/scripts/raw_handle_debt_files.txt @@ -19,6 +19,17 @@ # genuinely needs a new bare read in an unlisted file, the honest move is to # convert a pair elsewhere and say so in the PR, not to add a line here. # +# ONE shape may join the list instead of converting: a LOOP whose collection +# window is a user-visible trap/getter call (Proxy traps, accessors, valueOf) +# re-reads every live handle at the top of each iteration. That re-read IS the +# #7341 discipline -- `across_*` pairs exactly one allocating call with one +# re-read and cannot express "re-read across two interleaved trap calls, every +# iteration". Listing such a module is legitimate ONLY when the same change +# converts enough pairs elsewhere that the global baseline does not rise; name +# the loop and the trap in the PR. Current instances of this shape: +# - object/field_get_set/enumeration.rs -- proxy values/entries interleave +# (ownKeys, then per-key gOPD + get; #7467). +# # 595 of 705 runtime modules were already clean when this was seeded, so the # rule above covers 84% of the crate on day one. # @@ -51,7 +62,7 @@ 3 crates/perry-runtime/src/json/stringify.rs 1 crates/perry-runtime/src/json/stringify_scalars.rs 22 crates/perry-runtime/src/json_tape.rs -27 crates/perry-runtime/src/map.rs +26 crates/perry-runtime/src/map.rs 2 crates/perry-runtime/src/module_require.rs 7 crates/perry-runtime/src/node_stream/async_iterator.rs 2 crates/perry-runtime/src/node_stream_dispatch.rs @@ -66,6 +77,7 @@ 5 crates/perry-runtime/src/object/class_registry/construct.rs 2 crates/perry-runtime/src/object/delete_rest.rs 12 crates/perry-runtime/src/object/descriptors.rs +5 crates/perry-runtime/src/object/field_get_set/enumeration.rs 2 crates/perry-runtime/src/object/field_get_set/get_field_by_name.rs 3 crates/perry-runtime/src/object/field_set_by_name/fast_paths.rs 11 crates/perry-runtime/src/object/field_set_by_name/tail.rs @@ -100,11 +112,11 @@ 8 crates/perry-runtime/src/promise/then.rs 8 crates/perry-runtime/src/proxy.rs 1 crates/perry-runtime/src/proxy/put_value.rs -9 crates/perry-runtime/src/regex.rs -17 crates/perry-runtime/src/regex/exec.rs +8 crates/perry-runtime/src/regex.rs +16 crates/perry-runtime/src/regex/exec.rs 30 crates/perry-runtime/src/regex/exec_array.rs 14 crates/perry-runtime/src/regex/match_all.rs -24 crates/perry-runtime/src/regex/match_string.rs +22 crates/perry-runtime/src/regex/match_string.rs 7 crates/perry-runtime/src/regex/replace_expand.rs 3 crates/perry-runtime/src/regex/replace_fn.rs 41 crates/perry-runtime/src/set.rs