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
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,16 @@ jobs:
# workload it exists for — and that only 3 of 17 suite benchmarks promote a
# single shape local each. Nothing in CI could have told anyone that.
#
# What it counts is CONSUMPTION, not selection. An analysis proving a value
# and codegen emitting something for it are different events, and #7107 found
# by reading IR that `batch.ts` proves two `Ptr<Shape>` values and applies
# one: `totals` is proven, reported as a win, and keeps the guarded diamond at
# every access site. `07_object_create` and `12_binary_trees` are worse -- they
# report a promotion each while `PERRY_PTR_SHAPE_LOCALS=0` produces a
# byte-identical object. So `ptr-shape` and `ptr-shape-consumed` are separate
# columns with separate floors, and every unconsumed promotion must name the
# mechanism that ate it (#7109 / #7115).
#
# Why it can fail (CLAUDE.md, "Four ways a gate can be unable to fail"):
# floors alone would not be enough, because the honest floor for
# `Ptr<Shape>` on real code is zero today and a zero floor can never go red.
Expand Down Expand Up @@ -1246,6 +1256,16 @@ jobs:
echo "::error::but not for the reason that proves its subject was live."
exit 1
fi
# The consumed column is a SEPARATE counter fed from separate codegen
# sites, so it needs its own liveness assertion. A `ptr-shape-consumed`
# that stayed at its floor while `ptr-shape` went to zero would be a
# number disconnected from the compiler -- and it is the column the
# performance claims now rest on.
if ! printf '%s' "$out" | grep -q "fixture_ptr_shape: ptr-shape-consumed promoted 0"; then
echo "::error::ptr-shape went to zero but ptr-shape-consumed did not."
echo "::error::The consumption counter is not tracking the compiler."
exit 1
fi
echo "Census correctly went red with PERRY_PTR_SHAPE_LOCALS=0."

- name: Upload census reports
Expand Down
73 changes: 72 additions & 1 deletion benchmarks/repsel_census/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,58 @@ produced a byte-identical binary.

The census makes that a standing, visible, gated number instead of a discovery.

## Selected is not consumed

The census counts **consumed** promotions, not selected ones, and the two
columns are separate on purpose.

`select()` fires when an analysis *proves* a value. Whether codegen then emits
anything different for it is a different question, and for `Ptr<Shape>` the
answer is usually no:

| workload | `ptr-shape` | `ptr-shape-consumed` | mechanism |
|---|---|---|---|
| `fixture_ptr_shape` | 1 | 1 | — |
| `batch` | 2 | 1 | `module_init_context` |
| `suite_07_object_create` | 1 | 0 | `scalar_replaced` |
| `suite_09_method_calls` | 1 | 0 | `module_init_context` |
| `suite_12_binary_trees` | 1 | 0 | `scalar_replaced` |

Six proven, two applied. A promotion goes unconsumed three ways, and every one
of them is recorded at the site where the proof is dropped:

1. **`module_init_context`** (#7109) — `codegen/entry.rs` sets
`repsel_context_allows_canonical_i32: false` for module-init and
program-entry bodies, and `FnCtx::ptr_shape_receiver_fact` returns `None` for
the whole body when that flag is clear. Every access site falls back to the
guarded diamond.
2. **`async_body` / `generator_body`** (#6328) — the same flag, cleared for a
different reason.
3. **`scalar_replaced`** (#7115) — `collectors/escape_news.rs` deleted the
object outright. This one is the *better* outcome, not a defect; it is listed
because "scalar-replaced" and "promoted but wasted" used to render
identically and mean opposite things.

**Ground truth is the emitted IR, never a counter.** Every verdict above is
reproducible without the report at all: compile the workload twice, once with
`PERRY_PTR_SHAPE_LOCALS=0`, and compare the objects.

```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
```
Comment on lines +68 to +71

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use separate output paths for the IR comparison.

Both commands write to /tmp/x. The second compile overwrites the first object. The documented procedure cannot compare the default and disabled builds. Use separate output paths and compare them.

Proposed documentation fix
-perry compile <src> -o /tmp/x --no-link --no-cache
-PERRY_PTR_SHAPE_LOCALS=0 perry compile <src> -o /tmp/x --no-link --no-cache
+perry compile <src> -o /tmp/x-default --no-link --no-cache
+PERRY_PTR_SHAPE_LOCALS=0 perry compile <src> -o /tmp/x-disabled --no-link --no-cache
+cmp /tmp/x-default /tmp/x-disabled
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```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
```
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@benchmarks/repsel_census/README.md` around lines 68 - 71, Update the README
compile examples so the default and PERRY_PTR_SHAPE_LOCALS=0 builds use distinct
output paths, preserving both generated object files for comparison.


Byte-identical objects mean the promotions the report counted as wins changed
nothing. `07_object_create` and `12_binary_trees` are byte-identical today.
`09_method_calls` differs, but only by two `__pshape` clones with **zero call
sites** — which is why the census reports its consumption as 0 and the object
A/B alone would have been misleading.

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
applied" are exactly the pair this census exists to keep apart.

## How it cannot quietly pass

Read CLAUDE.md, "★ Four ways a gate can be unable to fail". The fourth applies
Expand Down Expand Up @@ -74,11 +126,30 @@ Three separate mechanisms, in increasing order of paranoia:
Only `suite_01_startup` is allowlisted: it is a lone `console.log`, with no
bindings for any analysis to consider.

5. **Consumption coherence checks.** `consumed` may never exceed `selected` for
the same representation — they must describe one population, and Phase 5a's
proven-`this` receiver is consumed without ever being selected, so folding it
in would silently break that. One value consumed at five access sites counts
once. And a workload with wasted promotions must NAME at least one mechanism.

That last one is what makes deleting a drop-recorder visible. Without it the
consumed column would not move, every floor would still pass, and the census
would go green having lost the only part of the finding that says *why* —
CLAUDE.md failure mode 4, one level in.

Sabotage-verified in both directions. Each of `PERRY_PTR_SHAPE_LOCALS=0`,
`PERRY_PTR_NUMARRAY_LOCALS=0`, `PERRY_CANONICAL_I32_LOCALS=0`,
`PERRY_CANONICAL_STR_LOCALS=0` and `PERRY_INT_VALUED_LOCALS=0` turns the census
red; the default build is green. CI re-runs the first of those on every job so
the property is checked, not just claimed once.
the property is checked, not just claimed once, and additionally asserts that
`ptr-shape-consumed` goes to zero with it — the consumed column is fed from
separate codegen sites and needs its own liveness proof.

The consumption machinery was sabotage-verified the same way: dropping
`outcome` from `Entry::dedup_key`, removing all six consumption recorders,
removing either mechanism recorder, counting per access site, folding
proven-`this` consumption into the local column, and deleting the consumed
liveness minimum each turn the gate red.

## Editing the fixtures

Expand Down
Loading
Loading