Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f15a5a7
objspace: NaN and complex take Python 3.14 pointer identity
kyokuping Aug 10, 2026
a10a27f
jit: pin w_class on the float list-store fast paths
kyokuping Aug 21, 2026
128e605
math: call the __floor__/__ceil__/__trunc__ descriptor without bindin…
youknowone Aug 19, 2026
a4570cc
math: reduce floor/ceil's __float__ fallback through newlong_from_float
youknowone Aug 19, 2026
9090c34
math: reduce a machine-word gcd pair without rbigint
youknowone Aug 19, 2026
1212c9f
jit: read a plain residual builtin call's positionals from the shadow…
youknowone Aug 19, 2026
3e8f379
jit: specialize math.floor, math.ceil, math.trunc and math.fabs
youknowone Aug 19, 2026
83b7d11
bench(synth): record the wasm jit-stats baselines for the two math fo…
youknowone Aug 19, 2026
bce4595
math: fold every remaining pymath primitive through a raw helper table
youknowone Aug 20, 2026
3b693d6
jit: specialize the generic math float folds and math.isclose
youknowone Aug 20, 2026
8f08e16
bench(synth): merge the four math fold fixtures into one
youknowone Aug 21, 2026
73d28c6
jit: read a residual call's roots through the scope's cached cell
youknowone Aug 20, 2026
e903efb
interpreter, jit: fold a table of builtins out of their residual call
youknowone Aug 20, 2026
13e1e7e
bench(synth): add the builtin fold fixture and its native jit-stats b…
youknowone Aug 20, 2026
ae672fc
jit: decline a fold before it runs the builtin, and cross-check every…
youknowone Aug 21, 2026
ddd9de6
jit: give the two-argument builtin fold its result value before it gu…
youknowone Aug 21, 2026
ee2fc92
math: convert isclose's operands before checking the tolerances
youknowone Aug 21, 2026
e14adca
bench(snippets): make the min/max tie assertion observable
youknowone Aug 21, 2026
906135d
jit: keep the min/max fold off bigint operands
youknowone Aug 21, 2026
ad5e124
bench(snippets): cover the bigint pair whose address order is determi…
youknowone Aug 21, 2026
dc6b634
jit: keep the abs fold off int subclasses
youknowone Aug 21, 2026
9230a8e
builtins: keep bigints out of the all-int sorter
youknowone Aug 21, 2026
e62dfad
builtins: dispatch abs() through the receiver's __abs__
youknowone Aug 21, 2026
876b5d4
comments: cite this branch's upstream references by symbol
youknowone Aug 21, 2026
276bbea
builtins: give int's __float__ and the __round__ slots a structural body
youknowone Aug 21, 2026
06ac64a
portal: key the pypyjit green on the running profile state
youknowone Aug 22, 2026
a35309a
check: band the collection-schedule guard_failures on inline_freevar_…
youknowone Aug 22, 2026
07937df
check: record builtin_folds_hot's wasm baseline and refit both its ce…
youknowone Aug 22, 2026
52d28ff
check: state a wasm ceiling for str_getitem_len_hot, and correct the …
youknowone Aug 22, 2026
1422b28
check: double builtin_folds_hot's loop counts and refit its wasm ceil…
youknowone Aug 22, 2026
2ff0deb
Merge branch 'main' into jitcode
youknowone Aug 22, 2026
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
2 changes: 1 addition & 1 deletion majit/majit-rlib/src/rbigint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5468,7 +5468,7 @@ fn parse_string_from_binary_base(

/// rbigint.py `gcd_binary`.
#[majit_macros::jit_elidable]
fn gcd_binary(mut a: i64, mut b: i64) -> i64 {
pub fn gcd_binary(mut a: i64, mut b: i64) -> i64 {
debug_assert!(a >= 0 && b >= 0);
if a == 0 {
return b;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=1
guard_failures=6
internal_compile_panics=0
loops_aborted=0
loops_compiled=1
loops_compiled=6
retraces_compiled=0
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=1
guard_failures=6
internal_compile_panics=0
loops_aborted=0
loops_compiled=1
loops_compiled=6
retraces_compiled=0
133 changes: 133 additions & 0 deletions pyre/bench/synth/builtin_folds_hot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# pyre-check: max-pypy-ratio=12
# pyre-check: skip-cpython
# Fitted between the two arms. With every fold in place the three runners read
# 4.6x / 4.7x on darwin-arm64, 7.2x / 7.6x on ubuntu-24.04 and 9.2x / 10.0x on
# windows, at half the loop counts below. The windows pair was marked `?`
# there: pypy's execution-only time sat under FLOOR_GATE_MIN_BASELINE_S, which
# is 0.15625s on a host whose CPU accounting advances in 1/64s ticks, and the
# pair cleared the ceiling of 8 it carried then only because `_compare_buffer`
# grants two ticks per unit of limit. Doubling the work carries that baseline
# over the bar, so the number is judged rather than excused. With
# PYRE_FBW_NO_SPECIALIZE=builtin_fold1,builtin_fold2 putting the same loops back
# on the residual, darwin-arm64 reads 52.9x and 61.4x -- eleven times the folded
# arm on that host. The gate clears the highest folded reading by 20% and still
# sits an order of magnitude under the residual arm scaled to it.
#
# pyre-check: max-wasm-ratio=13
# pyre-check: spec-folds=builtin_fold1,builtin_fold2
# The wasm ceiling is fitted to the highest reading observed plus 15%. Two
# ubuntu-24.04 runs of the same code read 8.2x and 11.3x, because the
# denominator is small enough for startup subtraction to move it: dynasm's
# execution-only time came out 0.69s and then 0.44s, and the second run's own
# failure line said a dynasm startup estimate 0.68x larger would have erased
# the gap. The loop counts below are twice what they were for that reason --
# the subtraction error is a fixed number of milliseconds, so doubling the work
# halves its share. Every recorded jit-stats counter is unchanged by the
# doubling.
#
# The structure is the host crossing. A JIT-emitted trace is its own wasm
# module, so a call leaving it reaches the interpreter through the
# `env.jit_call` trampoline, which marshals func_ptr and arguments through the
# frame call area in shared linear memory and dispatches through the main
# module's indirect function table. What the fold removes is the frame force,
# the argument rooting, the execution-context resolution and the gateway
# binding; the crossing itself stays, because every fold here still lowers to
# a call into a raw helper. The tree carries its own contrast: on the same
# ubuntu run `math_folds_hot`, whose folds lower to inline arithmetic instead,
# reads 3.3x. Every loop below is nothing but folded builtin calls, so the
# crossing is the whole measurement. The alternative to this allowance is to
# give the trace module direct imports for the raw helpers rather than one
# generic trampoline.
#
# `spec-folds` is the exact instrument neither ratio is. Six loops sum into
# one number, so retiring one channel moves it by less than the span this
# fixture reads across the runners; the census gates each fold's coverage
# instead, and it reads the same on every host.
#
# Every builtin the generic walker fold covers, one hot loop per channel.
#
# Without a fold, `hash(x)` / `ord(c)` / `abs(x)` / `min(a, b)` each reach the
# interpreter as `bh_call_fn(builtin, NULL, ...)`, and that residual costs the
# same for all of them: the frame force, the argument rooting, the execution
# context resolution and the gateway signature binding all run before the body
# does. The fold emits a direct call into the builtin's raw helper, a guard on
# that channel's decline sentinel, and an inline `wrapint` / `wrapfloat` the
# optimizer can keep virtual.
#
# The ratio is the detector here: losing a fold changes no jit-stats counter,
# because the residual it falls back to compiles the same loop.
#
# hash_int/hash_str the `Int1` channel — an `i64` result and the
# `INT_FOLD_DECLINE` guard.
# ord_str the same channel on an operand whose acceptance is a
# length, not a type.
# abs_int/abs_float one builtin holding two rows, one per result channel.
# min_max the `Ref2` channel — the helper returns one of its own
# arguments, so nothing is allocated at all.
HASH_N = 32000000
ORD_N = 32000000
ABS_N = 32000000
MINMAX_N = 24000000


def run_hash_int():
# `hash(int)` is the value itself, so this total is the same everywhere.
total = 0
x = 1234567
for _ in range(HASH_N):
total += hash(x)
return total


def run_hash_str():
# A string's hash is seeded per process, so the digest itself cannot be
# printed. Count the iterations that agree with the first one instead:
# the fold still has to produce the digest, and the count is invariant.
s = "specialize"
first = hash(s)
same = 0
for _ in range(HASH_N):
if hash(s) == first:
same += 1
return same


def run_ord():
total = 0
c = "q"
for _ in range(ORD_N):
total += ord(c)
return total


def run_abs_int():
total = 0
x = -7
for _ in range(ABS_N):
total += abs(x)
return total


def run_abs_float():
total = 0.0
x = -7.5
for _ in range(ABS_N):
total += abs(x)
return total


def run_min_max():
total = 0
a = 3
b = 9
for _ in range(MINMAX_N):
total += min(a, b) + max(a, b)
return total


print(run_hash_int())
print(run_hash_str())
print(run_ord())
print(run_abs_int())
print(round(run_abs_float(), 6))
print(run_min_max())
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=1
guard_failures=6
internal_compile_panics=0
loops_aborted=0
loops_compiled=1
loops_compiled=6
retraces_compiled=0
23 changes: 16 additions & 7 deletions pyre/bench/synth/inline_freevar_after_mayforce.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
# pyre-check: max-pypy-ratio=86
# pyre-check: jitstats-band=guard_failures=1
# One tree, three runners, one CI run (`1d212895c6b`): macOS and ubuntu read
# 1003 dynasm / 1008 cranelift, windows 1004 / 1009. The loop and bridge counts
# agreed at six and five everywhere, so the split is carried entirely by this
# counter and is not a function of the tree. The baseline holds the pair the two
# agreeing runners read; the band is exactly the measured width, so anything
# wider than the split still gates.
# pyre-check: jitstats-band=guard_failures=8
# `guard_failures` here is carried by two things this fixture is not about.
# One is the host: one tree read 1003 dynasm / 1008 cranelift on macOS and
# ubuntu and 1004 / 1009 on windows in a single CI run (`1d212895c6b`), with the
# loop and bridge counts agreeing everywhere. The other is the collection
# schedule -- one binary swept across nursery sizes read 1034 / 1014 / 1007 /
# 1007 at 2 / 4 / 6 / 8 MB, 27 counts, while `loops_compiled` and
# `bridges_compiled` did not move. Suppressing the whole trace-time fold table
# moved it by one count and suppressing the folds this branch adds by none, so
# it is not reading those either.
#
# Width 8 covers both: the one-count host split, and the several counts a tree
# that allocates differently picks up on top of it -- this branch read 1011 on
# all three runners against a baseline of 1008. Anything wider than a tree's own
# allocation behaviour still gates, and `loops_compiled` and `bridges_compiled`
# stay gated exactly. Only the loop count answers whether the arm compiles.
# The ceiling is a function of N, so raising N refits it. pypy's execution here
# is almost all fixed cost -- doubling N moved it 0.035s to 0.039s -- while this
# backend pays roughly 27us per iteration, so the ratio tracks N nearly one for
Expand Down
15 changes: 15 additions & 0 deletions pyre/bench/synth/math_folds_hot.cranelift.jitstats
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
bridges_compiled=0
descr_set_absent=0
descr_set_ambiguous=0
descr_set_stale_absent=0
fbw_blackhole_adopted_multi_frame=0
fbw_blackhole_adopted_single_frame=0
fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=7
internal_compile_panics=0
loops_aborted=0
loops_compiled=7
retraces_compiled=0
15 changes: 15 additions & 0 deletions pyre/bench/synth/math_folds_hot.dynasm.jitstats
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
bridges_compiled=0
descr_set_absent=0
descr_set_ambiguous=0
descr_set_stale_absent=0
fbw_blackhole_adopted_multi_frame=0
fbw_blackhole_adopted_single_frame=0
fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=7
internal_compile_panics=0
loops_aborted=0
loops_compiled=7
retraces_compiled=0
128 changes: 128 additions & 0 deletions pyre/bench/synth/math_folds_hot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# pyre-check: max-pypy-ratio=5
# pyre-check: skip-cpython
# pyre-check: max-wasm-ratio=13
# Fitted to the highest reading observed plus 15%: darwin-arm64 reads
# 8.1-9.0x across five runs at load 11 and 11.3x during a load spike. The wasm
# side is slower for a structural reason rather than a regression: `pymath`
# reaches the platform libm on native and its pure-Rust `libm` fallback in the
# guest, and the guest also pays an errno-classifying wrapper the native build
# folds away. Measured on the same fold machinery and the same loop, wasm
# runs 2M folded `log` (which lowers to `x.ln()`) in 0.09s and 2M folded `exp`
# (which goes through `pymath`) in 0.24s. Both backends fold; only the
# operation underneath differs.
# The ratio is the detector here: losing a fold changes no jit-stats counter,
# because the residual it falls back to compiles the same loop. Measured on
# an idle darwin-arm64 box against pypy 0.33s — every fold 0.63s, the generic
# float/isclose folds suppressed 2.71s, all folds suppressed 33.4s. The gate
# sits between the first two.
# Every `math` primitive the walker folds, one hot loop per fold shape. A
# residual `bh_call_fn(builtin, NULL, x)` costs an argument tuple, a
# `W_FloatObject` allocation and a full builtin dispatch per iteration; each
# specialization instead unboxes the operands, emits the raw operation, and
# leaves the result box virtualizable.
#
# sqrt `try_walker_specialize_math_sqrt` — `x >= 0` and `isfinite(x)`
# pin the two `ll_math_sqrt` branches, then a pure
# `CALL_F(sqrt_nonneg_jit)` with no result guard.
# log/cos/sin `try_walker_specialize_math_log_trig` — same shape, one
# domain guard each.
# fabs `try_walker_specialize_math_fabs` — a single `FloatAbs`;
# `fabs` raises for no input, so it carries no domain guard.
# floor/ceil/ `try_walker_specialize_math_round_to_int` — guard the operand
# trunc into the signed machine range, round, `CastFloatToInt`.
# isclose `try_walker_specialize_math_isclose` — one pure `CALL_I`
# into a total helper, whose truth the branch's own guard
# already pins, so it carries no result guard.
# the rest `try_walker_specialize_math_float{1,2}` — one pure elidable
# `CALL_F` into the function's raw helper plus a finite-result
# guard. The helper reports every raising direction as NaN, so
# the guard alone carries the domain: `exp` overflowing,
# `atanh` outside (-1, 1) and `pow(0.0, -2.0)` all resume in
# the builtin and raise there.
#
# A rebound callable, a numeric subclass, or an operand outside the folded
# domain keeps the residual in every case.
import math

# Sized so pypy's own execution clears the measurement floor: below it the
# ratio gate divides by the floor and declines the baseline as too small.
SQRT_N = 8000000
LOG_TRIG_N = 1600000
FABS_N = 10000000
ROUND_N = 10000000
UNARY_N = 1500000
BINARY_N = 2000000
ISCLOSE_N = 4000000


def run_sqrt():
total = 0.0
for i in range(SQRT_N):
total += math.sqrt(float(i))
return total


def run_log_trig():
total = 0.0
for i in range(LOG_TRIG_N):
x = 1.0 + float(i % 97) / 97.0
total += math.log(x) + math.cos(x) + math.sin(x)
return total


def run_fabs():
total = 0.0
for i in range(FABS_N):
total += math.fabs(float(i) - 6000000.0)
return total


def run_round_to_int():
total = 0
for i in range(ROUND_N):
x = float(i) * 0.5 - 1000.0
total += math.floor(x) + math.ceil(x) + math.trunc(x)
return total


def run_unary():
total = 0.0
for i in range(UNARY_N):
x = float(i % 71) / 128.0
total += math.exp(x) + math.tan(x) + math.atan(x)
total += math.tanh(x) + math.log1p(x) + math.degrees(x)
return total


def run_binary():
total = 0.0
for i in range(BINARY_N):
x = 1.0 + float(i % 53) / 53.0
y = 1.0 + float(i % 31) / 31.0
total += math.pow(x, y) + math.fmod(x, y)
total += math.copysign(x, y) + math.atan2(x, y)
return total


def run_isclose():
# The result has to decide a branch and nothing else: a bool that escapes
# keeps the residual, because pinning it would bail on every re-entry with
# the other truth.
# The perturbation stays well inside the default `rel_tol=1e-09`, so the
# branch resolves the same way every iteration and the trace measures the
# fold rather than a bridge.
hits = 0
for i in range(ISCLOSE_N):
x = 1.0 + float(i % 97) / 1e12
if math.isclose(x, 1.0):
hits += 1
return hits


print(round(run_sqrt(), 6))
print(round(run_log_trig(), 6))
print(round(run_fabs(), 6))
print(run_round_to_int())
print(round(run_unary(), 6))
print(round(run_binary(), 6))
print(run_isclose())
15 changes: 15 additions & 0 deletions pyre/bench/synth/math_folds_hot.wasm.jitstats
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
bridges_compiled=0
descr_set_absent=0
descr_set_ambiguous=0
descr_set_stale_absent=0
fbw_blackhole_adopted_multi_frame=0
fbw_blackhole_adopted_single_frame=0
fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=7
internal_compile_panics=0
loops_aborted=0
loops_compiled=7
retraces_compiled=0
Loading
Loading