Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 6 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1266,6 +1266,12 @@ jobs:
echo "::error::The consumption counter is not tracking the compiler."
exit 1
fi
if ! printf '%s' "$out" | grep -q "CONSUMPTION SITE NEVER EXERCISED"; then
echo "::error::No consumption site went dark with Ptr<Shape> disabled."
echo "::error::The per-site coverage gate is not tracking the compiler:"
echo "::error::two of the six recorders had never fired before it existed."
exit 1
fi
echo "Census correctly went red with PERRY_PTR_SHAPE_LOCALS=0."

- name: Upload census reports
Expand Down
25 changes: 23 additions & 2 deletions benchmarks/repsel_census/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,18 @@ of them is recorded at the site where the proof is dropped:
reproducible without the report at all: compile the workload twice, once with
`PERRY_PTR_SHAPE_LOCALS=0`, and compare the objects.

`--no-link` does **not** honour `-o`: the object goes to a per-run temp
directory and the path is printed. So capture the printed path in each arm and
compare those — comparing the `-o` arguments compares two files that were never
created.

```bash
perry compile <src> -o /tmp/x --no-link --no-cache # prints the .o path
PERRY_PTR_SHAPE_LOCALS=0 perry compile <src> -o /tmp/x --no-link --no-cache
obj() { # echo the object path this compile actually wrote
"$@" --no-link --no-cache 2>&1 | sed -n 's/^Wrote object file: //p'
}
a=$(obj perry compile <src> -o /tmp/ignored)
b=$(PERRY_PTR_SHAPE_LOCALS=0 obj perry compile <src> -o /tmp/ignored)
cmp "$a" "$b" && echo "IDENTICAL — the promotion emitted nothing"
```

Byte-identical objects mean the promotions the report counted as wins changed
Expand All @@ -76,6 +85,18 @@ nothing. `07_object_create` and `12_binary_trees` are byte-identical today.
sites** — which is why the census reports its consumption as 0 and the object
A/B alone would have been misleading.

### Per-site coverage

The consumed count is per **value**, so one working recorder is enough to mark a
value consumed and the other five could rot unnoticed. `CONSUMPTION_SITES` (in
the script) registers all six, and a site that records nothing corpus-wide fails
the run.

This is not hypothetical. When coverage was first measured, four fired and two
had **never fired on any workload here** —
`class_field_get_number.shape_proven_load` and `ptr_shape_update`. Both were
reachable; nothing reached them. `fixture_ptr_shape_sites.ts` exists to.

