Skip to content

jit: a dead-var link-arg trim scoped on the wrong reachability, a shared-receiver locals_w_mut!, bridge opt-fuel, and 91 stale CPython-suite baseline entries - #1138

Merged
youknowone merged 4 commits into
mainfrom
jitcode
Aug 10, 2026

Conversation

@youknowone

@youknowone youknowone commented Aug 10, 2026

Copy link
Copy Markdown
Owner

Three independent changes that came out of chasing a bridge-path wrong-code
failure. The failure itself turned out to be #1130, already merged — this
branch is rebased onto it and carries only what the hunt produced on its own
merits.

prune_dead_phis trimmed a link the way its own reachability walk saw it

prune_dead_phis (majit/majit-translate/src/model.rs, the port of
simplify.py:425-479 transform_dead_op_vars) drops a dead block.inputargs[i]
from every reachable block in Step 7, and trimmed the matching
Link.args[i] in Step 6 only for links whose source block was reachable.

Those two scopes are not the same set. A block the reachability walk excludes
can still name a reachable block as its link target — an orphan
eliminate_empty_blocks bypassed, or one of the merge blocks jtransform
leaves whose inputargs are phi targets with no backing OpKind::Input. Its
link kept the full argument list while the target lost a column.

Nothing asserts len(link.args) == len(link.target.inputargs), so the damage
surfaces one pass later. remove_duplicate_inputargs reads each phi column by
index across every incoming link, so the untrimmed link contributes its value
one slot over; the union-find then merges two variables that hold different
values, and the resulting rename is applied to every block in the graph.

Keying the trim on the target's reachability closes the gap. Reachability
is closed under exits, so this only ever adds links to the trim — it can never
drop one that was previously trimmed.

Upstream has no equivalent gap: graph.iterblocks() is its block list, so an
unreachable block is not in blocks and there is no link for it to skip.

A regression test builds the shape directly — entry and an orphan both jumping
to a merge whose inputargs are [dead, live] — and fails on the previous code
with the surviving column reading back as the orphan's value.

locals_w_mut! accepted a shared receiver

locals_cells_stack_w is a raw pointer, so it reads fine through a shared
&PyFrame. The macro that replaced the old &mut self accessor therefore let
a safe caller holding a shared frame mint &mut to the array. &mut * on the
receiver restores the requirement the accessor had.

The result's lifetime stays unconstrained — &mut *ptr has none to bind — so
overlapping calls still alias invisibly to the borrow checker. Binding it needs
a function signature, which is the accessor form whose separate graph defeats
the getfield/setfield pairing the macro exists for; the doc comment says so
rather than leaving the remaining hole unmarked.

MAJIT_MAX_BRIDGES — opt-fuel over bridge compilation

MAJIT_NO_BRIDGE is all-or-nothing. It can attribute a wrong value to the
bridge path but not to a bridge. MAJIT_MAX_BRIDGES=N compiles the first N and
declines the rest, which turns that attribution into a bisection over N: the
boundary names the compilation that first produces the wrong value.

The counter is consumed last in the should_bridge chain, so it only advances
on a guard failure the other conditions already admitted and the index counts
bridges actually taken. MAJIT_BRIDGE_FUEL_LOG=1 prints each one, which is what
pairs an index with the @@@GUARD line MAJIT_GUARDLOG emits beside it.

On the failure that motivated it the bisection was 6 runs of a few seconds each
against a repro that had cost a full rebuild per arm, and it landed on a single
bridge index with a stable 3/3 boundary on both sides.

The suite baseline recorded 91 working modules as broken

Separate from the three changes above, and found while asking what the green
gate actually covers.

pyre/cpython_tests/run.py runs only the modules baseline.json records as
PASS — everything else is deselected and carries no signal. That makes the file
stale in one direction: a module that starts passing stays outside the gate
until someone runs the non-PASS set and records it. Nothing does that
automatically.

