Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
21ec2c5
wpfacts: the length floor (P4 lever 1, PR-A)
Nov 9, 2026
e3a249c
Merge branch 'perf/p4-arr-floor' into perf/p4-arr-plain
Nov 9, 2026
9b733f8
wpfacts: plain Array births and Rep::Arr (P4 lever 1, PR-B)
Nov 9, 2026
ef86fbc
verify-facts S0: the pre-PR-B array arms run with PERRY_FACTS_ARRAY_R…
Nov 9, 2026
001a2fb
verify-facts S0: array-lazy-negative parses a 1 KiB text (the runtime…
Nov 9, 2026
113322e
wpfacts: the arrlazy arm sabotages Gen::json_value, the path HIR JSON…
Nov 9, 2026
bf73b61
Merge remote-tracking branch 'origin/main' into perf/p4-arr-plain
Nov 9, 2026
05d0ca9
Merge remote-tracking branch 'origin/main' into perf/p4-arr-plain
Nov 9, 2026
1ddb123
codegen: the element read tier keyed by the receiver's Arr rep (P4 le…
Nov 9, 2026
be32e49
PR-C2 wip: coalesce consumer, tests, fixtures, arrfront S0, changelog
Nov 9, 2026
254432a
Merge remote-tracking branch 'origin/main' into perf/p4-arr-tier
Nov 9, 2026
1622116
PR-C2 wip: rustfmt
Nov 10, 2026
d7450d9
PR-C2 wip: a key outside the floor is bounded by the capacity
Nov 10, 2026
3a41e51
PR-C2 wip: test expectations
Nov 10, 2026
85df72b
PR-C2 wip: arrfront fixture reads outside a loop
Nov 10, 2026
3681bde
PR-C2 wip: test inputs
Nov 10, 2026
27188ea
Merge remote-tracking branch 'origin/main' into perf/p4-arr-fields
Nov 10, 2026
8e86aa3
PR-C3 wip: field-held arrays through the rep-keyed tier
Nov 10, 2026
8766d29
PR-C3 wip: tests, fixtures, changelog
Nov 10, 2026
3f35773
PR-C3 wip: rustfmt
Nov 10, 2026
9384181
PR-C3 wip: test inputs
Nov 10, 2026
9b04629
Merge remote-tracking branch 'origin/main' into perf/p4-arr-fields
Nov 10, 2026
85f5113
PR-C3 wip: wpfacts test inputs
Nov 10, 2026
4aea763
PR-C3 wip: test expectation
Nov 10, 2026
3e92663
PR-C3 wip: test input
Nov 10, 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
1 change: 1 addition & 0 deletions changelog.d/wpfacts-array-rep-fields.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Performance: an array held in a class field reads under its whole-program array representation. A field that, on every object of its class, holds only plain program Arrays for the object's whole life (`FieldFacts::rep`: definite, a data field, the class neither escaped nor reshaped by `delete` or `setPrototypeOf`) makes a read of it a value of representation `Arr`, the join over the receiver's classes, so `this.f[i]` takes the element read tier that locals and module constants already take: no pointer-band test, no type test, the capacity as the bound, the slot addressed right behind the header. A method that is exactly `return this.f[k] ?? L` and is called with a key the facts prove inside the dense floor of every Array the field can hold (`this.at(move & 127)` on a board born `new Array(128).fill(0)`) reads a present element: the call is the field load, the forwarding test when the storage can grow, and the slot load, with no bound, no hole test and, for Number elements, no `??`. A call whose key can leave the floor keeps the bound, the hole test and the `??`. `PERRY_FACTS_ARRAY_FIELDS=0` turns both off; `PERRY_VERIFY_FACTS=1` re-tests the field's claim where the field is read and the call's claim where the slot is loaded, and `PERRY_FACTS_SABOTAGE=arrfield` and `arrb1` are the negative controls. `PERRY_FACTS_DUMP` prints each such call with its key interval, the representation it reads from and the in-floor verdict.
27 changes: 27 additions & 0 deletions crates/perry-codegen/src/codegen/program_facts_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ pub struct FactsField {
/// or when interpreted code can run.
pub classes: Vec<u32>,
pub nullable: bool,
/// The field's representation (wpfacts `FieldFacts::rep`, DESIGN-p4l1
/// §3): `Arr` when the field, on every object of the class, holds only
/// plain program Arrays for the object's whole life. A read of the
/// field is a value of this representation (`FieldProof::array`).
pub rep: FactsRep,
}

/// A plain compiled body of this module that a binding or a field holds
Expand Down Expand Up @@ -222,6 +227,23 @@ pub enum FactsEnc {
I32,
}

impl FactsArrRep {
/// The join (says less) of two array reps, wpfacts `ArrRep::join`.
pub fn join(a: FactsArrRep, b: FactsArrRep) -> FactsArrRep {
use FactsElemRep::{Boxed, F64, I32};
FactsArrRep {
elem: match (a.elem, b.elem) {
(Boxed, _) | (_, Boxed) => Boxed,
(F64, _) | (_, F64) => F64,
(I32, I32) => I32,
},
floor: a.floor.min(b.floor),
dense: a.dense && b.dense,
fixed: a.fixed && b.fixed,
}
}
}

impl FactsRep {
/// The slot encoding: an `Arr` slot holds the Boxed word.
pub fn encoding(self) -> FactsEnc {
Expand Down Expand Up @@ -463,6 +485,11 @@ pub struct FactsFieldReadCall {
/// The method body's `IndexGet::element_offset` and `this.f` offset.
pub element_offset: u32,
pub field_offset: u32,
/// `Some(floor)`: this call's index is an integer in `[0, floor)`, and
/// every Array the method's read may read from has at least `floor`
/// elements with no hole below it (wpfacts `FieldReadCall::in_floor`,
/// DESIGN-p4l1 §2.8): the slot is a present own element.
pub in_floor: Option<u32>,
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
Expand Down
1 change: 1 addition & 0 deletions crates/perry-codegen/src/expr/family_guard_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ fn tm1_facts(m: &Module) -> crate::codegen::ProgramFactsView {
fns: Vec::new(),
classes: Vec::new(),
nullable: false,
rep: crate::codegen::FactsRep::Boxed,
};
let mut elision = ElisionFacts::default();
elision.locals.insert(
Expand Down
2 changes: 1 addition & 1 deletion crates/perry-codegen/src/expr/index_get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use super::{
TypedFeedbackContract, TypedFeedbackKind,
};

mod array_rep;
pub(crate) mod array_rep;
mod foreign_counter;
pub(crate) mod descriptor_read;
pub(crate) mod elem_floor;
Expand Down
30 changes: 21 additions & 9 deletions crates/perry-codegen/src/expr/index_get/array_rep.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,23 @@ pub(crate) fn witness(ctx: &mut FnCtx<'_>, expr: &Expr) -> Result<()> {
} else {
None
};
emit(ctx, &recv, key.as_deref(), r, *id);
emit(ctx, &recv, key.as_deref(), r, &format!("local {id}"));
Ok(())
}

/// Check mode at a field read the facts give `Arr(r)` (DESIGN-p4l1 §5.1,
/// PR-C3): the claim on the word `value` the read loaded. `what` names the
/// field for the report.
pub(crate) fn witness_value(ctx: &mut FnCtx<'_>, value: &str, r: FactsArrRep, what: &str) {
crate::verify_facts::covered(
crate::verify_facts::Fact::ArrayRep,
|| serde_json::json!({ "field": what, "floor": r.floor }),
);
emit(ctx, value, None, r, what);
}

/// The tests of `witness` on the lowered receiver `recv` and key `key`.
fn emit(ctx: &mut FnCtx<'_>, recv: &str, key: Option<&str>, r: FactsArrRep, id: u32) {
fn emit(ctx: &mut FnCtx<'_>, recv: &str, key: Option<&str>, r: FactsArrRep, what: &str) {
let miss = ctx.new_block("arr_rep.verify_miss");
let next = ctx.new_block("arr_rep.admitted");
let (miss_l, next_l) = (ctx.block_label(miss), ctx.block_label(next));
Expand Down Expand Up @@ -121,7 +132,7 @@ fn emit(ctx: &mut FnCtx<'_>, recv: &str, key: Option<&str>, r: FactsArrRep, id:
let extent = ctx.block().and(I1, &long, &backed);
let Some(key) = key else {
ctx.block().cond_br(&extent, &next_l, &miss_l);
finish(ctx, miss, next, recv, r, id);
finish(ctx, miss, next, recv, r, what);
return;
};
branch(ctx, &extent, "arr_rep.key", &miss_l);
Expand Down Expand Up @@ -154,7 +165,7 @@ fn emit(ctx: &mut FnCtx<'_>, recv: &str, key: Option<&str>, r: FactsArrRep, id:
let present = ctx.block().xor(I1, &hole, "true");
branch(ctx, &present, "arr_rep.elem_kind", &next_l);
elem_test(ctx, &raw, r.elem, &next_l, &miss_l);
finish(ctx, miss, next, recv, r, id);
finish(ctx, miss, next, recv, r, what);
}

/// Check mode at the rep-keyed tier: what the read `recv[idx]` (`idx` an
Expand All @@ -169,7 +180,7 @@ pub(super) fn witness_read(
let r = read.rep;
crate::verify_facts::covered(
crate::verify_facts::Fact::ArrayRep,
|| serde_json::json!({ "local": read.local, "floor": r.floor, "read": true }),
|| serde_json::json!({ "receiver": read.receiver.label(), "floor": r.floor, "read": true }),
);
let miss = ctx.new_block("arep.verify_miss");
let next = ctx.new_block("arep.admitted");
Expand Down Expand Up @@ -215,7 +226,8 @@ pub(super) fn witness_read(
let extent = ctx.block().and(I1, &long, &backed);
branch(ctx, &extent, "arep.verify_key", &miss_l);
if read.in_floor {
let below = ctx.block().icmp_ult(I32, idx, &floor);
// The floor the key was proven below (a field read call's own).
let below = ctx.block().icmp_ult(I32, idx, &read.key_floor.to_string());
branch(ctx, &below, "arep.verify_stored", &miss_l);
}
// The slot, when the store holds it.
Expand Down Expand Up @@ -247,7 +259,7 @@ pub(super) fn witness_read(
r.elem
};
elem_test(ctx, &raw, elem, &next_l, &miss_l);
finish(ctx, miss, next, recv, r, read.local);
finish(ctx, miss, next, recv, r, &read.receiver.label());
}

/// The element kind test of a present slot `raw`: none for Boxed, a plain
Expand Down Expand Up @@ -287,12 +299,12 @@ fn branch(ctx: &mut FnCtx<'_>, cond: &str, name: &str, other: &str) {
}

/// The miss block's report, then `next` is current.
fn finish(ctx: &mut FnCtx<'_>, miss: usize, next: usize, recv: &str, r: FactsArrRep, id: u32) {
fn finish(ctx: &mut FnCtx<'_>, miss: usize, next: usize, recv: &str, r: FactsArrRep, what: &str) {
ctx.current_block = miss;
let site = crate::verify_facts::site_arg(
ctx,
&format!(
"array rep of local {id}: elem {:?}, floor {}, dense {}, fixed {}",
"array rep of {what}: elem {:?}, floor {}, dense {}, fixed {}",
r.elem, r.floor, r.dense, r.fixed
),
crate::verify_facts::Fact::ArrayRep,
Expand Down
124 changes: 103 additions & 21 deletions crates/perry-codegen/src/expr/index_get/proven_array.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
//! Element reads whose receiver wpfacts proves (representation RFC §10.7,
//! phase P4).
//!
//! **The rep-keyed tier** (DESIGN-p4l1 §4.2, PR-C2; `RepRead`). A local,
//! parameter or module const the facts give `FactsRep::Arr(r)` holds only
//! **The rep-keyed tier** (DESIGN-p4l1 §4.2, PR-C2 and PR-C3; `RepRead`).
//! A local, parameter or module const the facts give `FactsRep::Arr(r)`,
//! and a field read whose every class holds `Arr` in the field
//! (`wpfacts_access::field_array`, `r` their join), holds only
//! plain program Arrays: each is a `GC_TYPE_ARRAY` for its whole life, with
//! no element descriptor and no deleted element (no hard marker), `length`
//! and `capacity` of at least `r.floor`, no hole below the floor when
Expand Down Expand Up @@ -33,9 +35,14 @@
//! (REVIEW-p4l1 R6). `PERRY_FACTS_ARRAY_TIER=0` turns the tier off: every
//! read keeps the tier it had.
//!
//! A field read call (b1, `lower_call::field_read_call`) whose own index
//! is in the floor (wpfacts `FieldReadCall::in_floor`) reads here too
//! (`lower_in_floor`): the method body's read joins every caller's key,
//! the call has its own.
//!
//! **The fixed-storage read** (wpfacts `ModuleBindings::element_reads`,
//! `FactsElementRead`), for a receiver the tier above does not name (a
//! field read, a temporary).
//! temporary, a field the facts do not give `Arr`).
//!
//! Every receiver of such a read is a program Array that keeps its birth
//! length for the whole program: no store, builtin, `defineProperty` or
Expand Down Expand Up @@ -76,12 +83,32 @@ pub(super) enum Proven {
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct RepRead {
pub(crate) rep: FactsArrRep,
/// The key is an integer in `[0, rep.floor)` at every copy of the read.
/// The key is an integer in `[0, key_floor)` at every copy of the read.
pub(crate) in_floor: bool,
/// The floor the key is below: `rep.floor`, or a field read call's own
/// (every Array that call can read has at least as many elements).
pub(crate) key_floor: u32,
/// The read's value is a proven Number: a plain double.
pub(crate) number: bool,
/// The receiver local, for the check-mode report.
pub(crate) local: u32,
/// The receiver, for the check-mode report.
pub(crate) receiver: Receiver,
}

/// The receiver expression of a rep-keyed read.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Receiver {
Local(u32),
/// A field read (`FieldProof::array`).
Field,
}

impl Receiver {
pub(crate) fn label(self) -> String {
match self {
Receiver::Local(id) => format!("local {id}"),
Receiver::Field => "a field read".to_string(),
}
}
}

impl RepRead {
Expand Down Expand Up @@ -114,19 +141,7 @@ pub(crate) fn rep_read(ctx: &FnCtx<'_>, object: &Expr, element_offset: u32) -> O
if !tier_enabled() || typed_feedback_emission_enabled() {
return None;
}
let Expr::LocalGet(id) = object else {
return None;
};
// A local the codegen keeps in scalar slots holds no array word (R6).
if ctx.scalar_replaced_arrays.contains_key(id)
|| ctx.array_row_aliases.contains_key(id)
|| ctx.scalar_replaced.contains_key(id)
{
return None;
}
let FactsRep::Arr(rep) = crate::codegen::facts_rep::local(ctx, *id)? else {
return None;
};
let (rep, receiver) = receiver_rep(ctx, object)?;
let in_floor = element_offset != 0
&& ctx
.wpfacts
Expand All @@ -138,11 +153,78 @@ pub(crate) fn rep_read(ctx: &FnCtx<'_>, object: &Expr, element_offset: u32) -> O
Some(RepRead {
rep,
in_floor,
key_floor: rep.floor,
number,
local: *id,
receiver,
})
}

/// THE query for a receiver expression's `Arr` representation
/// (DESIGN-p4l1 §4.2): a local, parameter or module const
/// (`BindingFacts::rep`), or a field read (`FieldProof::array`).
fn receiver_rep(ctx: &FnCtx<'_>, object: &Expr) -> Option<(FactsArrRep, Receiver)> {
match object {
Expr::LocalGet(id) => {
// A local the codegen keeps in scalar slots holds no array
// word (R6).
if ctx.scalar_replaced_arrays.contains_key(id)
|| ctx.array_row_aliases.contains_key(id)
|| ctx.scalar_replaced.contains_key(id)
{
return None;
}
let FactsRep::Arr(rep) = crate::codegen::facts_rep::local(ctx, *id)? else {
return None;
};
Some((rep, Receiver::Local(*id)))
}
Expr::PropertyGet {
object: holder,
property,
..
} => {
if !crate::codegen::facts_rep::enabled() || ctx.wpfacts_off {
return None;
}
let rep = crate::expr::wpfacts_access::field_array(ctx, holder, property)?;
Some((rep, Receiver::Field))
}
_ => None,
}
}

/// The read of a field read call whose own index is in the floor
/// (`lower_call::field_read_call`), when the tier takes it: the receiver
/// expression's representation, dense, with a floor.
pub(crate) fn in_floor_rep(ctx: &FnCtx<'_>, object: &Expr) -> Option<FactsArrRep> {
if !tier_enabled() || typed_feedback_emission_enabled() || ctx.wpfacts_off {
return None;
}
let (rep, _) = receiver_rep(ctx, object)?;
(rep.dense && rep.floor > 0).then_some(rep)
}

/// Read `arr_box[idx_i32]` for a field read call whose index is an integer
/// in `[0, key_floor)` (`in_floor_rep` gave `rep`): a present own element,
/// so the load has no bound and no hole test, and the value is of
/// `rep.elem`.
pub(crate) fn lower_in_floor(
ctx: &mut FnCtx<'_>,
arr_box: &str,
idx_i32: &str,
rep: FactsArrRep,
key_floor: u32,
) -> String {
let read = RepRead {
rep,
in_floor: true,
key_floor,
number: rep.elem != crate::codegen::FactsElemRep::Boxed,
receiver: Receiver::Field,
};
lower_rep(ctx, arr_box, idx_i32, read, false)
}

/// `e` is an element read the tier loads below a dense floor and whose
/// value is a proven Number: a present own element, never `undefined` or
/// `null`, so `e ?? d` is `e`.
Expand Down Expand Up @@ -223,7 +305,7 @@ fn lower_rep(
crate::expr::store_census::bump(ctx, crate::expr::store_census::ELEM_READ_OTHER_TIER);
crate::verify_facts::consumed(
crate::verify_facts::Fact::ArrayRep,
|| serde_json::json!({ "local": read.local, "floor": r.floor, "in_floor": read.in_floor }),
|| serde_json::json!({ "receiver": read.receiver.label(), "floor": r.floor, "in_floor": read.in_floor }),
);
crate::facts_consumed::used();
if crate::verify_facts::enabled() {
Expand Down
Loading
Loading