Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
ccd645a
list: give the unused typed strategy an empty array, not a block
youknowone Aug 5, 2026
4747bfa
optimizeopt: answer an unwritten field of a virtual with its typed zero
youknowone Aug 6, 2026
f4a0955
mapdict: keep builtin storage on user subclasses
youknowone Aug 6, 2026
bc9e9e0
jit: record integer zero-divisor raising arms
youknowone Aug 6, 2026
04a9964
mapdict: harden builtin subclass carriers
youknowone Aug 6, 2026
2bbfd4f
jit: initialize inline allocation scalar fields
youknowone Aug 6, 2026
46999ae
jit: record range zero-step raising arms
youknowone Aug 6, 2026
48af5b3
jit: record float zero-divisor raising arms
youknowone Aug 6, 2026
639da0e
jit: record bigint zero-divisor raising arms
youknowone Aug 6, 2026
ff85876
jit: record negative bigint shift raising arms
youknowone Aug 6, 2026
fcc4663
jit: scope FOR_ITER safety to escaping range loops
youknowone Aug 6, 2026
7b1f865
bench: re-record seven wasm jitstats baselines on the rebased base
youknowone Aug 6, 2026
a88c2c5
jit: diagnose FOR_ITER gate opcode declines
youknowone Aug 6, 2026
3dd1ae0
jit: gate FOR_ITER decline census allocation
youknowone Aug 6, 2026
cec1dea
jit: gate FOR_ITER decline census collection
youknowone Aug 6, 2026
2b05fbb
jit: guard numeric binary specialization classes
youknowone Aug 6, 2026
df70b3f
jit: retain context on specialized builtin raises
youknowone Aug 6, 2026
ef21e95
jit: skip redundant numeric class guards
youknowone Aug 6, 2026
c99178d
test: drive the numeric subclass fixture through the specialized pc
youknowone Aug 7, 2026
5edb6b6
Grow FOR_ITER regions through handler rejoins
youknowone Aug 7, 2026
4bfa882
Tighten escaping range append recognition
youknowone Aug 7, 2026
a433dac
Update range constructor loop jitstats
youknowone Aug 7, 2026
58768e2
jit: admit LIST_EXTEND in FOR_ITER bodies
youknowone Aug 7, 2026
1a57255
jit: admit call-bearing LIST_APPEND bodies in the FOR_ITER gate
youknowone Aug 7, 2026
1ef061f
jit: census in-flight FOR_ITER delivery outcomes
youknowone Aug 7, 2026
ff25238
bench: re-record nineteen jit-stats baselines
youknowone Aug 7, 2026
4f8b4ac
Trace traceback escape marking in exception attribute fold
youknowone Aug 7, 2026
e388ed7
Trace fresh container allocations in FOR_ITER callees
youknowone Aug 7, 2026
6d48af8
Admit tuple copies from exact lists during replay
youknowone Aug 7, 2026
e4bc476
Identify traceback walk bridge training
youknowone Aug 7, 2026
e9805e6
bench: add a synthetic fixture for the subscript inline's index operand
youknowone Aug 8, 2026
e663ee4
bench: re-record the wasm pickle terminal-raise baseline
youknowone Aug 8, 2026
918255b
jit: pair the vable static shadow write with a heap write-back
youknowone Aug 9, 2026
f4369cd
docs: list the two FOR_ITER gate diagnostics in gate-triage
youknowone Aug 9, 2026
0034ef3
bench: re-record ten jit-stats baselines after the rebase
youknowone Aug 9, 2026
da2ae41
jit: follow the InflightForiterBody field rename in the census
youknowone Aug 9, 2026
80860af
mapdict: split the layout predicate from the storage predicate
youknowone Aug 9, 2026
1fd5db4
_structseq: re-read the pinned class after the tuple allocation
youknowone Aug 9, 2026
d910ee2
jit: gate the in-flight FOR_ITER census key lookup on the census
youknowone Aug 9, 2026
c617cb3
jit: correct the exception descr group note on w_context
youknowone Aug 9, 2026
d2cc283
test: scan the loop-region fixture in two passes
youknowone Aug 9, 2026
1ccd1f7
test: cover synchronize_virtualizable_static
youknowone Aug 9, 2026
347844e
majit: exclude the identity slot from the static write-back bound
youknowone Aug 9, 2026
195f713
jit: read PYRE_FOR_ITER_GATE_DIAG through one accessor
youknowone Aug 9, 2026
311a7d8
jit: admit builtin subclass carriers in the mapdict storage helpers
youknowone Aug 10, 2026
4cd76f2
Revert "jit: admit call-bearing LIST_APPEND bodies in the FOR_ITER gate"
youknowone Aug 10, 2026
7243e57
majit: drop the narrowed virtualizable static synchronizer
youknowone Aug 10, 2026
f828b0c
bench: restore five jit-stats baselines the reverted gate had moved
youknowone Aug 10, 2026
51e9218
bench: restore the cranelift jit-stats baselines to match
youknowone Aug 10, 2026
1d84385
bench: restore the wasm jit-stats baselines to match
youknowone Aug 10, 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
72 changes: 66 additions & 6 deletions majit/majit-gc/src/trace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,12 @@ pub struct TypeInfo {
/// reads it through the materialized `TYPE_INFO` table's `infobits`
/// byte (gc.py:631-642).
pub is_object: bool,
/// Whether this type corresponds to an RPython classdef and therefore
/// contributes peers to `assign_inheritance_ids`. Some frontend-private
/// storage layouts still carry an `rclass.OBJECT` header (and must set
/// `is_object`) while sharing a classdef/vtable with another registered
/// type; those layouts must not perturb the classdef census.
pub has_subclass_range: bool,
/// CPython-internal object that remains an OBJECT-layout GC boundary but
/// is omitted from app-level `gc.get_objects()` enumeration. This models
/// 3.11+ interpreter frames: owning coroutine/traceback objects expose
Expand Down Expand Up @@ -426,6 +432,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: None,
is_object: false,
has_subclass_range: false,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand All @@ -449,6 +456,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: None,
is_object: false,
has_subclass_range: false,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand Down Expand Up @@ -491,6 +499,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: None,
is_object: false,
has_subclass_range: false,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand Down Expand Up @@ -520,6 +529,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: None,
is_object: true,
has_subclass_range: true,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand Down Expand Up @@ -548,6 +558,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: None,
is_object: true,
has_subclass_range: true,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand All @@ -574,6 +585,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: None,
is_object: true,
has_subclass_range: true,
hide_from_app_level_inspector: false,
parent: Some(parent_typeid),
subclassrange_min: 0,
Expand Down Expand Up @@ -609,6 +621,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: None,
is_object: true,
has_subclass_range: true,
hide_from_app_level_inspector: false,
parent: Some(parent_typeid),
subclassrange_min: 0,
Expand All @@ -634,6 +647,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: Some(trace_fn),
is_object: true,
has_subclass_range: true,
hide_from_app_level_inspector: false,
parent: Some(parent_typeid),
subclassrange_min: 0,
Expand All @@ -656,6 +670,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: None,
is_object: false,
has_subclass_range: false,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand All @@ -682,6 +697,7 @@ impl TypeInfo {
items_have_gc_ptrs,
custom_trace: None,
is_object: false,
has_subclass_range: false,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand All @@ -704,6 +720,7 @@ impl TypeInfo {
items_have_gc_ptrs: false,
custom_trace: Some(trace_fn),
is_object: false,
has_subclass_range: false,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand All @@ -713,6 +730,16 @@ impl TypeInfo {
}
}

/// Mark an `rclass.OBJECT`-shaped private storage layout that shares the
/// classdef/vtable of another registered type. It remains visible to
/// `check_is_object` and app-level GC inspection, but contributes no
/// duplicate peer to the subclass-range numbering.
pub fn object_layout_without_subclass_range(mut self) -> Self {
self.is_object = true;
self.has_subclass_range = false;
self
}

/// Create a varsize type info with a custom trace hook.
///
/// RPython parity: `rgc.register_custom_trace_hook` on a
Expand All @@ -732,6 +759,7 @@ impl TypeInfo {
items_have_gc_ptrs: false, // custom_trace handles ref tracing
custom_trace: Some(trace_fn),
is_object: false,
has_subclass_range: false,
hide_from_app_level_inspector: false,
parent: None,
subclassrange_min: 0,
Expand Down Expand Up @@ -857,7 +885,7 @@ impl TypeRegistry {

/// `gctypelayout.encode_type_shapes_now` parity
/// (gctypelayout.py:393-398): freezes the registry so subsequent
/// `register_type` calls panic and assigns each `is_object`
/// `register_type` calls panic and assigns each registered classdef
/// type its preorder `subclassrange_{min,max}`.
///
/// The bounds come from `rtyper/normalizecalls.py:373-389
Expand All @@ -875,7 +903,7 @@ impl TypeRegistry {
}
self.can_add_new_types = false;
self.assign_inheritance_ids();
// Refresh layout_table rows for is_object types whose
// Refresh layout_table rows for object types whose
// subclassrange_{min,max} just changed.
for (i, info) in self.entries.iter().enumerate() {
self.layout_table[i] = TypeEntry::from_type_info(info, i as u32);
Expand All @@ -885,7 +913,7 @@ impl TypeRegistry {
/// `rtyper/normalizecalls.py:373-389 assign_inheritance_ids` /
/// `TotalOrderSymbolic` parity (normalizecalls.py:302-354).
///
/// For each `is_object` type, builds
/// For each type representing a registered classdef, builds
/// `witness = reversed(MRO of cdef ids)` and pairs it with a
/// matching `witness + [MAX]` peer:
///
Expand All @@ -909,12 +937,12 @@ impl TypeRegistry {
/// list without relying on a magic numeric sentinel.
fn assign_inheritance_ids(&mut self) {
let n = self.entries.len();
// Build reversed-MRO witness for each is_object type.
// Build reversed-MRO witness for each registered classdef type.
// Witness for typeid T = [T_root, ..., T_grandparent, T_parent, T]
// where T_root is the topmost rclass.OBJECT-layout ancestor.
let mut witness: Vec<Option<Vec<WitnessElement>>> = vec![None; n];
for id in 0..n {
if !self.entries[id].is_object {
if !self.entries[id].has_subclass_range {
continue;
}
let mut mro: Vec<WitnessElement> = Vec::new();
Expand All @@ -927,7 +955,7 @@ impl TypeRegistry {
witness[id] = Some(mro);
}

// Build the peer list: each is_object type contributes a
// Build the peer list: each registered classdef contributes a
// `(witness, owner)` Min peer and a `(witness + [Max], owner)`
// Max peer. Sort lexicographically by witness; the peer's
// position becomes its preorder value (TotalOrderSymbolic
Expand Down Expand Up @@ -1163,6 +1191,38 @@ mod tests {
);
}

#[test]
fn test_private_object_layout_does_not_perturb_subclass_ranges() {
unsafe fn trace_nothing(_: usize, _: &mut dyn FnMut(*mut majit_ir::GcRef)) {}

let build = |with_private_layout| {
let mut reg = TypeRegistry::new();
let parent = reg.register(TypeInfo::object(16));
let child = reg.register(TypeInfo::object_subclass(16, parent));
if with_private_layout {
let private = reg.register(
TypeInfo::with_custom_trace(24, trace_nothing)
.object_layout_without_subclass_range(),
);
assert!(reg.get(private).is_object);
assert!(!reg.get(private).has_subclass_range);
}
reg.freeze_types();
(
(
reg.get(parent).subclassrange_min,
reg.get(parent).subclassrange_max,
),
(
reg.get(child).subclassrange_min,
reg.get(child).subclassrange_max,
),
)
};

assert_eq!(build(false), build(true));
}

#[test]
fn test_for_each_gc_ptr_varsize() {
// Variable-size object: base_size=8 (length field at offset 0),
Expand Down
12 changes: 12 additions & 0 deletions majit/majit-metainterp/src/optimizeopt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2459,6 +2459,18 @@ impl OptContext {
}
}

/// optimizer.py:528-534 new_const(fielddescr) — default value for an
/// unset field of a freshly allocated virtual struct. GC allocations are
/// zero-filled, so optimizeopt can answer the read without materialising
/// the virtual.
pub fn new_const(&mut self, field_type: Type) -> OpRef {
match field_type {
Type::Int | Type::Void => self.make_constant_int(0),
Type::Ref => self.make_constant_ref(GcRef::NULL),
Type::Float => self.make_constant_float(0.0),
}
}

/// vstring.py:110-119 / 171-175 / 251-253 / 281-295
/// Per-subclass getstrlen() dispatch — returns a cached lgtop OpRef if
/// available, or computes/emits the length and caches in StrPtrInfo.lgtop.
Expand Down
33 changes: 32 additions & 1 deletion majit/majit-metainterp/src/optimizeopt/virtualize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1036,8 +1036,8 @@ impl OptVirtualize {
// virtual fields but can be resolved from the SizeDescr vtable.
// RPython doesn't need this because GUARD_CLASS reads the class
// directly from the object, not via a separate field read.
let is_typeptr = op.with_field_descr(|fd| fd.is_typeptr()).unwrap_or(false);
if field_val.is_none() && matches!(op.opcode, majit_ir::OpCode::GetfieldGcI) {
let is_typeptr = op.with_field_descr(|fd| fd.is_typeptr()).unwrap_or(false);
if is_typeptr {
let vtable = match &info {
PtrInfo::Virtual(vinfo) => vinfo
Expand Down Expand Up @@ -3943,6 +3943,37 @@ mod tests {
assert!(result.is_empty(), "NEW should be removed");
}

#[test]
fn test_fresh_virtual_unwritten_fields_are_typed_zero() {
// virtualize.py:184-190 optimize_GETFIELD_GC_I: GC allocations are
// zero-filled, so an unset virtual field folds through
// optimizer.new_const(fielddescr) without forcing the allocation.
for (get_opcode, field_descr) in [
(OpCode::GetfieldGcI, field_descr(0)),
(OpCode::GetfieldGcR, ref_field_descr(0)),
(OpCode::GetfieldGcF, float_field_descr(0)),
] {
let mut ops = vec![
Op::with_descr(OpCode::NewWithVtable, &[], size_descr(1)),
Op::with_descr(
get_opcode,
&[crate::history::test_support::rooted_resop_operand(
Type::Ref,
0,
)],
field_descr,
),
];
assign_positions(&mut ops);
let result = run_pass(&ops);
assert!(
result.is_empty(),
"{get_opcode:?} of an unset fresh field forced the virtual: {:?}",
result.iter().map(|op| op.opcode).collect::<Vec<_>>()
);
}
}

Comment on lines +3946 to +3976

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the typed zero value, not only operation removal.

result.is_empty() confirms only that the operations were removed. It does not confirm that the result contains Value::Int(0), Value::Ref(GcRef::NULL), or Value::Float(0.0). A regression that drops the result or assigns the wrong value type would still pass. Expose the forwarded value in the test helper and assert the expected value for each opcode.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@majit/majit-metainterp/src/optimizeopt/virtualize.rs` around lines 3825 -
3855, Update test_fresh_virtual_unwritten_fields_are_typed_zero and its run_pass
test helper to expose the value forwarded when the getfield operation is
removed. Assert the expected typed zero for each opcode: Value::Int(0),
Value::Ref(GcRef::NULL), or Value::Float(0.0), while retaining the existing
assertion that no operations remain.

#[test]
fn test_setfield_getfield_on_virtual() {
// p0 = new_with_vtable(descr=size1)
Expand Down
4 changes: 2 additions & 2 deletions pyre/bench/synth/enumerate_bignum_start.cranelift.jitstats
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=0
guard_failures=1
internal_compile_panics=0
loops_aborted=0
loops_compiled=0
loops_compiled=1
4 changes: 2 additions & 2 deletions pyre/bench/synth/enumerate_bignum_start.dynasm.jitstats
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=0
guard_failures=1
internal_compile_panics=0
loops_aborted=0
loops_compiled=0
loops_compiled=1
4 changes: 2 additions & 2 deletions pyre/bench/synth/enumerate_bignum_start.wasm.jitstats
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=0
guard_failures=1
internal_compile_panics=0
loops_aborted=0
loops_compiled=0
loops_compiled=1
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=812
guard_failures=811
internal_compile_panics=0
loops_aborted=0
loops_compiled=8
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ fbw_rolled_back_with_effects=0
fbw_store_journal_rollback_failed=0
field_pos_attached_misplaced=0
field_pos_spec_misplaced=0
guard_failures=812
guard_failures=811
internal_compile_panics=0
loops_aborted=0
loops_compiled=8
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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=1
internal_compile_panics=0
loops_aborted=0
loops_compiled=1
14 changes: 14 additions & 0 deletions pyre/bench/synth/foriter_inlined_callee_build_list.dynasm.jitstats
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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=1
internal_compile_panics=0
loops_aborted=0
loops_compiled=1
18 changes: 18 additions & 0 deletions pyre/bench/synth/foriter_inlined_callee_build_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# A FOR_ITER trace must keep compiling when an inlined callee allocates a
# fresh list and copies it into a tuple. Both fresh allocations are replay-safe
# and have no reason to abort the caller's loop trace.


def chain(item):
out = []
return len(tuple(out)) + (item - item)


def drive(n):
last = -1
for i in range(n):
last = chain(i)
return last


print(drive(20000))
14 changes: 14 additions & 0 deletions pyre/bench/synth/foriter_inlined_callee_build_list.wasm.jitstats
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
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=1
internal_compile_panics=0
loops_aborted=0
loops_compiled=1
Loading
Loading