Only `ptr-shape` has consumption instrumentation. The other seven census keys
report *no consumption data* rather than a zero
(`CONSUMPTION_INSTRUMENTED` in the script), because "uninstrumented" and "never
Expand Down
110 changes: 88 additions & 22 deletions benchmarks/repsel_census/baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,43 @@
"int-valued-ta": 0,
"spec-abi": 1
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {
"ptr_shape_get_number": 2,
"ptr_shape_set": 1,
"ptr_shape_method": 1
}
},
{
"name": "fixture_ptr_shape_sites",
"role": "liveness",
"source": "benchmarks/repsel_census/fixtures/fixture_ptr_shape_sites.ts",
"floors": {
"ptr-shape": 1,
"ptr-shape-consumed": 1,
"ptr-numarray": 0,
"canonical-i32": 0,
"canonical-u32": 0,
"canonical-str": 0,
"int-valued-ta": 0,
"spec-abi-entry": 1,
"spec-abi-taptr-slot": 0
},
"candidates": {
"ptr-shape": 1,
"ptr-numarray": 0,
"canonical-slot": 1,
"int-valued-ta": 0,
"spec-abi": 1
},
"unconsumed_mechanisms": {},
"consumption_sites": {
"ptr_shape_get_number": 1,
"class_field_get_number.shape_proven_load": 1,
"ptr_shape_set": 1,
"ptr_shape_update": 1,
"ptr_shape_method": 1
}
},
{
"name": "fixture_ptr_numarray",
Expand All @@ -47,7 +83,8 @@
"int-valued-ta": 0,
"spec-abi": 2
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "fixture_canonical_slots",
Expand All @@ -71,7 +108,8 @@
"int-valued-ta": 0,
"spec-abi": 3
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "fixture_int_valued_ta",
Expand All @@ -95,7 +133,8 @@
"int-valued-ta": 1,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "fixture_spec_abi_taptr",
Expand All @@ -119,7 +158,8 @@
"int-valued-ta": 0,
"spec-abi": 1
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "batch",
Expand All @@ -145,6 +185,11 @@
},
"unconsumed_mechanisms": {
"module_init_context": 1
},
"consumption_sites": {
"ptr_shape_get_number": 1,
"ptr_shape_set": 2,
"class_field_get.shape_proven_load": 2
}
},
{
Expand All @@ -169,7 +214,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_02_loop_overhead",
Expand All @@ -193,7 +239,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_03_array_write",
Expand All @@ -217,7 +264,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_04_array_read",
Expand All @@ -241,7 +289,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_05_fibonacci",
Expand All @@ -265,7 +314,8 @@
"int-valued-ta": 0,
"spec-abi": 1
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_06_math_intensive",
Expand All @@ -289,7 +339,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_07_object_create",
Expand All @@ -315,7 +366,8 @@
},
"unconsumed_mechanisms": {
"scalar_replaced": 1
}
},
"consumption_sites": {}
},
{
"name": "suite_08_string_concat",
Expand All @@ -339,7 +391,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_09_method_calls",
Expand All @@ -365,6 +418,11 @@
},
"unconsumed_mechanisms": {
"module_init_context": 1
},
"consumption_sites": {
"class_field_get.shape_proven_load": 1,
"ptr_shape_get_number": 1,
"ptr_shape_set": 1
}
},
{
Expand All @@ -389,7 +447,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_11_prime_sieve",
Expand All @@ -413,7 +472,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_12_binary_trees",
Expand All @@ -439,7 +499,8 @@
},
"unconsumed_mechanisms": {
"scalar_replaced": 1
}
},
"consumption_sites": {}
},
{
"name": "suite_13_factorial",
Expand All @@ -463,7 +524,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_14_closure",
Expand All @@ -487,7 +549,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_15_mandelbrot",
Expand All @@ -511,7 +574,8 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_16_matrix_multiply",
Expand All @@ -535,7 +599,8 @@
"int-valued-ta": 0,
"spec-abi": 1
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
},
{
"name": "suite_17_loop_data_dependent",
Expand All @@ -559,8 +624,9 @@
"int-valued-ta": 0,
"spec-abi": 0
},
"unconsumed_mechanisms": {}
"unconsumed_mechanisms": {},
"consumption_sites": {}
}
],
"generated_at": "2026-07-31T04:32:15.277779Z"
"generated_at": "2026-07-31T05:15:37.361861Z"
}
71 changes: 71 additions & 0 deletions benchmarks/repsel_census/fixtures/fixture_ptr_shape_sites.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Liveness fixture for the `Ptr<Shape>` CONSUMPTION SITES (#7106 follow-up).
//
// `fixture_ptr_shape.ts` proves the census can observe a promotion. This file
// proves something narrower and, it turned out, more urgent: that each codegen
// lowering which CONSUMES a `Ptr<Shape>` proof is exercised by the corpus at
// all.
//
// When per-site coverage was first measured, four of the six recorders fired
// and two had never fired on any workload in the census:
//
// - `class_field_get_number.shape_proven_load` (expr/property_get/helpers.rs)
// -- the bare fixed-offset load for a field that is NUMERIC-proven, as
// opposed to merely shape-proven. Needs a class whose every reachable
// store to the field is proven number-producing.
// - `ptr_shape_update` (expr/instance_misc1.rs) -- `o.f++` lowered to
// load/fadd/store at a fixed offset, with no by-name runtime call.
//
// Both are reachable. Nothing exercised them, so a break in either would have
// been invisible: the census counts promoted VALUES, and every value in the
// corpus was already consumed at some other site.
//
// What this program has to get right, all at once:
//
// 1. `c` must satisfy every `collectors/ptr_shape.rs` rule (see the sibling
// fixture's header for the list) -- so: bound by one `Let` from a `new`,
// never reassigned, captured, passed, returned or aliased.
// 2. Both fields must be NUMERIC-proven, which is what selects the
// `class_field_get_number` load over the plain shape-proven one. Every
// store is an integer literal or a proven-number expression.
// 3. `c` must survive SCALAR REPLACEMENT (#7115) -- otherwise the object is
// deleted, no access site is reached, and the promotion is counted but
// consumed nowhere. This is the delicate part. `c.v = c.v + 1` is what
// does it: a plain in-loop field store defeats scalar replacement, and
// dropping it takes this fixture to zero consumed sites. `c.v++` ALONE
// does not -- an earlier draft with only the update was scalar-replaced
// and exercised nothing.
// 4. `mix()` must be too complex for `simple_scalar_method_summary`, so the
// method call does not re-enable scalar replacement, and it supplies the
// `ptr_shape_method` site.
//
// Do not "tidy" this file. In particular do not fold `c.v = c.v + 1` into the
// `c.w++` update, and do not simplify `mix()`.

class Ctr {
v: number;
w: number;
constructor() {
this.v = 0;
this.w = 1;
}
mix(): number {
return this.v * this.v + this.w * this.w;
}
}

function counted(n: number): number {
let total = 0;
for (let i = 0; i < n; i++) {
const c = new Ctr();
// Plain field store: defeats scalar replacement (see note 3).
c.v = c.v + 1;
// Field update: the `ptr_shape_update` site.
c.w++;
// Numeric-proven reads: the `class_field_get_number` site.
// Method call on a proven receiver: the `ptr_shape_method` site.
total = total + c.v * c.w + c.mix();
}
return total;
}

console.log("ptr_shape_sites:" + counted(4));
Loading
Loading