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
1 change: 1 addition & 0 deletions pyre/bench/synth/comprehension_object_append_hot.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# pyre-check: max-pypy-ratio=40
# An inlined list comprehension whose LIST_APPEND element lands in a list
# Object-strategy (tuple / None / str / dict / f-string) folds through the #171
# orthodox append. Its Object arm stores a GC ref and runs list_write_barrier,
Expand Down
190 changes: 190 additions & 0 deletions pyre/bench/synth/inlined_helper_arith_hot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# pyre-check: max-pypy-ratio=12
# One-line arithmetic helpers called from a hot `for` loop body. The inline
# lever gates a FOR_ITER-in-flight callee on `fbw_callee_body_replay_safety`:
# a body whose only residual is a BINARY_OP the walker will specialize to a
# native op leaves nothing to replay and is admitted, anything else is Dirty
# and the whole callee stays a per-iteration residual call.
#
# The accepted tag set is every tag a specialization table lowers with no
# runtime decline left. Add / Subtract / Multiply are in both the int
# (IntAddOvf / IntSubOvf / IntMulOvf) and float (FloatAdd / FloatSub /
# FloatMul) tables; And / Or / Xor are in the int table only. Both tables key
# each in-place tag to the same arm as its plain form, so `a += i` is admitted
# exactly like `a + i`. Each loop below pins one tag, so a tag dropping out of
# the set shows up here as the callee reappearing as a residual and the ratio
# blowing past the gate.
#
# The divide / remainder / shift / power tags are deliberately NOT in the set
# — each can still decline (zero divisor, out-of-range shift, nan/inf base) and
# leave the residual behind — and they are kept out of this file so the gate
# measures the admitted paths rather than cases that are meant to stay slow.
# Callees are named directly rather than passed in, so the call site keeps a
# constant callee. Output verified against CPython/PyPy.
N = 200000


def add_body(a, i):
return a + i


def sub_body(a, i):
return a - i


def mul_body(a, i):
return a + i * 2


def iadd_body(a, i):
a += i
return a


def isub_body(a, i):
a -= i
return a


def imul_body(a, i):
b = i
b *= 2
return a + b


def and_body(a, i):
return a + (i & 255)


def or_body(a, i):
return a + (i | 1)


def xor_body(a, i):
return a + (i ^ 5)


def ibit_body(a, i):
b = i
b &= 255
b |= 1
b ^= 5
return a + b


def mixed_body(a, i):
return a + i * 3 - 1


def float_body(a, i):
return a + i * 0.5


def float_iadd_body(a, i):
a += i * 0.25
return a


def run_add(n):
s = 0
for i in range(n):
s = add_body(s, i)
return s


def run_sub(n):
s = 0
for i in range(n):
s = sub_body(s, i)
return s


def run_mul(n):
s = 0
for i in range(n):
s = mul_body(s, i)
return s


def run_iadd(n):
s = 0
for i in range(n):
s = iadd_body(s, i)
return s


def run_isub(n):
s = 0
for i in range(n):
s = isub_body(s, i)
return s


def run_imul(n):
s = 0
for i in range(n):
s = imul_body(s, i)
return s


def run_and(n):
s = 0
for i in range(n):
s = and_body(s, i)
return s


def run_or(n):
s = 0
for i in range(n):
s = or_body(s, i)
return s


def run_xor(n):
s = 0
for i in range(n):
s = xor_body(s, i)
return s


def run_ibit(n):
s = 0
for i in range(n):
s = ibit_body(s, i)
return s


def run_mixed(n):
s = 0
for i in range(n):
s = mixed_body(s, i)
return s


def run_float(n):
s = 0.0
for i in range(n):
s = float_body(s, i)
return s


def run_float_iadd(n):
s = 0.0
for i in range(n):
s = float_iadd_body(s, i)
return s


print(run_add(N))
print(run_sub(N))
print(run_mul(N))
print(run_iadd(N))
print(run_isub(N))
print(run_imul(N))
print(run_and(N))
print(run_or(N))
print(run_xor(N))
print(run_ibit(N))
print(run_mixed(N))
print(run_float(N))
print(run_float_iadd(N))
92 changes: 92 additions & 0 deletions pyre/bench/synth/list_append_write_barrier_gc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# GC stress for the in-place Object-append write barrier the tracer does NOT
# record. `w_list_append`'s in-place arm stores a GC ref into the items block
# and runs `list_write_barrier`; when the block is GC-managed the backend GC
# rewrite already marks that store with COND_CALL_GC_WB_ARRAY, so the walker
# drops the barrier residual instead of leaving a second barrier in the loop
# (rewrite.py:936-944; pyjitpl records no write barrier at all,
# executor.py:446). These cases pin what that suppression must not break: an
# OLD list whose block is already promoted receiving YOUNG elements. If the
# store went unremembered, a minor collection would never scan the slot and the
# element would be freed or read back stale. Run with PYRE_GC_ITEMSBLOCK=0 too
# — there the block is std::alloc with no GC header, the barrier on the
# W_ListObject is load-bearing, and the walker must keep emitting it.
# Output verified against CPython/PyPy.
N = 4000
CHURN = 300