Running the full 434 on dynasm turns up 94 such modules. 91 reproduce as PASS in
a second run at lower concurrency and are promoted here — 63 IMPORTERROR, 16
CRASH, 9 FAIL, 3 TIMEOUT
. The CRASH set alone includes test_dict,
test_tuple, test_range, test_slice, test_decimal, test_baseexception,
test_userdict, test_userlist, test_weakset, test_queue,
test_configparser and test_format.

Three more (test_embed, test_frozen, test_tools) pass under --full but
stay put: they are KNOWN_SKIPS, which is a policy about what the suite should
never gate on rather than a record of what works. --full also reports
test_c_locale_coercion as PASS -> FAIL; that is a KNOWN_SKIPS entry whose
stated reason is the failure it produces (it asserts a child's stderr is empty
while MAJIT_STATS=1 writes a [jit-stats] line to every process), so the gate
lane never runs it and its stale PASS is inert.

The gate now selects 201 modules and runs 200 in 158s, against 109 in 170s
before — the coverage roughly doubles and the wall clock does not move.

What remains genuinely broken on dynasm, for the record: 137 FAIL, 26 TIMEOUT,
10 IMPORTERROR, and 4 CRASH (test_mmap, test_resource, test_statistics,
test_weakref).

Gates

All on 729bcb51c9e, which did not move across the runs; working tree clean;
build/llbc/*.ullbc re-extracted for all three crates first, and no run
reported LLBC STALE.

gate result
cargo test --all --no-default-features --features dynasm exit 0, 103 test result lines
pyre/check.py --backend dynasm ALL PASSED 415/415, incl. vendored CPython suite 109 modules
pyre/check.py --backend cranelift ALL PASSED 414/414
pyre/extra_tests/parity_tests/run.py all parity tests pass

test.test_pickletools — red on the previous base and the reason this branch
went looking — passes here both with bridges on and under MAJIT_NO_BRIDGE=1,
which is #1130 doing its job; the baseline already recorded it as PASS, so it
was a real regression rather than a stale entry.

🤖 Generated with Claude Code

`locals_cells_stack_w` is a raw pointer, so it reads through a shared
`&PyFrame` and the macro let a caller holding one mint `&mut` to the
array.  The accessor this macro replaced took `&mut self`; `&mut *` on
the receiver restores that requirement.

The result's lifetime stays unconstrained — `&mut *ptr` has no lifetime
to bind — so overlapping calls still alias invisibly to the borrow
checker.  Binding it needs a function signature, which is the accessor
form whose separate graph defeats the getfield/setfield pairing.

Assisted-by: Claude
`prune_dead_phis` Step 7 drops a dead inputarg from every reachable
block, but Step 6 trimmed the matching `Link.args[i]` only for links
whose *source* block was reachable.  A block the reachability walk
excludes — an orphan `eliminate_empty_blocks` bypassed, or a merge block
jtransform leaves whose inputargs are phi targets rather than parameters
— can still name a reachable block as its link target, and its link kept
the full arg list.

`remove_duplicate_inputargs` reads each phi column by index across every
incoming link, so the untrimmed link contributes the value one slot over.
The union-find then merges two variables that hold different values, and
the resulting rename is applied to every block.

Reachability is closed under exits, so keying the trim on the target adds
links and never drops one.  `simplify.py:512-516` has no equivalent gap:
`graph.iterblocks()` is upstream's block list, so an unreachable block is
not in `blocks` and has no link to skip.

Assisted-by: Claude
`MAJIT_NO_BRIDGE` is all-or-nothing, so a wrong value that only appears once
bridges are compiled can be attributed to the bridge path but not to a bridge.
`MAJIT_MAX_BRIDGES=N` compiles the first N and declines the rest, which turns
that attribution into a bisection over N: the boundary names the compilation
that first produces the wrong value.

The counter is consumed last in the `should_bridge` chain, so it only advances
on a guard failure the other conditions already admitted and the index counts
bridges actually taken. `MAJIT_BRIDGE_FUEL_LOG=1` prints each one, which is
what pairs an index with the `@@@GUARD` line `MAJIT_GUARDLOG` emits beside it.

Assisted-by: Claude
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@youknowone, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: a682ef67-429d-42e9-b2c4-7abc240c9050

📥 Commits

Reviewing files that changed from the base of the PR and between 0768a7c and 2ee9f3a.

📒 Files selected for processing (4)
  • majit/majit-metainterp/src/jitdriver.rs
  • majit/majit-translate/src/model.rs
  • pyre/cpython_tests/baseline.json
  • pyre/pyre-interpreter/src/pyframe.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 729bcb51c9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +469 to +475
/// `MAJIT_MAX_BRIDGES=N` (diagnostic): allow the first N bridge compilations
/// and behave as `MAJIT_NO_BRIDGE` from then on. Bisecting N names the bridge
/// whose compilation first produces a wrong value, at seconds per run rather
/// than a rebuild per arm. Consumes fuel only when the rest of `should_bridge`
/// already held, so the count is bridges actually taken — place it last in the
/// `&&` chain. `MAJIT_BRIDGE_FUEL_LOG` reports each one taken.
fn bridge_fuel_take() -> bool {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Gate CALL_ASSEMBLER bridges with the fuel counter

When a guard failure arrives through CALL_ASSEMBLER, MAJIT_MAX_BRIDGES does not limit it: jit_ca_handle_guard_failure and try_compile_ca_bridge perform their own must_compile_with_values checks and call trace_and_compile_from_bridge directly (pyre/pyre-jit/src/call_jit.rs:3959-4017 and 4074-4109), while that function checks only no_bridge_enabled at lines 3240-3244. Consequently even MAJIT_MAX_BRIDGES=0 can compile these bridges, and larger limits no longer identify the global Nth bridge as documented. Make the fuel gate available to these frontend-owned bridge decisions, just as MAJIT_NO_BRIDGE is.

AGENTS.md reference: AGENTS.md:L252-L253

Useful? React with 👍 / 👎.

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown

🤖 Codex parity review

Static analysis of this diff vs the local RPython/PyPy sources (commit 2ee9f3a).
Updated: 2026-08-10T05:19:36.176Z

Files in the reviewed diff
majit/majit-metainterp/src/jitdriver.rs
majit/majit-translate/src/model.rs
pyre/cpython_tests/baseline.json
pyre/pyre-interpreter/src/pyframe.rs

1. Regressions to PyPy parity introduced by this patch

  • majit/majit-metainterp/src/jitdriver.rs:4441 (also :5744, :6890) ↔ rpython/jit/metainterp/compile.py:701-716: the added && bridge_fuel_take() makes a guard failure blackhole-resume after MAJIT_MAX_BRIDGES bridge attempts, even when must_compile() and stack capacity permit compilation. PyPy has no fuel condition: it always calls _trace_and_compile_from_bridge when those two conditions hold.

2. Other mismatches introduced by this patch

None.

3. Pre-existing mismatches (already present before this patch)

  • majit/majit-metainterp/src/jitdriver.rs:465-467rpython/jit/metainterp/compile.py:701-716: pre-existing MAJIT_NO_BRIDGE can suppress bridge compilation despite PyPy compiling whenever must_compile() and not stack_almost_full() hold.
  • majit/majit-translate/src/model.rs:4204-4227rpython/translator/simplify.py:565-579: the Rust port deliberately omits PyPy’s all-equal phi collapse (uf.union(new_args[0], input)), retaining a live phi column because this IR does not run the required subsequent SSA_to_SSI repair pass.

4. Structural adaptations

  • majit/majit-translate/src/model.rs:4073-4079rpython/flowspace/model.py:66-76 and rpython/translator/simplify.py:518-523: Rust retains unreachable blocks in graph.blocks, unlike PyPy’s reachability-only iterblocks(). Trimming links by reachable target preserves PyPy’s len(link.args) == len(link.target.inputargs) invariant for retained orphan predecessors.
  • pyre/pyre-interpreter/src/pyframe.rs:58pypy/interpreter/pyframe.py:365-376: requiring &mut *$frame before projecting the raw array pointer is a Rust aliasing-safety adaptation. It preserves PyPy’s direct mutable frame-array updates while preventing a shared Rust frame borrow from manufacturing mutable access.

… broken

The suite gate runs only the modules `baseline.json` records as PASS; every
other entry is deselected and carries no signal. Entries therefore go stale in
one direction only — a module that starts passing stays outside the gate until
someone runs the non-PASS set and says so.

Running the full 434 on dynasm finds 94 such modules. 91 reproduce as PASS in a
second run at lower concurrency and are recorded here: 63 were IMPORTERROR, 16
CRASH, 9 FAIL, 3 TIMEOUT. The CRASH set alone includes test_dict, test_tuple,
test_range, test_slice, test_decimal, test_baseexception, test_userdict,
test_userlist, test_weakset, test_queue, test_configparser and test_format.

The remaining 3 (test_embed, test_frozen, test_tools) pass under `--full` but
stay put: they are `KNOWN_SKIPS` entries, and that list is a policy about what
the suite should never gate on, not a record of what currently works.

`--full` also reports test_c_locale_coercion as PASS -> FAIL. That module is a
`KNOWN_SKIPS` entry whose stated reason is the failure it produces — it asserts
a child's stderr is empty while `MAJIT_STATS=1` writes a `[jit-stats]` line to
every process — so the gate lane never runs it and its stale PASS is inert.

The gate now selects 201 modules and runs 200 of them in 158s, against 109 in
170s before.

Assisted-by: Claude
@youknowone youknowone changed the title jit: a dead-var link-arg trim scoped on the wrong reachability, a shared-receiver locals_w_mut!, and bridge opt-fuel jit: a dead-var link-arg trim scoped on the wrong reachability, a shared-receiver locals_w_mut!, bridge opt-fuel, and 91 stale CPython-suite baseline entries Aug 10, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2ee9f3a7b4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

//
// Upstream cannot reach this: `graph.iterblocks()` *is* its block list,
// so an unreachable block is not in `blocks` and has no link to skip.
for block_idx in 0..graph.blocks.len() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Exclude unreachable links instead of rewriting them

When FunctionGraph retains the orphan predecessor described here, extending Step 6 to every source block is a workaround in the wrong pass: upstream transform_dead_op_vars walks only its reachable blocks, while upstream mkentrymap(graph) also sees only reachable iterlinks. The later Rust remove_duplicate_inputargs still builds its entry map from every raw graph.blocks link, so the orphan continues to participate in phi-tuple equality and can suppress legitimate duplicate-phi elimination. Preserve the upstream structure by filtering unreachable links when constructing that entry map (or removing the orphan), rather than making dead-variable trimming process links upstream never sees.

AGENTS.md reference: AGENTS.md:L231-L234

Useful? React with 👍 / 👎.

return true;
};
static USED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
let n = USED.fetch_add(1, std::sync::atomic::Ordering::Relaxed);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Spend bridge fuel only for an actual bridge attempt

With MAJIT_MAX_BRIDGES set, this counter advances before bridge tracing is known to be possible. In the green-resume path, for example, should_bridge consumes fuel before blackhole recovery can return pc == usize::MAX, which skips start_bridge_tracing; other paths call start_bridge_tracing, which can normally return false for !state.can_trace(), an evicted owning token, or a declined retrace. Thus MAJIT_MAX_BRIDGES=1 can consume its only slot without compiling any bridge, contradicting the documented “first N bridge compilations” semantics and misleading the intended bisection. Count or log only after the final bridge-path gates have succeeded.

AGENTS.md reference: AGENTS.md:L252-L253

Useful? React with 👍 / 👎.

@youknowone
youknowone merged commit 36666ef into main Aug 10, 2026
15 of 17 checks passed
@youknowone
youknowone deleted the jitcode branch August 10, 2026 08:27
@youknowone

Copy link
Copy Markdown
Owner Author

main is red on CPython suite (gate) since this merge

The baseline refresh in this PR promoted test.test_format from CRASH to PASS, and the gate now runs it and fails.

This commit's own runrun 31370182942, job 93400431577, 36666ef933c, no other branch involved:

200 to run, 22 skipped, 212 not gated (non-PASS), timeout=300s
── REGRESSIONS (1) ──
  - test.test_format: PASS -> FAIL  rc=1 FAILED (failures=1, skipped=2) | FAIL: test_locale (__main__.FormatTest.test_locale)

The parent-SHA controlrun 31353530450 at 0768a7cd41f, green:

109 to run, 22 skipped, 303 not gated (non-PASS)
  PASS 109   FAIL 0

test.test_format was CRASH in the baseline there, so the gate never ran it. The 91-entry refresh moved it into the gated set (109 → 200 to run), which is what surfaced the failure. Every open PR inherits this through its merge ref — e.g. #1141, whose only red is this exact line.

The failure is real, and JIT-independent

AssertionError: ',' not found in '123456789'
    lib-python/3/test/test_format.py:440  self.assertIn(sep, text)

Reproduced locally on macOS, LANG=en_US.UTF-8, and it reproduces with the JIT off:

$ PYRE_NO_JIT=1 ./target/release/pyre-dynasm -c "
import locale; locale.setlocale(locale.LC_ALL,'')
print(repr(format(123456789,'n')), repr(format(1234.5,'n')))"
'123456789' '1234.5'          # CPython: '123,456,789' '1,234.5'

The locale data and the grouping machinery both exist — only the 'n' path never consults the locale:

locale.localeconv()        -> grouping [3, 0], thousands_sep ','   # data is there
'{:,d}'.format(123456789)  -> '123,456,789'                        # insertion works
'{:n}'.format(123456789)   -> '123456789'                          # locale never read

Where

pyre/pyre-interpreter/src/type_methods.rs:2452

if let Some(separator) = p.grouping {
    magnitude = separate_integer_digits(magnitude, interval, separator, displayed_digits);
}

'n' carries no ,/_ separator, so p.grouping is None and this branch is never taken. interval is a per-ty constant in the radix table ('n' => (10, 3, false, ""), :2386), so there is no channel for a locale grouping vector either.

Upstream is shaped differently: newformat.py:642-658 _get_locale(tp) branches three ways and for 'n' reads rlocale.numeric_formatting() (:643-644); insertion is then a single routine _group_digits (:738-778) shared by ,/_ and 'n', driven by a grouping vector that is "\3"/"\4" for the literal separators and the locale vector for 'n'. pyre has only separate_integer_digits (:2332, RustPython-shaped), so the locale channel is absent by construction.

Notes for whoever picks up the fix

  • Float 'n' is not a one-line change. format_finite_float delegates render+group+pad end-to-end to FormatSpec::format_float (:3211-3216); upstream instead renders unpadded 'g', splits with _parse_number (:984-991), groups, substitutes _loc_dec in _fill_number (:798-813), then pads.
  • format(1234,'012n') must be '0,001,234', which comes from _group_digits's min_width argument (:759). A render-then-pad_to_width shortcut gives '0000001,234' and this test would not catch ittest_locale only asserts sep in text and text.replace(sep,'') == '123456789'.
  • rustpython_host_env::locale::localeconv_data().grouping cannot be used as-is for the grouping vector: its copy_grouping stops at CHAR_MAX and drops it, collapsing "\3\x7f" (stop) and "\3" (repeat) to the same value. interp_locale.rs:225-235 already does the raw NUL-terminated walk — minus the trailing 0 it appends at :220-222, which is _w_copy_grouping's app-level fixup and must not reach the formatter.
  • Windows/wasm/sandbox each need an arm; note pyre's localeconv is already unix-gated (interp_locale.rs:200) while host_env supports any(unix, windows), and the sandbox build installs raising stubs — format() must not start raising, which diverges from upstream's sandboxsafe=True (rlocale.py:167).

Until it is fixed, the alternative is to put test.test_format back to its pre-refresh state so the gate reflects reality.

commented by Claude

youknowone added a commit that referenced this pull request Aug 10, 2026
`format(x, 'n')` never consulted the locale.  The integer path inserted a
separator only inside `if let Some(separator) = p.grouping`, which `'n'` never
enters — it carries no `,` or `_` — and the group size came from a per-radix
constant with no channel for a locale's grouping vector.  The float path
delegated render, group and pad end-to-end to the shared engine, which treats
`'n'` as `'g'`.  Under `LC_ALL=en_US.UTF-8`, `format(123456789, 'n')` was
`'123456789'` and `format(1234.5, 'n')` was `'1234.5'`.

Port the three routines upstream splits this across.  `_get_locale`
(`newformat.py:642-658`) branches on the presentation code: `'n'` takes the
current locale, an explicit `,`/`_` takes that separator at a fixed group size
— four for the power-of-two radices, three otherwise — and anything else
carries a stop sentinel.  `_group_digits` (`:738-778`) and `_fill_digits`
(`:727-736`) perform the insertion for all of those, so `separate_integer_digits`
goes away and the `,`/`_` specs travel the same route as `'n'`.

The zero fill of a `0=` spec belongs inside the grouping rather than after it:
`format(1234, '012n')` is `'0,000,001,234'`, thirteen characters from a width
of twelve, because the padding digits are separated like any other.  That is
what `_calc_num_width`'s `n_min_width` (`:695-704`) carries, and both call
sites compute it — `width - (sign + prefix)` for integers,
`width - (sign + decimal point + remainder)` for floats, matching
`extra_length`.

The float path grows the split `_format_float` performs (`:1004-1044`): render
unpadded, take the sign off, separate the integer digit run alone, re-emit the
decimal point as the locale's, and leave the fraction and any exponent in the
remainder, so `format(1e300, 'n')` keeps its exponent ungrouped.  Zero padding
is rejected for complex specs, so the per-lane `complex_component_spec` split
reaches `_group_digits` only at `n_min_width` 0 and needs nothing further.

`numeric_formatting` (`rlocale.py:173-178`) is new, placed beside the `_locale`
module port so it shares that module's raw `localeconv()` walk: the grouping
`format(x, 'n')` separates by and the grouping `locale.localeconv()` reports
come out of one read.  The walk keeps a `CHAR_MAX` element, which
`rustpython_host_env::locale`'s reader drops — dropping it collapses "stop"
onto "repeat the last group".  Off unix, without `host_env`, and under sandbox
the C locale's values stand in; upstream declares `localeconv`
`sandboxsafe=True` and reads the host locale even there, but pyre's sandbox
build replaces `_locale`'s host entry points with raising stubs and `format()`
must not acquire a raising path.

Six unit tests pin `_group_digits` against values taken from the vendored
source: the zero-fill widths, the repeat-the-last-group rule, the stop
sentinel, and a multi-byte separator surviving the buffer reverse.  None are
covered by `test_format.test_locale`, which asserts only that the separator
appears — the test that made this visible when #1138 promoted its module into
the gated set.

Assisted-by: Claude
youknowone added a commit that referenced this pull request Aug 11, 2026
MAJIT_MAX_BRIDGES and MAJIT_BRIDGE_FUEL_LOG arrived in 36666ef
(#1138) and had no rows. Both are read inside bridge_fuel_take() in
majit-metainterp/src/jitdriver.rs.

Both descriptions are sourced to MAJIT_MAX_BRIDGES's doc comment, which
is also the only place MAJIT_BRIDGE_FUEL_LOG is described - it has no
doc comment of its own, so its row carries the one clause that names it
plus the message format read off the site.

Retirement conditions are UNRECORDED for both; the introducing commit
states neither.

The catalog is 50 rows.

Assisted-by: Claude
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant