-
-
Notifications
You must be signed in to change notification settings - Fork 159
fix(array): ask the typed-array question before clean_arr_ptr rejects it, for sort/toSorted/toReversed/with #8119
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,110 @@ | ||
| ### Fixed | ||
|
|
||
| - **Typed-array `sort`, `toSorted`, `toReversed` and `with` were silent no-ops | ||
| or returned an empty array (#8096).** #8090 fixed `fill`, ranged `fill`, | ||
| `reverse` and `copyWithin` by asking `array::header::typed_array_receiver()` | ||
| BEFORE the `clean_arr_ptr` funnel, and named these six helpers as carrying the | ||
| identical dead delegation: | ||
|
|
||
| | helper | file | clean at | delegation at | | ||
| |---|---|---|---| | ||
| | `js_array_to_reversed` | `array/immutable.rs:32` | 33 | 37 | | ||
| | `js_array_to_sorted_default` | `array/immutable.rs:59` | 60 | 61 | | ||
| | `js_array_to_sorted_with_comparator` | `array/immutable.rs:96` | 105 | 106 | | ||
| | `js_array_with` | `array/immutable.rs:232` | 242 | 246 | | ||
| | `js_array_sort_default` | `array/sort.rs:542` | 551 | 561 | | ||
| | `js_array_sort_with_comparator` | `array/sort.rs:635` | 651 | 658 | | ||
|
|
||
| Codegen routes statically-typed typed-array receivers through the generic | ||
| `js_array_*` helpers on purpose (#3148 / #654 — `is_array_expr` answers `true` | ||
| for `Int32Array` &co.) on the contract that each helper re-dispatches on | ||
| `lookup_typed_array_kind`. `clean_arr_ptr` rejects those receivers, and must: | ||
| since #7574 it returns null for every tracked non-`GC_TYPE_ARRAY` object, | ||
| because a `TypedArrayHeader`'s raw per-kind storage is not boxed-f64 | ||
| `ArrayHeader` slots. So every delegation written below the clean was | ||
| unreachable. | ||
|
|
||
| Two distinct silent failures followed. In-place `sort` returned the null'd | ||
| pointer having sorted nothing; `toReversed` / `toSorted` / `with` returned | ||
| `js_array_alloc(0)` — an EMPTY PLAIN ARRAY, so `.constructor.name` was | ||
| `"Array"` and `JSON.stringify(Array.from(x))` was `[]`. | ||
|
|
||
| Measured against the pinned node oracle (`v26.5.1`, `.node-version`), both | ||
| arms exit 0: | ||
|
|
||
| ``` | ||
| node perry before | ||
| i32 sort default 1 2 9 10 10 9 2 1 (no-op) | ||
| f64 sort cmp desc 10.5 9.25 2 1 1 10.5 2 9.25 (no-op) | ||
| i32 toSorted 1 2 9 10 undefined x4 (empty array) | ||
| f64 toReversed 4.5 3.5 2.5 1.5 undefined x4 | ||
| i32 with 1 99 3 4 undefined x4 | ||
| i8 with wraps -56 undefined | ||
| ctor names Int32Array x3 Array x3 | ||
| ``` | ||
|
|
||
| The plain-`Array` controls in the same program are correct in BOTH arms | ||
| (`plain sort default: 1 10 2 9`, the ToString ordering), which is what makes | ||
| the defect typed-array-specific rather than a general mutator break. | ||
|
|
||
| `sort` and `toSorted` have to reach the typed helper for the ORDER as well as | ||
| the layout: `%TypedArray%.prototype.sort` compares NUMERICALLY (§23.2.3.29 | ||
| CompareTypedArrayElements) where `Array.prototype.sort` compares by `ToString`. | ||
| `[10, 9, 2, 1]` separates all three possible answers — numeric `1, 2, 9, 10`, | ||
| string `1, 10, 2, 9`, no-op `10, 9, 2, 1` — and is what the new tests feed. | ||
| The comparator cases feed `[1, 10, 2, 9]` for the same reason: an | ||
| already-descending input would let a no-op pass a descending-sort assertion. | ||
|
|
||
| - **`Uint8Array` and `Buffer` `toReversed` / `toSorted` answered an empty array | ||
| on every dispatch path (#8096, second receiver shape).** Perry's | ||
| `new Uint8Array([…])` is not a registry `TypedArrayHeader` at all — | ||
| `buffer::js_uint8array_new` returns a `BufferHeader`, registered as a buffer | ||
| and marked `mark_as_uint8array`. So `typed_array_receiver`, which is | ||
| registry-backed, legitimately answers `None` for the most common typed array | ||
| in the language, while `clean_arr_ptr` still rejects it as a tracked | ||
| non-array. Verified directly on the real constructor path: | ||
|
|
||
| ``` | ||
| addr=0x20000b00008 is_registered_buffer=true lookup_typed_array_kind=None | ||
| gc_header_obj_type=Some(10) clean_arr_ptr_null=true | ||
| js_array_to_reversed(...).length = 0 | ||
| ``` | ||
|
|
||
| Most `Array.prototype` entry points never see this: `sort` / `with` / | ||
| `reverse` / `fill` on that shape resolve through the dynamic method | ||
| dispatcher, and `copyWithin` grew its own Buffer arm in #8090. But | ||
| `toReversed` and `toSorted` fold unconditionally in HIR | ||
| (`lower/expr_call/local_array_methods.rs` — no receiver-type guard), and the | ||
| dynamic tower's own `toReversed` / `toSorted` arms | ||
| (`object/native_call_method/handle_methods.rs`) call straight back into these | ||
| same helpers, so those two were wrong for `Uint8Array` and `Buffer` on every | ||
| dispatch path, static and dynamic: | ||
|
|
||
| ``` | ||
| ann u8 toReversed: node 4 9 2 10 1 [object Uint8Array] | ||
| perry 0 undefined undefined undefined undefined [object Array] | ||
| ``` | ||
|
|
||
| New `buffer_receiver_as_uint8_typed_array` (`array/header.rs`) resolves that | ||
| shape into a fresh `KIND_UINT8` %TypedArray% copy. It is sound only for the | ||
| IMMUTABLE methods — a copy-based in-place `sort` would sort the copy and leave | ||
| the receiver untouched, a different wrong answer — so `toReversed`, | ||
| `toSorted` and `with` use it and `js_array_sort_*` deliberately does not. It | ||
| declines `ArrayBuffer` / `SharedArrayBuffer` / `DataView` receivers, which | ||
| have no `%TypedArray%.prototype` in node. | ||
|
|
||
| Validation: five probes byte-identical to node `v26.5.1`, all exit 0 in both | ||
| arms. 12 tests added to | ||
| `crates/perry-runtime/src/array/typed_array_receiver_tests.rs` (19 in the file | ||
| with #8090's), sabotage-verified in two stages after real rebuilds — | ||
| reverting `immutable.rs` + `sort.rs` turns 6 of them red, neutering | ||
| `buffer_receiver_as_uint8_typed_array` turns the other 3 red. The 3 that stay | ||
| green in both arms are the intended controls: the plain-`Array` control, the | ||
| `ArrayBuffer` / `DataView` decline, and the registry precondition the Buffer | ||
| arm exists for (#8090's `clean_arr_ptr_still_rejects_a_typed_array_receiver` | ||
| keeps #7574 pinned alongside them). `cargo test -p perry-runtime --lib`: 2380 passed / 0 failed / 4 ignored | ||
| (`main`: 2361 / 0 / 4). `perry-codegen`: 1434 passed / 11 failed, identical to | ||
| the `main` baseline (#8092). | ||
|
|
||
| `js_array_to_spliced` needs no equivalent — `%TypedArray%.prototype` has no | ||
| `toSpliced`. | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -718,6 +718,69 @@ pub(crate) fn typed_array_receiver( | |||||||||||||||||||||||||||||||||||
| .map(|_| addr as *mut crate::typedarray::TypedArrayHeader) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// #8096: resolve a receiver that is a registered `Buffer` / `Uint8Array` | ||||||||||||||||||||||||||||||||||||
| /// into a fresh `KIND_UINT8` %TypedArray% COPY, so an `Array.prototype` | ||||||||||||||||||||||||||||||||||||
| /// helper can delegate to a `js_typed_array_*` twin that only accepts a | ||||||||||||||||||||||||||||||||||||
| /// `TypedArrayHeader`. | ||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||
| /// [`typed_array_receiver`] does not answer for the most common typed array | ||||||||||||||||||||||||||||||||||||
| /// in the language. Perry's `new Uint8Array([…])` returns a `BufferHeader` | ||||||||||||||||||||||||||||||||||||
| /// (`buffer::js_uint8array_new`), registered as a BUFFER and marked | ||||||||||||||||||||||||||||||||||||
| /// `mark_as_uint8array` — it is not in the typed-array registry at all. The | ||||||||||||||||||||||||||||||||||||
| /// receiver is still a tracked non-`GC_TYPE_ARRAY` allocation, so | ||||||||||||||||||||||||||||||||||||
| /// `clean_arr_ptr` rejects it exactly as it rejects a real | ||||||||||||||||||||||||||||||||||||
| /// `GC_TYPE_TYPED_ARRAY`, and the caller answers an EMPTY plain array. | ||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||
| /// Most `Array.prototype` entry points never see that: `sort` / `with` / | ||||||||||||||||||||||||||||||||||||
| /// `reverse` / `fill` on a Buffer-backed `Uint8Array` resolve through the | ||||||||||||||||||||||||||||||||||||
| /// dynamic method dispatcher, and `copyWithin` grew its own Buffer arm in | ||||||||||||||||||||||||||||||||||||
| /// #8090. `toReversed` and `toSorted` fold unconditionally in HIR | ||||||||||||||||||||||||||||||||||||
| /// (`lower/expr_call/local_array_methods.rs` — no receiver-type guard), and | ||||||||||||||||||||||||||||||||||||
| /// the dynamic tower's own `toReversed` / `toSorted` arms | ||||||||||||||||||||||||||||||||||||
| /// (`object/native_call_method/handle_methods.rs`) call straight back into | ||||||||||||||||||||||||||||||||||||
| /// these same helpers, so those two were wrong on EVERY dispatch path. | ||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||
| /// **Only sound for the IMMUTABLE methods.** The answer is a copy, so an | ||||||||||||||||||||||||||||||||||||
| /// in-place mutator delegating to it would sort/reverse the copy and leave | ||||||||||||||||||||||||||||||||||||
| /// the receiver untouched — a different wrong answer. `toReversed`, | ||||||||||||||||||||||||||||||||||||
| /// `toSorted` and `with` all return a new collection, which is why they can | ||||||||||||||||||||||||||||||||||||
| /// use it; `js_array_sort_*` deliberately does not. | ||||||||||||||||||||||||||||||||||||
| /// | ||||||||||||||||||||||||||||||||||||
| /// `None` for an `ArrayBuffer` / `SharedArrayBuffer` / `DataView` receiver: | ||||||||||||||||||||||||||||||||||||
| /// none of those has `%TypedArray%.prototype`, so node throws | ||||||||||||||||||||||||||||||||||||
| /// `TypeError: … is not a function` rather than answering elements. | ||||||||||||||||||||||||||||||||||||
| #[inline] | ||||||||||||||||||||||||||||||||||||
| pub(crate) fn buffer_receiver_as_uint8_typed_array( | ||||||||||||||||||||||||||||||||||||
| arr: *mut ArrayHeader, | ||||||||||||||||||||||||||||||||||||
| ) -> Option<*mut crate::typedarray::TypedArrayHeader> { | ||||||||||||||||||||||||||||||||||||
| let addr = array_receiver_addr(arr); | ||||||||||||||||||||||||||||||||||||
| if addr == 0 | ||||||||||||||||||||||||||||||||||||
| || !crate::buffer::is_registered_buffer(addr) | ||||||||||||||||||||||||||||||||||||
| || crate::buffer::is_any_array_buffer(addr) | ||||||||||||||||||||||||||||||||||||
| || crate::buffer::is_data_view(addr) | ||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||
| return None; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
Comment on lines
+756
to
+763
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 Use the canonical heap-address predicate. Add Proposed fix let addr = array_receiver_addr(arr);
if addr == 0
+ || !crate::value::addr_class::is_plausible_heap_addr(addr)
|| !crate::buffer::is_registered_buffer(addr)
|| crate::buffer::is_any_array_buffer(addr)
|| crate::buffer::is_data_view(addr)Based on learnings, similar receiver routing must call 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Learnings |
||||||||||||||||||||||||||||||||||||
| // Copy the bytes out BEFORE allocating: `typed_array_alloc` can collect, | ||||||||||||||||||||||||||||||||||||
| // and a raw payload pointer read across it is exactly the borrowed-heap- | ||||||||||||||||||||||||||||||||||||
| // slice shape rooting cannot fix. | ||||||||||||||||||||||||||||||||||||
| let buf = addr as *const crate::buffer::BufferHeader; | ||||||||||||||||||||||||||||||||||||
| let bytes: Vec<u8> = unsafe { | ||||||||||||||||||||||||||||||||||||
| let len = (*buf).length as usize; | ||||||||||||||||||||||||||||||||||||
| if len == 0 { | ||||||||||||||||||||||||||||||||||||
| Vec::new() | ||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||
| std::slice::from_raw_parts(crate::buffer::buffer_data(buf), len).to_vec() | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
| let ta = | ||||||||||||||||||||||||||||||||||||
| crate::typedarray::typed_array_alloc(crate::typedarray::KIND_UINT8, bytes.len() as u32); | ||||||||||||||||||||||||||||||||||||
| for (i, byte) in bytes.iter().enumerate() { | ||||||||||||||||||||||||||||||||||||
| crate::typedarray::js_typed_array_set(ta, i as i32, f64::from(*byte)); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| Some(ta) | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| /// The de-NaN-boxed address of an `Array.prototype` receiver, for side-table | ||||||||||||||||||||||||||||||||||||
| /// probes only. Says nothing about what lives there — never dereference it | ||||||||||||||||||||||||||||||||||||
| /// without one of the registry answers (`typed_array_receiver`, | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,15 +30,29 @@ fn throw_invalid_index(index: f64) -> ! { | |
| /// `arr.toReversed()` — return a new reversed copy (immutable) | ||
| #[no_mangle] | ||
| pub extern "C" fn js_array_to_reversed(arr: *const ArrayHeader) -> *mut ArrayHeader { | ||
| // #3148/#2879/#8096: %TypedArray% receiver — reverse over element-typed | ||
| // storage. Asked BEFORE `clean_arr_ptr` for the reason | ||
| // `typed_array_receiver` documents: written after the clean this branch is | ||
| // unreachable, because since #7574 the funnel returns null for every | ||
| // tracked non-`GC_TYPE_ARRAY` object and every typed array is a tracked | ||
| // `GC_TYPE_TYPED_ARRAY` one. Reached that way the helper answered an EMPTY | ||
| // plain array, not the receiver's reversed elements. | ||
| if let Some(ta) = typed_array_receiver(arr as *mut ArrayHeader) { | ||
| return crate::typedarray::js_typed_array_to_reversed(ta) as *mut ArrayHeader; | ||
| } | ||
| // #8096: the Buffer-backed `Uint8Array` shape — the SAME symptom from a | ||
| // different receiver. `new Uint8Array([…])` is a registered BufferHeader, | ||
| // so `typed_array_receiver` legitimately answers `None` and the clean | ||
| // below rejects it as a tracked non-array. See | ||
| // `buffer_receiver_as_uint8_typed_array` for why this arm is sound only | ||
| // for the immutable methods. | ||
| if let Some(ta) = buffer_receiver_as_uint8_typed_array(arr as *mut ArrayHeader) { | ||
| return crate::typedarray::js_typed_array_to_reversed(ta) as *mut ArrayHeader; | ||
| } | ||
| let arr = clean_arr_ptr(arr); | ||
| if arr.is_null() { | ||
| return js_array_alloc(0); | ||
| } | ||
| if crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() { | ||
| return crate::typedarray::js_typed_array_to_reversed( | ||
| arr as *const crate::typedarray::TypedArrayHeader, | ||
| ) as *mut ArrayHeader; | ||
| } | ||
| unsafe { | ||
| let len = (*arr).length as usize; | ||
| let new_arr = js_array_alloc(len as u32); | ||
|
|
@@ -57,12 +71,30 @@ pub extern "C" fn js_array_to_reversed(arr: *const ArrayHeader) -> *mut ArrayHea | |
| /// `arr.toSorted()` — return a new sorted copy (default string sort, immutable) | ||
| #[no_mangle] | ||
| pub extern "C" fn js_array_to_sorted_default(arr: *const ArrayHeader) -> *mut ArrayHeader { | ||
| let arr = clean_arr_ptr(arr); | ||
| if !arr.is_null() && crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() { | ||
| return crate::typedarray::js_typed_array_to_sorted_default( | ||
| arr as *const crate::typedarray::TypedArrayHeader, | ||
| ) as *mut ArrayHeader; | ||
| // #3148/#2879/#8096: %TypedArray% receiver — sort over element-typed | ||
| // storage. Asked BEFORE `clean_arr_ptr` for the reason | ||
| // `typed_array_receiver` documents: written after the clean this branch is | ||
| // unreachable, because since #7574 the funnel returns null for every | ||
| // tracked non-`GC_TYPE_ARRAY` object and every typed array is a tracked | ||
| // `GC_TYPE_TYPED_ARRAY` one. | ||
| // The typed twin also carries the right ORDER: `%TypedArray%.prototype. | ||
| // toSorted` with no comparator sorts NUMERICALLY (§23.2.3.32 -> | ||
| // CompareTypedArrayElements), where `Array.prototype.toSorted` sorts by | ||
| // ToString. Falling through to the plain-array body would be the string | ||
| // order even if the slots were readable (#8096). | ||
| if let Some(ta) = typed_array_receiver(arr as *mut ArrayHeader) { | ||
| return crate::typedarray::js_typed_array_to_sorted_default(ta) as *mut ArrayHeader; | ||
| } | ||
| // #8096: the Buffer-backed `Uint8Array` shape — the SAME symptom from a | ||
| // different receiver. `new Uint8Array([…])` is a registered BufferHeader, | ||
| // so `typed_array_receiver` legitimately answers `None` and the clean | ||
| // below rejects it as a tracked non-array. See | ||
| // `buffer_receiver_as_uint8_typed_array` for why this arm is sound only | ||
| // for the immutable methods. | ||
| if let Some(ta) = buffer_receiver_as_uint8_typed_array(arr as *mut ArrayHeader) { | ||
| return crate::typedarray::js_typed_array_to_sorted_default(ta) as *mut ArrayHeader; | ||
| } | ||
| let arr = clean_arr_ptr(arr); | ||
| if arr.is_null() { | ||
| return js_array_alloc(0); | ||
| } | ||
|
|
@@ -102,13 +134,27 @@ pub extern "C" fn js_array_to_sorted_with_comparator( | |
| if comparator.is_null() { | ||
| return js_array_to_sorted_default(arr); | ||
| } | ||
| let arr = clean_arr_ptr(arr); | ||
| if !arr.is_null() && crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() { | ||
| return crate::typedarray::js_typed_array_to_sorted_with_comparator( | ||
| arr as *const crate::typedarray::TypedArrayHeader, | ||
| comparator, | ||
| ) as *mut ArrayHeader; | ||
| // #3148/#2879/#8096: %TypedArray% receiver — sort over element-typed | ||
| // storage. Asked BEFORE `clean_arr_ptr` for the reason | ||
| // `typed_array_receiver` documents: written after the clean this branch is | ||
| // unreachable, because since #7574 the funnel returns null for every | ||
| // tracked non-`GC_TYPE_ARRAY` object and every typed array is a tracked | ||
| // `GC_TYPE_TYPED_ARRAY` one. | ||
| if let Some(ta) = typed_array_receiver(arr as *mut ArrayHeader) { | ||
| return crate::typedarray::js_typed_array_to_sorted_with_comparator(ta, comparator) | ||
| as *mut ArrayHeader; | ||
| } | ||
| // #8096: the Buffer-backed `Uint8Array` shape — the SAME symptom from a | ||
| // different receiver. `new Uint8Array([…])` is a registered BufferHeader, | ||
| // so `typed_array_receiver` legitimately answers `None` and the clean | ||
| // below rejects it as a tracked non-array. See | ||
| // `buffer_receiver_as_uint8_typed_array` for why this arm is sound only | ||
| // for the immutable methods. | ||
| if let Some(ta) = buffer_receiver_as_uint8_typed_array(arr as *mut ArrayHeader) { | ||
| return crate::typedarray::js_typed_array_to_sorted_with_comparator(ta, comparator) | ||
| as *mut ArrayHeader; | ||
| } | ||
|
Comment on lines
+147
to
156
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 | 🔴 Critical | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
ast-grep outline crates/perry-runtime/src/gc --items all --match RuntimeHandleScope
rg -n -C 6 \
'RuntimeHandleScope|js_typed_array_to_sorted_with_comparator|js_typed_array_with|typed_array_alloc' \
crates/perry-runtime/src
rg -n -C 6 'seeded.*gc|gc.*schedule|force.*collect|collect.*force' crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- immutable.rs relevant sections ---'
sed -n '110,175p;270,325p' crates/perry-runtime/src/array/immutable.rs
printf '%s\n' '--- conversion helper definition and callers ---'
rg -n -C 12 \
'fn buffer_receiver_as_uint8_typed_array|buffer_receiver_as_uint8_typed_array|js_typed_array_to_sorted_with_comparator|js_typed_array_with' \
crates/perry-runtime/src/array crates/perry-runtime/src/typedarray crates/perry-runtime/src
printf '%s\n' '--- RuntimeHandleScope API ---'
rg -n -C 8 \
'pub struct RuntimeHandleScope|impl RuntimeHandleScope|root_nanbox_f64|root_raw_const_ptr|root_raw_mut_ptr|get_nanbox_f64|refreshed_nanbox' \
crates/perry-runtime/src/gc crates/perry-runtime/src | head -n 500Repository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- buffer conversion helper ---'
sed -n '753,810p' crates/perry-runtime/src/array/header.rs
printf '%s\n' '--- typed-array transform functions ---'
sed -n '254,345p' crates/perry-runtime/src/typedarray/transform.rs
printf '%s\n' '--- RuntimeHandleScope declarations and accessors ---'
rg -l 'pub struct RuntimeHandleScope|impl RuntimeHandleScope' crates/perry-runtime/src/gc crates/perry-runtime/src \
| while read -r file; do
echo "--- $file ---"
rg -n -A 12 -B 4 \
'pub struct RuntimeHandleScope|impl RuntimeHandleScope|root_nanbox_f64|root_raw_const_ptr|root_raw_mut_ptr|get_nanbox_f64|refreshed_nanbox' \
"$file"
doneRepository: PerryTS/perry Length of output: 14931 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- typed_array_alloc and collection behavior ---'
rg -n -l 'fn typed_array_alloc|pub.*typed_array_alloc|gc_check_trigger|collect_minor|collect_major' \
crates/perry-runtime/src/typedarray crates/perry-runtime/src/gc crates/perry-runtime/src \
| sort -u \
| while read -r file; do
echo "--- $file ---"
rg -n -A 18 -B 8 \
'fn typed_array_alloc|pub.*typed_array_alloc|gc_check_trigger|collect_minor|collect_major' \
"$file" | head -n 260
done
printf '%s\n' '--- typed-array allocation/rooting patterns ---'
rg -n -C 8 \
'typed_array_alloc\(|root_raw.*TypedArray|root_nanbox.*value|js_typed_array_with\(' \
crates/perry-runtime/src/typedarray crates/perry-runtime/src/array \
| head -n 500Repository: PerryTS/perry Length of output: 50369 🏁 Script executed: #!/bin/bash
set -euo pipefail
file=$(rg -l 'typed_array_alloc' crates/perry-runtime/src | head -n 1)
printf '%s\n' "--- $file ---"
rg -n -A 30 -B 10 'typed_array_alloc' "$file"
printf '%s\n' '--- allocation-point policy comments ---'
rg -n -A 18 -B 8 \
'gc_check_trigger\(\).*non-moving|force_full_scan|allocation point|raw header pointers|raw heap pointers' \
crates/perry-runtime/src/gc crates/perry-runtime/src/arena \
| head -n 400Repository: PerryTS/perry Length of output: 40197 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 10 \
'(^|[[:space:]])(pub\(crate\)[[:space:]]+)?fn[[:space:]]+typed_array_alloc|typed_array_alloc[[:space:]]*=' \
crates/perry-runtime/src --glob '*.rs'
rg -n -C 8 \
'typed_array_alloc' \
crates/perry-runtime/src/typedarray --glob '*.rs'Repository: PerryTS/perry Length of output: 49895 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '852,910p' crates/perry-runtime/src/typedarray/mod.rsRepository: PerryTS/perry Length of output: 2553 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -A 28 -B 12 \
'fn arena_alloc_gc_old|pub.*arena_alloc_gc_old|arena_alloc_gc_old\(' \
crates/perry-runtime/src/arena crates/perry-runtime/src/gcRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n 'arena_alloc_gc_old' crates/perry-runtime/src/arena --glob '*.rs'Repository: PerryTS/perry Length of output: 3544 🏁 Script executed: #!/bin/bash
set -euo pipefail
sed -n '200,275p' crates/perry-runtime/src/arena/allocators.rsRepository: PerryTS/perry Length of output: 3706 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -A 24 -B 10 \
'pub.*fn arena_alloc_old|fn arena_alloc_old' \
crates/perry-runtime/src/arena/allocators.rsRepository: PerryTS/perry Length of output: 2166 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -A 22 -B 10 \
'pub.*fn js_closure_call2|fn js_closure_call2|js_closure_call2\(' \
crates/perry-runtime/src/closure crates/perry-runtime/src/thread.rs
sed -n '266,300p' crates/perry-runtime/src/typedarray/transform.rsRepository: PerryTS/perry Length of output: 10781 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -A 25 -B 10 \
'pub.*fn js_closure_alloc|fn js_closure_alloc|js_closure_alloc\(' \
crates/perry-runtime/src/closure crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 50370 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -l 'pub.*fn js_closure_alloc|fn js_closure_alloc' crates/perry-runtime/src/closure crates/perry-runtime/src --glob '*.rs' \
| while read -r file; do
echo "--- $file ---"
rg -n -A 28 -B 8 'pub.*fn js_closure_alloc|fn js_closure_alloc' "$file"
doneRepository: PerryTS/perry Length of output: 11719 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -A 24 -B 8 \
'fn closure_alloc_storage|closure_alloc_storage\(' \
crates/perry-runtime/src/closure/alloc.rsRepository: PerryTS/perry Length of output: 2996 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -A 55 -B 8 \
'pub extern "C" fn js_array_map|fn js_array_map' \
crates/perry-runtime/src/array crates/perry-runtime/srcRepository: PerryTS/perry Length of output: 26018 Root
🤖 Prompt for AI AgentsSource: Learnings |
||
| let arr = clean_arr_ptr(arr); | ||
| if arr.is_null() { | ||
| return js_array_alloc(0); | ||
| } | ||
|
|
@@ -239,17 +285,30 @@ pub extern "C" fn js_array_with( | |
| // source local can't corrupt it. No-op for SSO / non-string. The cloned | ||
| // elements come from `arr` and are already shared. (Mirrors #5548.) | ||
| crate::string::js_string_addref_if_heap_string(value); | ||
| // #3148/#2879/#8096: %TypedArray% receiver — replace one element over | ||
| // element-typed storage. Asked BEFORE `clean_arr_ptr` for the reason | ||
| // `typed_array_receiver` documents: written after the clean this branch is | ||
| // unreachable, because since #7574 the funnel returns null for every | ||
| // tracked non-`GC_TYPE_ARRAY` object and every typed array is a tracked | ||
| // `GC_TYPE_TYPED_ARRAY` one. | ||
| // The typed twin also owns the per-kind coercion of `value` (a `300` into | ||
| // a `Uint8Array` must read back `44`) and the typed RangeError text. | ||
| if let Some(ta) = typed_array_receiver(arr as *mut ArrayHeader) { | ||
| return crate::typedarray::js_typed_array_with(ta, index, value) as *mut ArrayHeader; | ||
| } | ||
| // #8096: the Buffer-backed `Uint8Array` shape — the SAME symptom from a | ||
| // different receiver. `new Uint8Array([…])` is a registered BufferHeader, | ||
| // so `typed_array_receiver` legitimately answers `None` and the clean | ||
| // below rejects it as a tracked non-array. See | ||
| // `buffer_receiver_as_uint8_typed_array` for why this arm is sound only | ||
| // for the immutable methods. | ||
| if let Some(ta) = buffer_receiver_as_uint8_typed_array(arr as *mut ArrayHeader) { | ||
| return crate::typedarray::js_typed_array_with(ta, index, value) as *mut ArrayHeader; | ||
| } | ||
| let arr = clean_arr_ptr(arr); | ||
| if arr.is_null() { | ||
| return js_array_alloc(0); | ||
| } | ||
| if crate::typedarray::lookup_typed_array_kind(arr as usize).is_some() { | ||
| return crate::typedarray::js_typed_array_with( | ||
| arr as *const crate::typedarray::TypedArrayHeader, | ||
| index, | ||
| value, | ||
| ) as *mut ArrayHeader; | ||
| } | ||
| unsafe { | ||
| let len = (*arr).length as isize; | ||
| // ECMA ToIntegerOrInfinity: NaN coerces to 0, ±Infinity stay infinite. | ||
|
|
||
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.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
Correct the location table.
The
clean atanddelegation atcoordinates no longer identify those statements in the final files. Label these values as pre-fix coordinates, or replace them with stable function names.Based on learnings, changelog fragments must accurately describe shipped behavior.
🤖 Prompt for AI Agents
Source: Learnings