def churn(k):
# Allocation pressure to drive minor collections between appends.
junk = None
for i in range(k):
junk = (i, [i], {i: i})
return junk


def old_list_young_appends():
r = []
for i in range(N):
r.append((i, i))
churn(CHURN * 20)
# r and its block are old now; append fresh young tuples.
for i in range(N):
r.append((i + N, i + N))
if i % 200 == 0:
churn(CHURN)
assert len(r) == 2 * N, len(r)
total = 0
for a, b in r:
assert a == b, (a, b)
total += a
return total


def interleaved_growth():
lists = [[] for _ in range(16)]
total = 0
for i in range(N):
lists[i % 16].append((i,))
if i % 100 == 0:
churn(CHURN)
for lst in lists:
for (v,) in lst:
total += v
return total, sum(len(x) for x in lists)


def strings_and_dicts():
# Non-tuple Object-strategy elements: str and dict payloads that the
# collector must keep reachable through the appended slots.
r = []
for i in range(N):
r.append(str(i))
if i % 250 == 0:
churn(CHURN)
joined = 0
for i, s in enumerate(r):
assert s == str(i), (i, s)
joined += len(s)
return joined


def none_then_objects():
r = [None] * 8
r.clear()
for i in range(N):
r.append(None if i % 2 else [i])
if i % 300 == 0:
churn(CHURN)
total = 0
for i, v in enumerate(r):
if i % 2:
assert v is None, (i, v)
else:
assert v == [i], (i, v)
total += v[0]
return total


print(old_list_young_appends())
print(interleaved_growth())
print(strings_and_dicts())
print(none_then_objects())
12 changes: 8 additions & 4 deletions pyre/pyre-interpreter/src/jit_fnaddr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1219,10 +1219,14 @@ pub fn jit_trace_fnaddrs() -> Vec<(&'static str, i64)> {
);
// The #171 object-append fold descends `w_list_append` and folds the
// store leaves to native ops, leaving `list_write_barrier(l)` as a
// residual call (the off-GC ItemsBlock is reached by the collector only
// through the remembered W_ListObject). Register it so the codewriter
// resolves the residual to a runtime-patchable address instead of a
// `symbolic_fnaddr_for_path` hash the inline sub-walk must decline.
// residual call. Register it so the codewriter resolves the residual to a
// runtime-patchable address instead of a `symbolic_fnaddr_for_path` hash
// the inline sub-walk must decline. The address is also what the walker
// matches on to drop the residual entirely when the backend GC rewrite
// already covers the store (`FbwWalkMode::append_inplace_wb_covered`);
// with an off-GC ItemsBlock the residual stays, because there the
// collector reaches the block's slots only through the remembered
// `W_ListObject`.
push_alias_pair(
&mut entries,
"pyre_object::listobject::list_write_barrier",
Expand Down
8 changes: 5 additions & 3 deletions pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1411,7 +1411,8 @@ pub(crate) enum CalleeReplaySafety {
/// there, which this straight-line scan cannot name.
pub(crate) fn fbw_callee_body_replay_safety(
body_code: &[u8],
args_all_numeric: bool,
args_all_exact_numeric: bool,
args_all_exact_plain_int: bool,
num_regs_i: usize,
constants_i: &[i64],
callee_descr_refs: &[DescrRef],
Expand Down Expand Up @@ -1465,9 +1466,10 @@ pub(crate) fn fbw_callee_body_replay_safety(
|| ei.check_is_elidable()
|| ei.extraeffect == majit_ir::ExtraEffect::LoopInvariant;
if !provably_side_effect_free
&& !residual_call_is_specialized_plain_int_add(
&& !residual_call_is_specialized_plain_numeric_binop(
body_code,
args_all_numeric,
args_all_exact_numeric,
args_all_exact_plain_int,
&d,
num_regs_i,
constants_i,
Expand Down
Loading
Loading