Latest Results
Remove quadratic scans from the JIT prepass analyses (#1908)
* Remove quadratic scans from the JIT prepass analyses
- `front::iter_next`: index each backward walk's producers, inputarg slots
and incoming links once per call instead of rescanning every block.
- `majit-charon-reader`: decode each statement's `StmtKind` and each block's
`TermKind` once behind a `OnceLock` instead of re-deserializing a cloned
`serde_json::Value` per query.
- `front::mir`: compute the accumulator facts once per lowered body; resolve
trait associated types through a per-LLBC index.
- `front::semantic`: bucket `StructFieldRegistry` keys by their last path
segment so a suffix lookup no longer scans every key.
- `model`: compute block dominators lazily, and as an immediate-dominator
tree rather than a `Vec<HashSet<usize>>` seeded with every block; share the
phi-column scan in `remove_duplicate_inputargs`.
- `codewriter::call`: cache each graph's local read/write scan and replay the
recorded descriptor mints in the original DFS order, so the effect sets and
the minted descriptor indices are unchanged.
Measured on the pyre-dynasm prepass: build-script CPU 4:25 -> 1:15, peak
memory 11.4 GB -> 7.3 GB. The generated artefacts stay byte-identical apart
from the files that embed host addresses.
Assisted-by: Claude Opus 5 (1M context)
Assisted-by: Grok 4.6
* Invalidate the prepass caches and drop redundant copies
- `fielddescrof_memo`: clear on `set_struct_fields`, `set_known_struct_names`
and `set_struct_layout`; a hit also rechecks `struct_id_for_name`,
`canonical_struct_name` and the field's immutability rank. Key the memo by
nested maps so a hit borrows `&str` instead of allocating two `String`s,
and move the footprint with `mem::take`.
- `readwrite_replay`: drop a path's entry when its graph is registered or
its call target is stamped, and clear it when
`materialize_deferred_indirect_families` or
`replace_force_virtualizable_with_call` rewrite graphs in place.
- Read/write replay: keep only the producers `producer_array_identity`
reads, as a `ValueProducer` enum, and read the cached map directly
instead of rebuilding a borrowed copy on every visit.
- `ProducerIndex::root_uncached`: borrow the incoming link list.
- `front::iter_next`: fold the unindexed wrappers that have no outside
caller into the functions that take a `BackEdges`.
Tests: fielddescrof_keyed_sees_layout_registered_after_first_mint,
readwrite_reregistered_graph_drops_cached_effects,
readwrite_materialized_indirect_family_replaces_cached_top.
Assisted-by: Claude Opus 5.5 (1M context)
Assisted-by: Grok 4.7
* Address review findings on the prepass caches
- `remove_duplicate_inputargs`: record the epoch a block observed before
its own unions advance it, so a block whose unions make its earlier
columns equal is walked again, as `remove_identical_vars_SSA` does
while `progress` is set.
- `front::iter_next` `BackEdges`: number the variable ids present in the
graph densely instead of sizing tables by the global id span.
- `readwrite_replay`: key the cache by `GraphKey`, so alias paths of one
graph share an entry and a mutation through any alias drops it.
- `CallControl::struct_layouts`: private; writes go through
`set_struct_layout`, which clears `fielddescrof_memo`.
- `StructFieldRegistry::fields`: a `FieldRows` map that keeps an xor
fingerprint of its key set, so the path index rebuilds after any key
insert or remove, including one that restores the previous length.
- `majit-charon-reader`: document the trait-assoc index.
Tests: remove_duplicate_inputargs_rewalks_block_after_its_own_unions,
remove_duplicate_inputargs_rewalks_back_edge_phi,
field_path_index_rebuilds_after_same_len_key_replacement,
readwrite_replay_invalidates_every_alias_of_a_mutated_graph.
Assisted-by: Claude Opus 5.5 (1M context)
Assisted-by: Grok 4.7
* Serialize FieldRows as its inner map without cloning it
Assisted-by: Claude Opus 5.5 (1M context) Unblock main's build, wire the wasm JIT knobs, and refresh the skip-subject ratchet (#1871)
* jit: resolve the execution-changing JIT knobs through a supplied environment
`wasm32-unknown-unknown` has a permanently empty `std::env`, so a knob the
guest reads through it is unset no matter what the host was configured
with, and an A/B run through such a knob measures the same build twice.
`majit_metainterp::jit_env` carries the names and the table:
PYRE_NO_JIT, PYRE_JIT, MAJIT_NO_BRIDGE, PYRE_NO_JD1, PYRE_JD1,
PYRE_JD1_NO_ENTER, PYRE_JD1_THRESHOLD, MAJIT_BRIDGE_BAIL. Each read site
moves to `jit_env::{env_var, env_var_os}`, which consults the supplied
table only when `std::env` misses; every OnceLock, parse and default is
unchanged. `pyre_jit_env_names` / `pyre_set_jit_env` export the list and
receive the values, and both hosts forward them -- the wasmtime host had
no such block at all.
The names also join `warn_inert_guest_env`'s HOST_HANDLED so a knob that
now works is no longer reported as inert. PYRE_JD1_DEBUG, PYRE_JD1_DUMP,
PYRE_PORTAL_METATRACE and the *_DIAG / *_CENSUS / *_PROBE / *_DUMP names
stay inert: they change what is printed, not what runs.
`gate_triage_complete`'s reader scan learns the seam: its `READ_FORMS`
matched `env::var` and four other spellings, so moving six live gates
behind `jit_env::{env_var, env_var_os}` made them read but reported
unread. `env_var` joins the list the way `read_uint_from_env` did, bare
because the call sites spell it both imported and qualified.
Assisted-by: Grok
Assisted-by: Claude Opus 5
* jit: dump bytecode for whichever function the dump switch first sees
The dump was gated on the name `fannkuch` and followed by a fixed list of
program counters, so it answered one benchmark and nothing else.
Assisted-by: Claude Opus 5
* rtyper: accept a fixed-size list repr in an _immutable_fields_ array rank
`rclass.py _parse_field_list` tests `isinstance(r, AbstractBaseListRepr)`,
and `rlist.py AbstractFixedSizeListRepr` inherits that base, so a
never-resized list is accepted upstream. `InstanceRepr` here demanded
exactly `ListRepr` and rejected `Ptr(GcArray)`, which left
`pyopcode::BranchOpcodeHandler::enter_branch_truth` skipped to the legacy
walker.
Assisted-by: Grok
* cpyext: gate the PyList_* entry points on isinstance of list
`listobject.py PyList_Size` applies `PyList_Check`, which accepts a list
subclass; the gate here was an exact type test. A `list` subclass
instance is still a `W_ListObject`, unlike a `dict` subclass, so the
object itself remains the operand.
Assisted-by: Grok
* cpyext: size a synthesized type mirror from its declared layout
A type with no module-supplied basicsize but a declared layout in
`cpython_type_layout` fell through to the bare header, which is under the
struct an extension declared. Read the declared size before that
fallback, and compute it while `tp_name` is still null so
`declared_type_layout` does not read the linked-but-unfilled mirror as a
C declaration.
Assisted-by: Grok
* pyrex: compile PYTHONSTARTUP through the codegen-warning path
The script and `-c` paths compile through
`compile_with_codegen_warnings`, so an invalid escape is a
SyntaxWarning; the startup file went through
`compile_source_named_by_bytes` and raised none. It now takes the same
path, keeps the filename bytes that name the unit, and routes a compile
error through `compile_err_to_syntax_error` with the same filename.
Assisted-by: Grok
* jit-trace: resume a VableEscape walk without replay once writes are applied
`virtualizable.py write_boxes` has no undo once the vable is forced, so a
walk that already applied heap effects cannot take the legacy replay --
replaying the traced region would execute those writes a second time.
Where the rewind commit was declined and `fbw_executed_effect_count()` is
non-zero, keep the flushed frame and take the `AfterApplied` resume,
which is always provable and so always commits.
Assisted-by: Grok
Assisted-by: Claude Opus 5
* jit-trace: census the portal frame-tracer decline
`PortalFrameTracerArmed` was the one decline on this path with no census
row, so a run that took it read as a run with no declines.
Assisted-by: Grok
* jit-trace: name the inline-call family in the jd1 portal assertion
`3442e431312` rewrote `unpackiterable_portal` to pin through
`pin_roots(&[w_iterator, items])` instead of two `pin_root` calls, so the
`inline_call_r_r` the test looked for -- `pin_root`, ref to ref -- is no
longer in the body. The one inline call left is `drain_append_at`, spelled
`inline_call_ir_v/dIR`.
Every `inline_call_*` spelling carries the descr as its leading 2-byte
`d` operand (`dR>r`, `dIR`, `dIRF>f`), which is the property the test
exists to check, so it now finds the call by family.
Assisted-by: Claude Opus 5
* gc: size a forwarded candidate from its copy in describe_nearest_header
A forwarded object's first payload word is the forwarding address, so its
own header no longer sizes it. Follow the copy, and keep scanning unless
that extent actually covers the address being described.
Assisted-by: Grok
* object: resolve the root-stack cell per pin instead of caching it on the scope
`RootScope` held a `*const RootStack` resolved once at `new`. Resolving
it in each `pin_root` / `get` removes the cached raw pointer from the
scope and leaves `save_point` as its only state.
Assisted-by: Grok
* backend: name cls_of_gcref for the raw gcref it reads
The argument is a raw reference word, not a box, and the body reads
offset 0 of it. `model.py cpu.cls_of_box` takes a box and stays cited on
`Cpu::cls_of_box`, which does.
Assisted-by: Grok
* scripts: fail the skip-subject ratchet on a moved corpus
A rise measured over a corpus the baseline does not name returned 0, so
additions on a re-extracted corpus were reported and never gated -- the
allowance that let +86 unattributed names through. It now fails and names
`--update` as the way to absorb the move, once the additions are
attributed to it.
Assisted-by: Grok
* scripts: give each worktree its own charon target directory
Two checkouts compiling the same crate at different revisions into one
incremental store make the extraction fail with phantom E0425 errors
against files the worktree does not have. The default is now keyed by the
worktree basename; an explicit CHARON_TARGET_DIR still wins.
Assisted-by: Grok
* rtyper: refresh the skip-subject baseline onto the corpus it is read against
The darwin baseline named corpus a64a26a63d9726e4, 39 commits old; this
run reads 4cc0a9a23ef4f39f. +221 / -94, and one reclassification.
Attribution: every addition is `two-phase-never-a-subject`, the class
that says the prepass closure never selected the graph, which a
re-extraction produces by the hundred -- `codec_engine` alone accounts
for 13 and the baseline holds zero of its rows because it predates
`095f200bd03`. `two-phase-rtype-skipped`, the class that means the graph
WAS a subject and the real rtyper refused it, is 3:
`_check_len_result`, `struct::lookup_fmt`, `unicodedata::normalize_form`.
The baseline recorded 0 there only because it predates the split of one
message into two. `_check_len_result` is the reclassification, not an
addition, and `pyopcode::BranchOpcodeHandler::enter_branch_truth` left
the set with the `_immutable_fields_` fix in this branch.
The reading is reproducible: two censuses of the same tree are
byte-identical (8680 `[decline]` lines, `diff` empty), and
`PYRE_CODEGEN_DETERMINISM_CHECK=cache` reports all 15 reproducible
outputs identical to the stored entry.
Assisted-by: Claude Opus 5
* rtyper: pin what the string/instance duality repairs cannot be
`__strlen`'s argument annotates as SomeInstance(pyobject::PyObject) and
getrepr gives InstanceRepr, which has no rtype_len, so routing it through
`len` does not reach AbstractStringRepr.rtype_len -> ll_strlen; the
annotator's len_SomeInstance goes to getattr(__len__), a code-point count
rather than a WTF-8 byte count.
translate_op_eq selects the pair from hop.args_r before hop.inputargs
consults convert_from_to, so an (InstanceRepr, StringRepr) conversion pair
does not reach pairtype(StringRepr, StringRepr).rtype_eq.
bookkeeper's intern already subclasses payload-less enum variants, and
union over them lands on the Code base through ClassDef::commonbase.
Tests only; no behaviour change.
Assisted-by: Grok 4.6
Assisted-by: Claude Opus 5
* front: fold cross-crate consts, register the f64 llexternals, project the wtf8 cast as a string
`discover_foldable_const_lits` harvests globals whose initializer folds in
the artefact that owns them and registers them by full name_path;
`const_eval_global` consults that set after the local-body and core::num
lanes fail, so a const read from a crate whose init body is Foreign folds to
its literal. An initializer containing a Call stays unharvested except
`core::mem::size_of` / `align_of`, which fold through
`layout::target_word_size` (CARGO_CFG_TARGET_POINTER_WIDTH), not the host
width, and compose with the surrounding const arithmetic. A name_path that
is not unique inside one artefact is not harvested and no winner is picked;
artefacts disagreeing on a unique path assert.
`F64_METHOD_LLEXTERNALS` is the single table behind both the CallRegistry
registration of `["ll_math", name]` and the extfuncregistry HostObject
entries, so the crate path the front end emits for an f64 inherent method
resolves. Declared results are Float; the wrapper `Result` on ceil, sqrt,
log, pow, fmod, hypot and ldexp is the error path, not the annotator result
type.
The `w_str_get_wtf8` / `__cast_instance_intrinsic` destination projects as
SomeString rather than inheriting the receiver's PyObject instance type.
Measured on a fresh extraction: skip-subject ratchet 1980 -> 1944 subjects,
distinct census leaves 277 -> 250, ll_math leaves 68 -> 0, const/static
leaves 267 -> 194. The ratchet still FAILs: 12 additions, 9 of them graphs
that only became reachable now (4 blocked on an Acquire atomic load, 5
relocated onto from_raw_parts / from_size_align) and 3 memoryview graphs
that now reach phase B. Full suite 9908 passed / 0 failed; dynasm 544/544.
Assisted-by: Grok 4.6
Assisted-by: Claude Opus 5
* interpreter: drop the duplicate HOPELESS re-export
origin/main already re-exports gateway::HOPELESS as a dedicated
item so the pyre_function expansion folds; the skip-subject commit
also named it in the gateway glob, which rustc rejects as E0252.
Assisted-by: Grok 4.6
* style: reflow three files rustfmt had not been run over
`cargo fmt --all` rewrites an argument list in `annotator/builtin.rs`, a
`pub use` group in `pyre-interpreter/src/lib.rs`, and one `let` binding in
`pyrex/src/repl.rs`. No behaviour change.
Assisted-by: Claude Opus 5 (1M context)
* rtyper: route `__strlen` through `StringRepr.rtype_len`
`translate_op`'s `is_len_op` accepts `__strlen` alongside `__len`. The
receiver at `w_str_get_wtf8` sites is the dest of
`__cast_instance_intrinsic` with a `Wtf8` root and `ValueType::Str`, so it
annotates `SomeString` and dispatches `StringRepr.rtype_len` ->
`ll_strlen`, which reads the rstr `STR` `chars` length. `op_canraise`
records that the rewritten op raises nothing. The Skip spine keeps its
residual `__strlen` arm in `jtransform.rs`.
Removes 25 `__strlen` leaf events from the prepass decline census.
Assisted-by: Claude Opus 5 (1M context)
* front+rtyper: declare word-only Acquire atomic readers as llexternals
`front::mir::collect_atomic_load_declined_fun_decls` harvests the function
declarations the MIR loop declined for a non-Relaxed atomic load.
`lltypesystem::module::ll_extaccessor` admits only a verified word-only
reader -- zero arguments, unsigned scalar result, ordered-load decline
reason, no translatable body -- and
`cutover::register_atomic_load_llexternals` declares each as a
`register_external` CallRegistry stub through
`extfuncregistry::register_atomic_load_accessor_externals`, the same
non-overwriting, annotator-only carrier `register_ll_math_llexternals`
uses. `ExternalAnnotation::Unsigned` carries the `SomeInteger(unsigned)`
knowntype so a `u32` result does not collapse to signed `Int`. The stub
annotation is non-const, so the residual calls the function rather than
baking a translation-time value: the ids these four readers return are
published at run time by `pyre-jit::eval` `build_gc`.
`jit_trace_fnaddrs` publishes the four addresses through `pa0`, and
`jit_static_ref_addrs` gains `stack_check::PYRE_STACKTOOBIG`.
An Acquire load rejects the whole enclosing function body, so
`pyre_object::lowlevel_string::lowlevel_str_gc_type_id` stood at 94 events
in the prepass decline census. It is now absent, and the graphs behind it
reach `alloc::Layout::from_size_align`, which goes from 16 to 110.
Assisted-by: Claude Opus 5 (1M context)
* front: lower four more std shapes, and gate the identity alias on register bank
New passes:
- `std_identity` lowers transparent one-field std wrappers (`Box`, `Ref`,
`MutexGuard`, `Atomic*`) and machine-word `Clone` / `Default` to
`same_as` / a typed zero. The identity arm fires only when receiver and
destination share a register bank (`flatten.py getkind`): an alias across
banks spells `int_same_as/r>i` and its consumer `int_add/ir>i`, opnames
`blackhole.rs` leaves unwired as kind-flow bugs. A borrow carries no
representation of its own, so the receiver's bank is the pointee's.
`Cell` is Opaque in the LLBC and so reads `Ref` against a scalar
destination; `Cell::get` is a load across banks there and keeps its
residual call.
- `from_raw_parts` aliases a `slice::from_raw_parts{,_mut}` over a
length-prefixed GC string header to that header, the same identity the
name-gated `bytes_block_chars` / `as_slice` folds already emit.
- `saturating_add` splits the word-sized clamp into
`sum = a + b; if uint_lt(sum, a) { MAX } else { sum }`.
- `iter_adapter` rewrites `map(it, f).collect()` at the construction site,
where `f` is still a concrete closure ADT, into `Vec::new` + `next` +
`call_once` + `push`, before the range divert and the `next` diamond run.
Extended: `slice_get`, `from_size_align`, `checked_arith`,
`checked_arith_uint`, `iter_next`. `iter_next` now replaces the residual
`next()` result itself rather than the peeled scrutinee, so a block whose
`next()` carries a trailing `__cast_instance_intrinsic` recast no longer
keeps `slice::iter::Iter::next` alongside the native op.
`resolve_place`'s Global lane chain gains `bitflags_trait_empty_const`;
`primitive_float_const` answers the other six `f64` associated constants;
`static_key_segments` spells a disambiguated Ident `name#n` so
function-local statics stop colliding on a shared leaf.
Retires two predicates left without a production caller:
`checked_arith_uint_atom_is_word_sized` (copied verbatim into
`front::checked_arith_uint::is_word_sized_uint_atom`, its test moved) and
`std_identity::is_mem_replace_target`.
With the preceding commit the prepass decline census goes from 241 to 235
leaf species and 1963 to 1936 Phase-A failures, eliminating nine families
and 156 leaf events. The rtyper skip-subject set goes from 1933 to 1904:
29 names leave it and none join.
Assisted-by: Claude Opus 5 (1M context)
* rtyper: give `__discriminant` the result repr's lltype
`InstanceRepr.rtype_getattr`'s nullable-instance arm lowered the
`__discriminant` of a `can_be_none` instance with a fixed
`cast_bool_to_int` / `Signed`, while a switch over the tags annotates
`SomeInteger(unsigned)`. The two disagreed, so the hop's result never
bound and `bindingrepr` raised `KeyError` on the unbound arg. The op and
its result lltype now follow `hop.r_result`; a discriminant that never
gained knowntypedata still reads `Signed`.
Phase-B rtype failures go from 10 to 3 -- the seven that leave are
`pyre_object::bufferview::<Impl>::{backing,itemsize,ndim,w_obj}` and
`pyre_object::memoryview::w_memoryview_{backing,itemsize,ndim}` -- and
`two-phase-rtype-skipped` goes from 5 to 2.
`run_phase_b_rtype_isolated` also emits, under `MAJIT_RTYPER_VERBOSE` only
and only for the graph that failed, the failing block's exits and a
per-block annotated / blocked / missing census. A `bindingrepr` panic
carries only the unbound Variable, so the producer of an unbound arg
otherwise cannot be located without a second instrumented build.
Assisted-by: Claude Opus 5 (1M context)
* majit: refresh the rtyper skip-subject baseline
`scripts/check-rtyper-skip-subjects.py --update` on the current corpus:
1904 subjects in two classes, down from 1980. 110 names leave the list and
33 join.
Every one of the 33 was already declining at the commit this branch starts
from, measured by parking the working tree back to that commit and taking a
census there: none of them is a graph this branch stopped lowering. Against
that same reference the gate's accept set goes from 1650 to 1665 subjects
and its decline set from 1933 to 1904, with no subject moving from accept to
decline.
Assisted-by: Claude Opus 5 (1M context)
* front: skip a scoped Result site whose arm constant folding already deleted
`we_are_jitted()` lowers to `ConstBool(true)`, and
`simplify_lowered_graph`'s `fold_constant_exitswitch` +
`clear_unreachable_blocks` then drop the interpreter arm. Scoped Result
call sites are collected during body lowering, before that runs, so a call
in the deleted arm survives only as a collected result var with no producer
block -- and the first such var declined the WHOLE graph to a residual.
`create_exception_handling` (`exceptiontransform.py`) walks `iterblocks()`,
so a call in an unreachable block was never its subject. A collected var
that has no producer and no use anywhere in the graph is that deleted arm
and is skipped. A var that is still used without a producer is an unproven
shape and declines the graph as before.
Three functions in the tree carried this, all of them
`if we_are_jitted()` dispatchers: `baseobjspace::finditem_str_named`,
`eval::<Impl>::load_attr_cached` and `eval::<Impl>::store_attr_cached`.
`finditem_str_named` had no graph at all, which made it the largest leaf in
the prepass decline census at 218 events -- 18% of the total. That family
is now gone: 1213 leaf events over 235 species become 1118 over 237, and
the 218 graphs behind it reach later walls (`Layout::from_size_align` goes
from 110 to 208). `call-site-declined-to-residual` goes from 60 to 54; the
`drain-match-fusion-declined` and `option-ok-or-else-try-declined` tallies
are unchanged at 4433 and 147.
The gate's accept set is unchanged at 1665 subjects with no subject moving
from accept to decline. Its decline set gains one: `finditem_str_named`
itself, which is a prepass subject for the first time and stops at
`UnionError: String ∪ Instance(dictmultiobject::StrLookupKey)`.
Assisted-by: Grok Code (delegated), Claude Opus 5 (1M context)
* front: follow SSA copies and fold primitive widths at `Layout::from_size_align`
Lowering the four GC allocation callers with `MAJIT_MIR_FRONTEND_DEBUG` names
two shapes the rewriter could not take:
- `object_array::try_items_block_layout` and
`rlib::rlist::try_typed_items_block_layout` use `.ok()`, and their align
argument is already a `ConstInt(8)` -- but in a predecessor block, reaching
the site as an SSA copy. The rewriter only looked for a `ConstInt` in the
site's own block.
- `lowlevel_string::bh_alloc_lowlevel_string` and
`unicodeobject::alloc_raw_utf8_payload` use `.expect()`, whose receiver
arrives as a block inputarg rather than as the predecessor's result, and
whose align is an unfolded `align_of::<usize>()`.
So: the align constant is resolved through `resolve_to_producer_op` to a
unique predecessor `ConstInt` / `ConstUInt` -- a merge point or a residual
call still declines; `.expect()` gets the same predecessor threading `.ok()`
already had, aliasing through the Q inputarg (both sides are `Ref`, so the
register banks agree); and `size_align_const_from_tyexpr` folds a primitive
width through `primitive_size_align`, the lane the NamedConst-initializer form
already used, so an inline `align_of::<usize>()` folds as well. A pointer or
tuple argument, and an unresolved layout, stay residual.
`alloc::layout::Layout::from_size_align` leaves the prepass decline census
entirely: 208 leaf events become 0, and the census goes from 1118 events over
237 species to 1107 over 236. The graphs behind it advance to the next
residual in the same allocation spine (`core::slice::raw::from_raw_parts` goes
from 17 to 205), so the Phase-A failure total is unchanged at 1940.
The gate's accept set is unchanged at 1665 subjects and its decline set at
1905, with no subject moving from accept to decline.
Assisted-by: Grok Code (delegated), Claude Opus 5 (1M context)
* front: match the real STR-header shape at `slice::from_raw_parts`
The pass aimed at the length-prefixed GC string header already, but only
looked for a `ConstInt` in the site's own block, so it matched its synthetic
test and none of the graphs in the tree. The shape `unicodeobject::
utf8_payload_bytes` actually presents is `add(header, ConstUInt(16))` for the
pointer and `bh_lowlevel_string_len(cast_ptr_to_int(header))` for the length,
with only `(len, chars)` crossing the edge into the `from_raw_parts` block.
So the pass now resolves the pointer, length and offset through
`resolve_to_producer_op`; peels `lltype.cast_ptr_to_int` / `cast_int_to_ptr`,
which name the same STR; threads the header in as a block inputarg when it
dominates the site but is not live there, declining at a merge point; and
gates the alias on `value_type_bank`, which `mir.rs` now exposes
`pub(crate)` -- `Str` and `Ref` share the `'ref'` bank, and an alias across
banks would spell `int_same_as/r>i`.
`utf8_payload_bytes` lowers; the leaf goes from 205 events to 11. The other
six callers keep their residual calls, and should: they are buffer exporter
windows (`Buffer::External.{address,size}`, an mmap `Tuple<usize,usize,bool>`,
a cffi `Tuple<*mut u8,usize>`, `WritableBuffer.{address,length}`), not GC
arrays. Their length is a separate field rather than the header's length
word and their address is mmap / cffi / ctypes memory, so aliasing them to a
header would read foreign memory as an array body.
The 193 graphs behind the closed shape advance to the next residual in the
same spine, `Wtf8::from_bytes_unchecked`, so the Phase-A failure total is
unchanged at 1940. The gate's accept set is unchanged at 1665 subjects and
its decline set at 1905, with no subject moving from accept to decline.
Assisted-by: Grok Code (delegated), Claude Opus 5 (1M context)
* check: record nbody wasm merge-exits 1, fit str_fstring wasm ratio
nbody.wasm.jitstats wasm_inline_merge_exits 2 -> 1, confirmed on two
runs with loops_compiled=5 bridges_compiled=5. str_fstring reads 4.4x
wasm/dynasm on the same 6/5/1046 topology; 5.1x is 4.4x plus 15%.
Assisted-by: Grok 4.6
* rtyper: count classdef-less Ref shells under MAJIT_RTYPER_VERBOSE
`valuetype_to_someshell`'s `Ref(_)` and `State` arms, and the two
deliberate `project_struct_field_type` mints (a raw pointer to a
scalar/unit pointee, `"BigInt"`), record into a verbose-only census.
`derive_subject_inputcells` records each startblock `Ref` input that
keeps the classdef-less shell, with its `class_root` and whether the
struct registry holds that root. The ranked census prints once after
Phase A. With the switch off, none of this runs.
`rtyper_verbose_enabled` is now `pub(crate)` and cached per thread; the
`Ref` arm consults it on every projection.
The comment at the classdef-less `SomeInstance.getattr` panic named
`project_struct_field_type` as the mint. That function returns
`Impossible` for an unregistered named root; the comment now names
`valuetype_to_someshell`'s `Ref(_)` arm.
Assisted-by: Grok 4.6 (delegated), Claude Opus 5 (1M context)
* front: drop the leaf-matched Clone/Default arms from std_identity
`lower_std_primitive_op` rewrote any one-argument call whose last path
segment is `clone` to `same_as` when the destination was a machine-word
integer or bool, and any zero-argument `default` to that width's zero.
The leaf alone does not name the callee: `core::default::Default::default`
(the trait method, dispatch unresolved) and `core::clone::<Impl>::clone`
spell the same leaf as the primitive impls, and the clone arm did not
consult the register-bank check the wrapper arm uses. It ran before the
exact-path lowering (`is_copy_scalar_or_thin_ptr_clone`,
`is_scalar_or_ptr_default`), so `clone_lookalike_path_stays_residual`
and `default_lookalike_path_stays_residual` failed.
The exact-path lowering resolves the callee declaration and covers every
width the removed arms did (signed, unsigned, bool), plus floats and
thin pointers. `lower_std_primitive_op` keeps only the transparent-
wrapper identity arm; its three destination-width parameters are gone.
Assisted-by: Grok 4.6 (delegated), Claude Opus 5 (1M context)
* majit: rename two test identifiers the ownership boundary check rejects
`scripts/check-majit-boundary.py` rejects a Rust identifier under
`majit/` that spells the runtime's object-model name. A test fn in
`annotator/builtin.rs` and a test local in `rtyper/flowspace_adapter.rs`
did; they are now `cast_instance_intrinsic_wtf8_root_projects_root_instance_to_somestring`
and `projected_root_instance`. String literals and assertions are
unchanged.
Assisted-by: Grok 4.6 (delegated), Claude Opus 5 (1M context)
* scripts: expect the worktree-keyed charon target in the selftest
`charon_cargo_target_dir` returns `<shared>/charon-target/<worktree
basename>` since 785a0ccd96e, but `llbc_extract_selftest.py` still
expected `<shared>/charon-target`, so the `prepare Charon/LLBC` job failed
at its first step. Both expectations now include the basename component.
Assisted-by: Grok 4.6 (delegated), Claude Opus 5 (1M context)
* cpyext: give date the tzinfo basestruct and size it back down at import
`cdatetime::basicsize` answered 0 for `date`, so since 5cf0376732b the
mirror fell back to `cpython_type_layout` (64) and `datetime` copied that
larger base size, while `time` kept the tzinfo shape (48). The
`cpyext_datetime` test read time 48 against datetime 64.
`date` now takes the same tzinfo-word shape as `datetime` and `time`, and
`build_api` resets its mirror to the object header before `datetime`'s
mirror is made (`init_datetime` / `_PyDateTime_Import`). Measured sizes:
date 32, datetime 48, time 48, timedelta 48.
Assisted-by: Grok 4.7 (delegated), Claude Opus 5 (1M context)
* front+jtransform: lower `<*T>::add` to `lltype.direct_ptradd`
The `<*mut T>::add` / `<*const T>::add` intercept emitted
`cast_ptr_to_int` + `int_add` at the front, so the result was an
integer while a `null_mut()` arm returning through the same block stayed
a pointer, and the two return arms disagreed on their kind. The
intercept now emits `lltype.direct_ptradd(ptr, n)` with a pointer
result. The pointee size is not recoverable from the erased pointer at
jtransform time, so the count is scaled to a byte offset at the
callsite (`n * sizeof(T)`, skipped for sizes 0 and 1). The intercept
steps aside when a later intercept in the same match lowers the call
(items-base accessors, string-array remove, getarrayitem element adds).
- annotator: `lltype.direct_ptradd` is registered; `ann_direct_ptradd`
returns `s_p` for an Instance or Ptr operand.
- rtyper: `rtype_direct_ptradd` accepts an `InstanceRepr` pointer.
- jtransform: `rewrite_op_direct_ptradd` treats the shift as already
scaled and emits `cast_ptr_to_int` / `int_add` / `cast_int_to_ptr`,
so the raw pointer stays in the Ref bank.
Decline census (darwin, same HEAD with and without this change and the
following Option function-item commit): twelve graphs leave the skip
list for the dual gate's accept arm -- `items_block_items_base`,
`typed_items_block_items_base`, `digits_item`, `<Impl>::digit`,
`range_state_value`, `range_list_length`, `range_list_start_step`,
`sizehint_state_value`, and `base` / `set` of `bytes_array` and
`unicode_array`.
`bh_lowlevel_string_len` and its caller `intobject::descr_str` go the
other way. They read `*((p as *const u8).add(OFF) as *const usize)`;
with the add lowered to integer arithmetic and the front's `Deref`
projection a no-op, the accepted graph returned `p + OFF` instead of
the word at that address. The pointer-typed add now meets the `0`
arm as `Integer ∪ Ptr` in the annotator and the graph declines until a
raw scalar load is lowered.
Assisted-by: Grok 4.7 (delegated), Claude Opus 5 (1M context)
* front: select Option combinators over a function-item callable
`recognize_closure_select_site` now accepts a function item (`FnDef`) as
the second argument of `Option::map` / `and_then` / `is_some_and` /
`unwrap_or_else` / `or_else`, either as a `FnDef` constant or as a
Copy/Move of a `FnDef`-typed local. `ClosureSelectSite` carries its
call segments in `fn_item_segments`, and `emit_site_callable` emits a
direct `Call(FunctionPath)` on the selected arm instead of
`call_once(env, (x,))`; no closure env is threaded into the arms.
The function-item segment spelling `decode_constant` used moves into
`fundecl_fn_item_segments`, shared with the new `FnDef` type reader.
Decline census (darwin, same HEAD with and without this change and the
preceding `direct_ptradd` commit): `stginfo::<Impl>::from_name` is
newly accepted. `stginfo_format`, `stginfo_paramfunc` and
`stginfo_proto` become prepass subjects for the first time and decline
in phase A on the `Result<*mut PyObject, PyError> ∪ PyObject` union
their callee `stginfo::dict_of` already fails on without this change.
`builtins::exc_exception_new` gets past `cls_slot.map(shadow_stack_get)`
and now stops at `kind_of_canonical_exc_class`, whose body is rejected
for its `Acquire` atomic loads.
Assisted-by: Grok 4.7 (delegated), Claude Opus 5 (1M context)
* majit: refresh the rtyper skip-subject baselines
darwin is rewritten from a decline census at bbc87965d75 (corpus
2cb7105a45018827): 14 names join, 11 leave. Of the 14, three
(`stginfo_format`, `stginfo_paramfunc`, `stginfo_proto`) became prepass
subjects with the Option function-item commit and decline on their
callee `stginfo::dict_of`; the other eleven decline identically in the
census at 30d85f114b9, before the rebase onto e75b3435a84.
`builtins::exc_exception_new` is left out on purpose. The dual gate
accepted it before #1843; it now declines because
`kind_of_canonical_exc_class`, reached through
`w_exception_new_empty_for_class`, is rejected for its `Acquire` atomic
loads, so the darwin ratchet keeps reporting it.
linux is the list CI refreshed at d473ef9dae6 with the darwin delta
between 30d85f114b9 and bbc87965d75 applied: the twelve `<*T>::add`
graphs leave, and `stginfo_format`, `stginfo_paramfunc`,
`stginfo_proto`, `bh_lowlevel_string_len` and `intobject::descr_str`
join. `exc_exception_new` was already listed there, under
`two-phase-rtype-skipped`.
Assisted-by: Claude Opus 5 (1M context)
* pyrex: size READ_FORMS for its seven read forms
The rebase onto 4a929a733a4 joined two additions to
`gate_triage_complete::READ_FORMS`: `env_is_set` from main and
`env_var` from this branch. Each side had resized the array from 5 to
6, so the merged text listed seven forms under `[&str; 6]`.
Assisted-by: Claude Opus 5 (1M context)
* interpreter: port _dict_merge_loop iff and unpackiterable shim
pyopcode.py `_dict_merge_loop` is `@jit.look_inside_iff(unroll_safe)`
with `iter(keys())`/`next`. baseobjspace.py splits known-length unpack
through `@jit.dont_look_inside` `_unpackiterable_known_length` and
`@jit.unroll_safe` `_unpackiterable_known_length_jitlook`, returning a
W_List; unpackiterable_unroll / fixedview_unroll call the latter.
Assisted-by: Grok 4.6
* jit: forward AfterApplied resume state and wasm JIT knobs
VableEscape AfterApplied now mirrors the flushed live frame into the
walk snapshot the same way the exact-escape commit does. Bytecode dump
is keyed per code object. JIT_ENV_NAMES includes MAJIT_MAX_BRIDGES and
MAJIT_SKIP_BRIDGES; wasm hosts forward presence via var_os. Charon
target dirs are keyed by a hash of the resolved worktree path.
Assisted-by: Grok 4.6
* front: fill fun_decl_id on FunctionPath constructors
CallTarget::FunctionPath gained fun_decl_id on origin/main; the
direct_ptradd and Option combinator emitters still constructed the
pre-FunDecl shape.
Assisted-by: Grok 4.6
* interpreter: re-read the merge key after `__contains__` in `_dict_merge_loop`
`_dict_merge_loop` read the key from its shadow-stack slot once and kept
using that word after `baseobjspace::contains`, which runs Python and
can collect. The duplicate-key message and `w_dict_store` now re-read
the slot, as the loop did before d4a850c436a.
Assisted-by: Claude Opus 5 (1M context)
* majit-translate: spell `fun_decl_id` in the branch's CallTarget tests
After the rebase onto 72eafe31dd1, `CallTarget::FunctionPath` and
`CallTarget::Method` carry `fun_decl_id`. Twelve test sites this
branch added in `from_size_align`, `iter_next`, `mir`,
`option_closure_select` and `flowspace_adapter` still built or matched
the old shape, and the `majit-translate` lib tests did not compile.
Constructors now pass `fun_decl_id: None` and patterns end in `..`.
Assisted-by: Claude Opus 5 (1M context)
* majit: refresh the skip-subject baselines after the rebase onto 72eafe31dd1
darwin decline census at e365481abaa (corpus df6b1398da4911a8) against
the one at 63ceca475af. Seven names join, none of them a graph the
earlier census saw:
- `unicodeobject::w_str_from_storage_and_length`, which lost
`dont_look_inside` in #1889;
- `host_seam::getenv`, `host_seam::ops::getenv` and
`host_seam::real::<Impl>::getenv`, stopped by `OsStr::from_bytes`;
- the closure `call` graphs of `executioncontext::_trace`,
`_lsprof` `enable` and `_ssl` `set_default_verify_paths`, stopped by
a classdef-less `__pos_0` / `tupled_args` read.
`bh_lowlevel_string_len` leaves: #1889 reads the length through
`LowlevelStringHeader`. `interp_kevent` `__new__` / `default` leave on
darwin, and nine `__new__` / `default` graphs move from
`two-phase-never-a-subject` to `two-phase-rtype-skipped`.
`builtins::exc_exception_new` stays out of the darwin list.
linux takes the same delta: the seven join and `bh_lowlevel_string_len`
leaves.
Assisted-by: Claude Opus 5 (1M context)
* majit-translate: ignore `fun_decl_id` in the result-exc lowering test pattern
`CallTarget::FunctionPath` gained the field in #1889; this integration
test still matched `{ segments }` and failed to compile.
Assisted-by: Claude Opus 5 (1M context) Latest Branches
+15%
0%
0%
© 2026 CodSpeed Technology