You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
crates/perry-codegen/src/codegen/param_guard.rs::build_named refuses class types outright, so
a parameter annotated with a class never gets a runtime descriptor and never enters a
proof-bearing $spec_ clone:
}elseifself.classes.contains_key(name) && self.class_ids.contains_key(name){// Class identity alone cannot prove mutable field values, while// compact instances do not expose the ordinary `keys_array`// needed for read-only field validation. Keep class parameters on// the generic path until a layout-aware field guard exists.returnNone;
This is a deliberate scope boundary from #8094, not an inherent limit, and it is measurable.
What it costs, measured
In the tree benchmark (/private/tmp/perry-current-sweep-artifacts/sources/tree.ts) the whole
program is three functions:
functionbuild(depth: number): Treefunctioncount(t: Tree): number// the hot recursive one — t.left, t.rightfunctionmain(): void
count is the hot function and does all the field reads, and its only parameter is class-typed,
so it is refused. The Tree constructor is refused at one remove: its parameters are Tree | null, and Type::Union maps every variant through build_type, so the Named("Tree")
variant poisons the union. build(depth: number) ends up the only guardable parameter in the
program.
Against the branch's own base 601a02d23, tree recovered 55.1% where an unsound
annotation-trusting control recovered 72.0% — the only row in the sweep where the guarded
design visibly left recovery on the table; every other row matched the control to within half
a percent.
The runtime half is already written, and is currently dead code
crates/perry-runtime/src/param_type_guard.rs:399 already implements the class-identity check:
if class_id != 0
&& !crate::object::class_chain_reaches((*object).class_id, class_id){returnfalse;}
Codegen always emits class_id: 0 (both GuardNode::Object constructions pass class_id: None),
so this branch has never executed. Per the repo's own kill-policy — an unexercised mode is a
configuration nobody has verified — it should either get a caller or be deleted.
What a fix has to answer
Field validation vs. compact instances. The OP_OBJECT arm validates each descriptor
field through own_data_field, which reads keys_array; a compact class instance does not
expose one. An identity-only descriptor (class_id: Some(id), fields: vec![]) sidesteps this,
but then the guard proves class identity and nothing about field values.
Subclasses.class_chain_reaches admits a subclass; confirm subclass layout preserves the
parent's field indices.
delete. A delete mints a shape word and zeroes class_id, so the guard fails and the
generic path is taken. That looks like it falls the right way, but should be pinned by a test.
Deliberately kept out of #8094: it widens the guard surface across every class-typed parameter
in every program, and that PR needs to be correct first.
Summary
crates/perry-codegen/src/codegen/param_guard.rs::build_namedrefuses class types outright, soa parameter annotated with a class never gets a runtime descriptor and never enters a
proof-bearing
$spec_clone:This is a deliberate scope boundary from #8094, not an inherent limit, and it is measurable.
What it costs, measured
In the
treebenchmark (/private/tmp/perry-current-sweep-artifacts/sources/tree.ts) the wholeprogram is three functions:
countis the hot function and does all the field reads, and its only parameter is class-typed,so it is refused. The
Treeconstructor is refused at one remove: its parameters areTree | null, andType::Unionmaps every variant throughbuild_type, so theNamed("Tree")variant poisons the union.
build(depth: number)ends up the only guardable parameter in theprogram.
Against the branch's own base
601a02d23,treerecovered 55.1% where an unsoundannotation-trusting control recovered 72.0% — the only row in the sweep where the guarded
design visibly left recovery on the table; every other row matched the control to within half
a percent.
The runtime half is already written, and is currently dead code
crates/perry-runtime/src/param_type_guard.rs:399already implements the class-identity check:Codegen always emits
class_id: 0(bothGuardNode::Objectconstructions passclass_id: None),so this branch has never executed. Per the repo's own kill-policy — an unexercised mode is a
configuration nobody has verified — it should either get a caller or be deleted.
What a fix has to answer
OP_OBJECTarm validates each descriptorfield through
own_data_field, which readskeys_array; a compact class instance does notexpose one. An identity-only descriptor (
class_id: Some(id), fields: vec![]) sidesteps this,but then the guard proves class identity and nothing about field values.
raw-
f64field read are sound given identity. Trusting a field's declared type is not —that is the fix(codegen): require runtime evidence for local binding types #8033 hazard.
class_chain_reachesadmits a subclass; confirm subclass layout preserves theparent's field indices.
delete. Adeletemints a shape word and zeroesclass_id, so the guard fails and thegeneric path is taken. That looks like it falls the right way, but should be pinned by a test.
whatever proof it gets cannot survive a call — see the aliasing fix in perf(codegen): guarded ordinary-parameter specialization #8094.
Deliberately kept out of #8094: it widens the guard surface across every class-typed parameter
in every program, and that PR needs to be correct first.
Refs #8079, #8094.