-
-
Notifications
You must be signed in to change notification settings - Fork 161
perf(object): borrow ASCII property keys unchecked; ask the async-resource registry before decoding the key #9765
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| ### Performance | ||
|
|
||
| - **Property reads no longer UTF-8-validate ASCII keys, and no longer copy | ||
| the key or take the async-resource registry lock for ordinary receivers.** | ||
| The generic read ladder decodes the key `StringHeader` at several layers | ||
| per miss (`js_object_get_field_ic_miss`, closure expando lookup, accessor | ||
| and reflection probes, the typed-feedback class-field guards, async- | ||
| resource dispatch); `core::str::from_utf8` on those decodes was 2 % of the | ||
| claude-code keystroke profile, the guard's `String` copy was a `malloc` | ||
| per guarded class-field access, and `async_resource_property` copied the | ||
| key and locked the registry before asking whether any AsyncResource | ||
| handle existed at all. | ||
|
|
||
| - `crates/perry-runtime/src/string/mod.rs` — `header_str_checked`: a | ||
| header whose `utf16_len == byte_len` is pure ASCII, so it is borrowed | ||
| unchecked; anything else takes the `from_utf8` scan it always took | ||
| (WTF-8 payloads still answer `None`). Used by `has_own_helpers`, | ||
| `closure_dynamic_prop_by_key`, the accessor probes, | ||
| `typedarray_props::string_header_str` and the typed-feedback guards | ||
| (which now borrow instead of allocating a `String`; every consumer is a | ||
| Rust-side table read, so the payload cannot move while borrowed). | ||
| - `crates/perry-runtime/src/async_hooks.rs` — `is_async_resource_handle` | ||
| answers from the atomic handle count before touching the mutex, and the | ||
| IC-miss handler / `async_resource_property` ask it before decoding or | ||
| copying the key. | ||
|
|
||
| Test: `header_str_checked_matches_from_utf8_on_every_payload_class` | ||
| (ASCII, non-ASCII scalar, lone surrogate, empty). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1027,6 +1027,32 @@ pub(crate) fn is_ascii_string(s: *const StringHeader) -> bool { | |
| unsafe { (*s).utf16_len == (*s).byte_len } | ||
| } | ||
|
|
||
| /// Borrow a header's payload as `&str`, answering `None` for a WTF-8 payload | ||
| /// (lone surrogates), like `std::str::from_utf8(..).ok()` — but without the | ||
| /// scan when the header already proves the answer: `utf16_len == byte_len` | ||
| /// holds iff every byte is a one-byte code unit, i.e. pure ASCII, which is | ||
| /// what nearly every property key is. The generic property-read ladder | ||
| /// decodes the key at several layers per read (`ic_miss`, closure expandos, | ||
| /// accessor and reflection probes, async-resource dispatch), and | ||
| /// `core::str::from_utf8` was 2 % of the claude-code keystroke profile on | ||
| /// those decodes alone. | ||
| /// | ||
| /// Same borrow rule as [`string_as_str`]: the slice must not outlive any | ||
| /// call that can move the payload. | ||
| /// | ||
| /// # Safety | ||
| /// `s` must point at a live `StringHeader`. | ||
| #[inline] | ||
| pub(crate) unsafe fn header_str_checked<'a>(s: *const StringHeader) -> Option<&'a str> { | ||
| let len = (*s).byte_len as usize; | ||
| let bytes = slice::from_raw_parts(string_data(s), len); | ||
| if (*s).utf16_len as usize == len { | ||
| Some(str::from_utf8_unchecked(bytes)) | ||
|
Comment on lines
+1049
to
+1050
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🔎 Supported by static analysis🤖 get_repo_knowledge executed:
Length of output: 9639 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- target context ---'
sed -n '1000,1070p' crates/perry-runtime/src/string/mod.rs
printf '%s\n' '--- length helper and StringHeader references ---'
rg -n -C 4 'compute_utf16_len_wtf8|struct StringHeader|StringHeader|header_str_checked|from_utf8_unchecked' crates/perry-runtime/src/string
printf '%s\n' '--- string module outline ---'
ast-grep outline crates/perry-runtime/src/string/mod.rs --match 'fn $_' --view compact | head -120Repository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- exact helper definitions and call sites ---'
rg -n -C 8 'compute_utf16_len_wtf8|header_str_checked|js_string_from_bytes_with_capacity|js_string_from_bytes\(' crates/perry-runtime/src/string --glob '*.rs' | head -240
printf '%s\n' '--- focused constructor implementation ---'
rg -n 'pub .*fn (js_string_from_bytes|js_string_from_bytes_with_capacity)|fn compute_utf16_len_wtf8|struct StringHeader' crates/perry-runtime/src/string --glob '*.rs'Repository: PerryTS/perry Length of output: 20976 🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- StringHeader and UTF-16 length calculation ---'
sed -n '390,430p;870,925p' crates/perry-runtime/src/string/mod.rs
printf '%s\n' '--- byte-string constructors ---'
sed -n '1,180p' crates/perry-runtime/src/string/alloc.rsRepository: PerryTS/perry Length of output: 12459 Require valid UTF-8 before the unchecked conversion.
🤖 Prompt for AI Agents |
||
| } else { | ||
| str::from_utf8(bytes).ok() | ||
| } | ||
| } | ||
|
|
||
| /// `PERRY_GC_CENSUS`: the fixed-size intern table (slots, bytes). Entries | ||
| /// point into the GC heap; only the table itself is counted. | ||
| pub(crate) fn intern_table_census() -> (usize, usize) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Copy the key before
closure_get_dynamic_prop.header_str_checkedreturns a borrow into GC-managed storage.closure_get_dynamic_propcan run an accessor and allocate, as documented incrates/perry-runtime/src/object/field_get_set/accessors.rsLines [694-707]. If the key moves,reified_function_method_nameuses stale bytes at Line [1399]. This can return the wrong method or crash.Copy the validated bytes into
HeapKeyBytesbefore the call, or root the key and re-read it after the call.Suggested fix
📝 Committable suggestion
🤖 Prompt for AI Agents