Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
056abd7
perf(runtime): one string per property-key text, so a key confirm is …
Sep 23, 2026
ee4b31a
perf(runtime): confirm a megamorphic site's slot guess against the re…
Sep 26, 2026
e8a6c40
fix(runtime): an atom is key identity, never interned-key eligibility
Sep 27, 2026
e8e5307
docs(changelog): megamorphic reads confirm the slot guess by key atom
Sep 28, 2026
d98497f
changelog: name the fragment after PR #11633
Sep 28, 2026
a875735
Merge remote-tracking branch 'origin/main' into pr11633
Sep 28, 2026
71406b4
fix(runtime): an SSO key slot is its own atom; say so in code for the…
Sep 28, 2026
330dfd6
Merge origin/main into perf-megamorphic-atoms
Sep 28, 2026
805b222
regen: js_string_pool_atom in the wasm ABI table and the linux gc-cal…
Sep 28, 2026
5bfb01c
test(runtime): atoms survive a moving minor via the atom young log
Sep 28, 2026
93aee48
perf(runtime): POSBOUND, the shape record's position bound as one field
Sep 28, 2026
c519ea9
perf: one GC-leaf miss front per generic read site (D3, D3b)
Sep 29, 2026
70c6e2f
changelog: name the fragment after #11657
Sep 29, 2026
a7f95ca
perf: the read miss front takes the receiver as the fused test holds …
Sep 29, 2026
f7ac76c
changelog: name the fragment after #11658
Sep 29, 2026
06d8f06
Merge PerryTS/main into perf-megamorphic-front
Sep 29, 2026
269c1a0
merge fixups: stack guard knows WindowsTeb; census reads the Slot slab
Sep 29, 2026
cdc9844
Merge commit 'refs/tmp/mf-main' into HEAD
Sep 29, 2026
617b834
rustfmt; say that an in-place rep deprecation leaves POSBOUND as it is
Sep 29, 2026
e7d4f0e
Merge commit 'refs/tmp/fx57-main' into HEAD
Sep 29, 2026
f51aea9
shapes tests: the position-bound rep test passes no static id request
Sep 29, 2026
d59f8c4
Merge commit 'f51aea97b2e798574467b8201bd3ab4c8e9e1c3d' into HEAD
Sep 29, 2026
3158e10
Merge commit 'refs/tmp/m57b-main' into m57b-front
Sep 29, 2026
bf251a1
Merge branch 'm57b-front' into m57b-ways
Sep 29, 2026
159470f
lint: thread-exit verdicts for the shared-empty shape statics; drop a…
Sep 29, 2026
2b6cca2
Merge branch 'm57b-front' into m57b-ways
Sep 29, 2026
b4dcefb
Merge remote-tracking branch 'origin/main' into HEAD
Sep 29, 2026
9f4f312
Merge commit 'b4dcefb44' into HEAD
Sep 29, 2026
776412c
shapes tests: the seeded-literal confirm test follows the dir-passing…
Sep 29, 2026
de70497
Merge commit '776412c4e0e14575f7c21cdf30ec944a0d2dca84' into HEAD
Sep 29, 2026
4681706
Merge main (squashed #11657) into perf-read-ways
Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions changelog.d/11658-read-miss-front-biased-receiver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
A generic property read site passes its receiver to the GC-leaf miss front
(`js_object_get_field_ic_front`) as the value its fused receiver test already
holds (the payload minus the native-handle floor, now one shared constant in
perry-abi), so the site pays a register move where it paid a 10-byte constant
and an add; the front adds the floor back inside its load displacements.
Every polymorphic way hit and latched megamorphic read saves three
instructions (lead_poly4 132.0 -> 129.7 instr/iter, lead_mega1 164.3 ->
161.5). The way cascade now asserts, in debug builds, that an
overflow-encoded slot never enters a way: the front answers a way with a
plain inline load and no spill re-test.
7 changes: 7 additions & 0 deletions crates/perry-abi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ pub const AGENT_PTR_SLOTS: usize = 4;
/// thread-local. Slot 1 held the implicit-`this` cell's address until
/// this-as-a-parameter deleted the cell, and is free; slot 2 is the stack limit.
pub const AGENT_PTR_SHAPE_DIR: usize = 0;
/// Payloads below this are native-registry handles, never heap cells
/// (`addr_class::HANDLE_BAND_MAX`). A generic read site's fused receiver test
/// computes `payload - RECEIVER_HANDLE_FLOOR` on its pointer edge, and its
/// miss front (`js_object_get_field_ic_front`) takes the receiver in exactly
/// that form: the front adds the floor back inside its load displacements,
/// and the site passes the value its test already holds.
pub const RECEIVER_HANDLE_FLOOR: usize = 0x10_0000;
/// Slot 2: this agent's stack limit (#10812) — not a pointer to anything, the
/// lowest frame address a compiled prologue accepts before it throws
/// `RangeError: Maximum call stack size exceeded`. Null means unchecked.
Expand Down
16 changes: 14 additions & 2 deletions crates/perry-codegen/src/expr/property_get/generic_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1098,15 +1098,27 @@ pub(crate) fn lower_generic_property_get(
EMPTY_SHAPE_DIR,
)
};
let front_handle = recv_handle(ctx, fused_recv.as_ref(), &entry_handle);
// The receiver as the fused test's biased value (payload minus
// `RECEIVER_HANDLE_FLOOR`, the front's operand form): one register
// move here, where the payload is a 10-byte constant and an add,
// since LLVM folds `biased + floor` back into `bits - POINTER_TAG`.
// A `length` site has no fused test and subtracts the floor itself.
let front_recv = match fused_recv.as_ref() {
Some(f) => f.biased.clone(),
None => ctx.block().sub(
I64,
&entry_handle,
&crate::runtime_abi::RECEIVER_HANDLE_FLOOR.to_string(),
),
};
let key_box = ctx.block().load(DOUBLE, &key_handle_global);
let key_bits = ctx.block().bitcast_double_to_i64(&key_box);
let answered = ctx.block().call(
DOUBLE,
"js_object_get_field_ic_front",
&[
(PTR, &dir),
(I64, &front_handle),
(I64, &front_recv),
(I64, &key_bits),
(PTR, &cache_slot_ref),
(PTR, &packed_ref),
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/expr/receiver_range.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ use crate::types::{I32, I64, I8};

/// Payloads below this are native-registry handles, never heap cells
/// (`js_native_call_method`'s small-handle test, `addr_class::HANDLE_BAND_MAX`).
pub(crate) const HANDLE_FLOOR: u64 = 0x10_0000;
pub(crate) const HANDLE_FLOOR: u64 = crate::runtime_abi::RECEIVER_HANDLE_FLOOR as u64;
/// `POINTER_TAG | HANDLE_FLOOR`: subtracting it maps exactly the heap-object
/// receivers onto `[0, RECEIVER_SPAN)`.
pub(crate) const RECEIVER_BIAS: u64 = crate::nanbox::POINTER_TAG | HANDLE_FLOOR;
Expand Down
7 changes: 7 additions & 0 deletions crates/perry-runtime/src/object/field_get_set/ic_miss.rs
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,13 @@ pub(crate) unsafe fn pic_prime_get(cache: *mut PicCache, token: i64, slot: i64)
PIC_WAY_BASE + v as usize * 2
}
};
// First-read Q1: a way never holds a spill or overflow entry, so the miss
// front answers a way with a plain inline load and no spill re-test.
// `cascade` above is what guarantees it.
debug_assert!(
(prev_slot as u64) & u64::from(crate::proxy::IC_SLOT_OVERFLOW_BIT) == 0,
"an overflow-encoded slot must never enter a way"
);
c[ti] = prev_tok;
c[ti + 1] = prev_slot;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ unsafe fn inline_slot(obj: *const ObjectHeader, slot: usize) -> f64 {
/// nothing; never null. A `length` site passes the empty one on purpose: an
/// Array-subclass receiver serves `length` from its elements store, which no
/// key list names.
/// * `obj_handle` — the receiver's payload. The site calls only on the
/// ShapeId compare's false edge, which its small-handle test dominates, so
/// this is a real object pointer (its `+4` word was just loaded).
/// * `obj_biased` — the receiver's payload minus
/// `perry_abi::RECEIVER_HANDLE_FLOOR`: the value the site's fused receiver
/// test already holds on its pointer edge, so passing it costs the site a
/// register move where the payload cost a 10-byte constant and an add
/// (first-read D4: every way hit pays this edge). The front adds the floor
/// back in its load displacements. The site calls only on the ShapeId
/// compare's false edge, which its small-handle test dominates, so the
/// payload is a real object pointer (its `+4` word was just loaded).
/// * `key_bits` — the site's key exactly as its pool global holds it: the
/// interned key, STRING-tagged, which is also how a canonical key list
/// stores it, so the confirm compares one word.
Expand All @@ -74,12 +79,13 @@ unsafe fn inline_slot(obj: *const ObjectHeader, slot: usize) -> f64 {
#[no_mangle]
pub unsafe extern "C" fn js_object_get_field_ic_front(
dir: *const u8,
obj_handle: i64,
obj_biased: i64,
key_bits: u64,
cache_slot: *mut PicCacheSlot,
packed: *const AtomicU64,
) -> f64 {
let obj = obj_handle as usize as *const ObjectHeader;
let obj =
(obj_biased as usize).wrapping_add(perry_abi::RECEIVER_HANDLE_FLOOR) as *const ObjectHeader;
let shape_id = (*obj).parent_class_id;
// `pic_slot_peek` without its null test: the slot is the site's global.
let cache = (*(cache_slot as *const std::sync::atomic::AtomicPtr<crate::object::PicCache>))
Expand All @@ -93,8 +99,12 @@ pub unsafe extern "C" fn js_object_get_field_ic_front(
(*cache)[PIC_WAY_STATE]
};
if state > 0 {
// 1. The ways. A way token is `PIC_ID_TOKEN_BIT | ShapeId`; an empty
// way is 0 and cannot match.
// 1. The ways (first-read D4), in order, each hit loading its own
// way's slot; the ShapeId is the one loaded above. A way token is
// `PIC_ID_TOKEN_BIT | ShapeId`; an empty way is 0 and cannot match.
// No spill re-test: a way never holds a spill or overflow entry
// (`pic_prime_get` publishes a spill entry only to the compact word,
// and refuses to cascade an overflow-encoded slot into a way).
let token = (shape_id as u64 | PIC_ID_TOKEN_BIT) as i64;
for w in 0..PIC_WAYS {
if (*cache)[PIC_WAY_BASE + 2 * w] == token {
Expand Down Expand Up @@ -204,7 +214,8 @@ pub(crate) unsafe fn test_site_miss_read(
crate::object::shapes::ordinary_dir_addr()
};
let key_bits = key as usize as u64 | crate::value::STRING_TAG;
let v = js_object_get_field_ic_front(dir, obj_handle, key_bits, cache_slot, packed);
let obj_biased = obj_handle.wrapping_sub(perry_abi::RECEIVER_HANDLE_FLOOR as i64);
let v = js_object_get_field_ic_front(dir, obj_biased, key_bits, cache_slot, packed);
if v.to_bits() != crate::value::TAG_HOLE {
return v;
}
Expand Down Expand Up @@ -347,7 +358,9 @@ mod tests {
let packed = AtomicU64::new(word);
let dir = crate::object::shapes::ordinary_dir_addr();
let bits = unsafe {
super::js_object_get_field_ic_front(dir, obj as i64, key_bits, cache_slot, &packed)
// The operand form a site passes (`obj_biased`, see the front).
let biased = (obj as i64).wrapping_sub(perry_abi::RECEIVER_HANDLE_FLOOR as i64);
super::js_object_get_field_ic_front(dir, biased, key_bits, cache_slot, &packed)
.to_bits()
};
(bits, packed.load(Ordering::Relaxed))
Expand Down
2 changes: 2 additions & 0 deletions crates/perry-runtime/src/value/addr_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ use crate::gc::{GcHeader, GC_HEADER_SIZE};
/// Raising any sub-band past this value requires auditing every
/// `is_handle_band` caller.
pub const HANDLE_BAND_MAX: usize = 0x100000;
// Emitted receiver tests and the read miss front share this floor.
const _: () = assert!(HANDLE_BAND_MAX == perry_abi::RECEIVER_HANDLE_FLOOR);

/// Exclusive end of the generic perry-stdlib `common/handle.rs` registry band
/// (`[1, COMMON_HANDLE_BAND_END)`). The registry panics rather than allocate
Expand Down
Loading