diff --git a/majit/majit-gc/src/collector.rs b/majit/majit-gc/src/collector.rs index fb2e09b1118..bee00e6feab 100644 --- a/majit/majit-gc/src/collector.rs +++ b/majit/majit-gc/src/collector.rs @@ -7135,15 +7135,35 @@ impl MiniMarkGC { self.oldgen_nonmoving_active = true; self.oldgen_nonmoving_young_marks.clear(); - if self.gc_state == GcState::Scanning { - self.start_incremental_cycle(); - } + // `do_collect_full` / incminimark.py `gc_step_until` first finishes + // an in-progress major, then starts a fresh cycle whose root snapshot + // is taken at the explicit collection boundary. The non-moving twin + // owes the same ordering: merely finishing an older cycle can retain + // an object which became unreachable after that cycle began. + if self.gc_state != GcState::Scanning { + self.gc_step_until_scanning(); + self.clear_oldgen_nonmoving_young_marks(); + } + self.start_incremental_cycle(); // Keep this oldgen-only entry stop-the-world: it may enter while // MARKING or SWEEPING, but always returns after the complete cycle. self.gc_step_until_scanning(); // Strictly-last: clear VISITED on every young object greyed this cycle // (the oldgen sweep already cleared it on old-gen survivors). + self.clear_oldgen_nonmoving_young_marks(); + self.oldgen_nonmoving_active = false; + + // This entry has no upstream counterpart, but it is a public collection + // entry point and it can queue mirrors, so it owes the same schedule. + self.rrc_invoke_callback(); + } + + /// Clear the nursery VISITED bits accumulated by one non-moving major. + /// A fresh explicit cycle must begin with those bits clear or its marker + /// will mistake the preceding cycle's young survivors for already-traced + /// objects and skip their old-generation children. + fn clear_oldgen_nonmoving_young_marks(&mut self) { let marks = std::mem::take(&mut self.oldgen_nonmoving_young_marks); for addr in marks { // Nothing moved and nothing young was freed, so each addr is still @@ -7154,11 +7174,6 @@ impl MiniMarkGC { unsafe { (*hdr).clear_flag(flags::VISITED) }; } } - self.oldgen_nonmoving_active = false; - - // This entry has no upstream counterpart, but it is a public collection - // entry point and it can queue mirrors, so it owes the same schedule. - self.rrc_invoke_callback(); } fn gc_step_until_scanning(&mut self) { @@ -13811,6 +13826,32 @@ cache size\t: 8192 kB\n"; gc.roots.clear(); } + /// An explicit non-moving collection must use roots observed at the call, + /// not merely finish an incremental cycle whose root snapshot predates a + /// release. This is the non-moving counterpart of `do_collect_full`'s + /// initial `gc_step_until_scanning_with_minors` followed by a fresh cycle. + #[test] + fn nonmoving_major_starts_fresh_after_finishing_an_in_progress_cycle() { + let mut gc = test_gc(4096); + let tid = gc.register_type(TypeInfo::simple(16)); + let object = gc.alloc_in_oldgen_clear(tid, GcHeader::SIZE + 16); + let mut root = object; + unsafe { gc.roots.add(&mut root) }; + + // Seed the in-progress cycle while `object` is live, then release it. + gc.major_collection_step(); + assert_ne!(gc.gc_state, GcState::Scanning); + gc.roots.clear(); + + gc.do_collect_oldgen_nonmoving(); + + assert_eq!( + gc.oldgen.object_count(), + 0, + "the explicit collection's fresh root snapshot must sweep object" + ); + } + /// A non-moving major must run `invalidate_old_weakrefs` (reads the /// target's VISITED) BEFORE the nursery-VISITED clear, so an old weakref /// whose target is a live nursery object is kept, not spuriously nulled. diff --git a/majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs b/majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs index b7232ca4513..e5547ff09ff 100644 --- a/majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs +++ b/majit/majit-metainterp/tests/jit_interp_label_entry_deopt_resume.rs @@ -42,6 +42,11 @@ fn count_program(n: i64) -> Vec { } static COMPILES: AtomicU32 = AtomicU32::new(0); +// Both tests install the same capture-free compile callback, hence share the +// process-global counter. Rust runs sibling tests concurrently; keep each +// reset/run/read interval indivisible just as +// `jit_interp_halt_arm_post_loop_expression::run` does for its probe. +static PROBE_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); struct VmState { regs: Vec, @@ -151,6 +156,7 @@ fn clean_interp(program: &Bytecode) -> i64 { #[test] fn a_label_entered_deopt_resumes_at_the_green_pc() { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); for n in [1_000i64, 1_001] { let program = count_program(n); COMPILES.store(0, Ordering::Relaxed); @@ -179,6 +185,7 @@ fn a_label_entered_deopt_resumes_at_the_green_pc() { /// attributed to the compiled tier rather than to the bytecode or the fixture. #[test] fn the_same_machine_without_tracing_answers_n() { + let _guard = PROBE_LOCK.lock().unwrap_or_else(|e| e.into_inner()); for n in [1_000i64, 1_001] { let program = count_program(n); COMPILES.store(0, Ordering::Relaxed); diff --git a/pyre/bench/synth/bound_method_builtin_fold.cranelift.jitstats b/pyre/bench/synth/bound_method_builtin_fold.cranelift.jitstats index f04d2e4f7e1..c28ea9b2862 100644 --- a/pyre/bench/synth/bound_method_builtin_fold.cranelift.jitstats +++ b/pyre/bench/synth/bound_method_builtin_fold.cranelift.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=468 +guard_failures=687 internal_compile_panics=0 loops_aborted=0 loops_compiled=8 diff --git a/pyre/bench/synth/bound_method_builtin_fold.dynasm.jitstats b/pyre/bench/synth/bound_method_builtin_fold.dynasm.jitstats index f04d2e4f7e1..c28ea9b2862 100644 --- a/pyre/bench/synth/bound_method_builtin_fold.dynasm.jitstats +++ b/pyre/bench/synth/bound_method_builtin_fold.dynasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=468 +guard_failures=687 internal_compile_panics=0 loops_aborted=0 loops_compiled=8 diff --git a/pyre/bench/synth/bound_method_builtin_fold.wasm.jitstats b/pyre/bench/synth/bound_method_builtin_fold.wasm.jitstats index f04d2e4f7e1..c28ea9b2862 100644 --- a/pyre/bench/synth/bound_method_builtin_fold.wasm.jitstats +++ b/pyre/bench/synth/bound_method_builtin_fold.wasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=468 +guard_failures=687 internal_compile_panics=0 loops_aborted=0 loops_compiled=8 diff --git a/pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats b/pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats index b660d9fc35b..df00680270e 100644 --- a/pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats +++ b/pyre/bench/synth/comprehension_object_append_hot.cranelift.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=14 +bridges_compiled=10 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2810 +guard_failures=2014 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats b/pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats index b660d9fc35b..df00680270e 100644 --- a/pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats +++ b/pyre/bench/synth/comprehension_object_append_hot.dynasm.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=14 +bridges_compiled=10 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2810 +guard_failures=2014 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/comprehension_object_append_hot.wasm.jitstats b/pyre/bench/synth/comprehension_object_append_hot.wasm.jitstats index b660d9fc35b..df00680270e 100644 --- a/pyre/bench/synth/comprehension_object_append_hot.wasm.jitstats +++ b/pyre/bench/synth/comprehension_object_append_hot.wasm.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=14 +bridges_compiled=10 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2810 +guard_failures=2014 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/exception_catching_frame_tb_node.cranelift.jitstats b/pyre/bench/synth/exception_catching_frame_tb_node.cranelift.jitstats index 070cd5e4503..ce07d173c11 100644 --- a/pyre/bench/synth/exception_catching_frame_tb_node.cranelift.jitstats +++ b/pyre/bench/synth/exception_catching_frame_tb_node.cranelift.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=3 +bridges_compiled=2 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=601 +guard_failures=401 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 +retraces_compiled=0 diff --git a/pyre/bench/synth/exception_catching_frame_tb_node.dynasm.jitstats b/pyre/bench/synth/exception_catching_frame_tb_node.dynasm.jitstats index 070cd5e4503..ce07d173c11 100644 --- a/pyre/bench/synth/exception_catching_frame_tb_node.dynasm.jitstats +++ b/pyre/bench/synth/exception_catching_frame_tb_node.dynasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=3 +bridges_compiled=2 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=601 +guard_failures=401 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 +retraces_compiled=0 diff --git a/pyre/bench/synth/exception_catching_frame_tb_node.wasm.jitstats b/pyre/bench/synth/exception_catching_frame_tb_node.wasm.jitstats index 9e762300eb2..ce07d173c11 100644 --- a/pyre/bench/synth/exception_catching_frame_tb_node.wasm.jitstats +++ b/pyre/bench/synth/exception_catching_frame_tb_node.wasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=3 +bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=601 +guard_failures=401 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.cranelift.jitstats b/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.cranelift.jitstats index 64fd594f038..037e11e5b13 100644 --- a/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.cranelift.jitstats +++ b/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.cranelift.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=807 +guard_failures=1007 internal_compile_panics=0 loops_aborted=0 loops_compiled=11 diff --git a/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.dynasm.jitstats b/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.dynasm.jitstats index 64fd594f038..037e11e5b13 100644 --- a/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.dynasm.jitstats +++ b/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.dynasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=807 +guard_failures=1007 internal_compile_panics=0 loops_aborted=0 loops_compiled=11 diff --git a/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats b/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats index 64fd594f038..037e11e5b13 100644 --- a/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats +++ b/pyre/bench/synth/exception_escape_hot_callee_tb_node_once.wasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=807 +guard_failures=1007 internal_compile_panics=0 loops_aborted=0 loops_compiled=11 diff --git a/pyre/bench/synth/exception_inline_callee_tb_frames.cranelift.jitstats b/pyre/bench/synth/exception_inline_callee_tb_frames.cranelift.jitstats index cc151fbfda3..3bbe6e74de3 100644 --- a/pyre/bench/synth/exception_inline_callee_tb_frames.cranelift.jitstats +++ b/pyre/bench/synth/exception_inline_callee_tb_frames.cranelift.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=604 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/exception_inline_callee_tb_frames.dynasm.jitstats b/pyre/bench/synth/exception_inline_callee_tb_frames.dynasm.jitstats index cc151fbfda3..3bbe6e74de3 100644 --- a/pyre/bench/synth/exception_inline_callee_tb_frames.dynasm.jitstats +++ b/pyre/bench/synth/exception_inline_callee_tb_frames.dynasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=604 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats b/pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats index cc151fbfda3..3bbe6e74de3 100644 --- a/pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats +++ b/pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=2 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=604 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/exception_reentry_guard_finally_residual.cranelift.jitstats b/pyre/bench/synth/exception_reentry_guard_finally_residual.cranelift.jitstats index 2f7eeec1bd3..ccee4a3cdd6 100644 --- a/pyre/bench/synth/exception_reentry_guard_finally_residual.cranelift.jitstats +++ b/pyre/bench/synth/exception_reentry_guard_finally_residual.cranelift.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=12 +bridges_compiled=11 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2461 +guard_failures=2261 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 +retraces_compiled=0 diff --git a/pyre/bench/synth/exception_reentry_guard_finally_residual.dynasm.jitstats b/pyre/bench/synth/exception_reentry_guard_finally_residual.dynasm.jitstats index 2f7eeec1bd3..ccee4a3cdd6 100644 --- a/pyre/bench/synth/exception_reentry_guard_finally_residual.dynasm.jitstats +++ b/pyre/bench/synth/exception_reentry_guard_finally_residual.dynasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=12 +bridges_compiled=11 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2461 +guard_failures=2261 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 +retraces_compiled=0 diff --git a/pyre/bench/synth/exception_reentry_guard_finally_residual.wasm.jitstats b/pyre/bench/synth/exception_reentry_guard_finally_residual.wasm.jitstats index 4b452e10424..ccee4a3cdd6 100644 --- a/pyre/bench/synth/exception_reentry_guard_finally_residual.wasm.jitstats +++ b/pyre/bench/synth/exception_reentry_guard_finally_residual.wasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=12 +bridges_compiled=11 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=2461 +guard_failures=2261 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/exception_traceback_frame_lineno.cranelift.jitstats b/pyre/bench/synth/exception_traceback_frame_lineno.cranelift.jitstats index 679a894638a..5fa5e506d59 100644 --- a/pyre/bench/synth/exception_traceback_frame_lineno.cranelift.jitstats +++ b/pyre/bench/synth/exception_traceback_frame_lineno.cranelift.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=5 +bridges_compiled=6 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=814 +guard_failures=1024 internal_compile_panics=0 loops_aborted=0 loops_compiled=17 diff --git a/pyre/bench/synth/exception_traceback_frame_lineno.dynasm.jitstats b/pyre/bench/synth/exception_traceback_frame_lineno.dynasm.jitstats index 679a894638a..5fa5e506d59 100644 --- a/pyre/bench/synth/exception_traceback_frame_lineno.dynasm.jitstats +++ b/pyre/bench/synth/exception_traceback_frame_lineno.dynasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=5 +bridges_compiled=6 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=814 +guard_failures=1024 internal_compile_panics=0 loops_aborted=0 loops_compiled=17 diff --git a/pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats b/pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats index 679a894638a..5fa5e506d59 100644 --- a/pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats +++ b/pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=5 +bridges_compiled=6 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=814 +guard_failures=1024 internal_compile_panics=0 loops_aborted=0 loops_compiled=17 diff --git a/pyre/bench/synth/exception_traceback_lineno_chain.cranelift.jitstats b/pyre/bench/synth/exception_traceback_lineno_chain.cranelift.jitstats index 02a1110d883..f5ade4531d3 100644 --- a/pyre/bench/synth/exception_traceback_lineno_chain.cranelift.jitstats +++ b/pyre/bench/synth/exception_traceback_lineno_chain.cranelift.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=606 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=5 diff --git a/pyre/bench/synth/exception_traceback_lineno_chain.dynasm.jitstats b/pyre/bench/synth/exception_traceback_lineno_chain.dynasm.jitstats index 02a1110d883..f5ade4531d3 100644 --- a/pyre/bench/synth/exception_traceback_lineno_chain.dynasm.jitstats +++ b/pyre/bench/synth/exception_traceback_lineno_chain.dynasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=606 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=5 diff --git a/pyre/bench/synth/exception_traceback_lineno_chain.wasm.jitstats b/pyre/bench/synth/exception_traceback_lineno_chain.wasm.jitstats index 02a1110d883..f5ade4531d3 100644 --- a/pyre/bench/synth/exception_traceback_lineno_chain.wasm.jitstats +++ b/pyre/bench/synth/exception_traceback_lineno_chain.wasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=606 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=5 diff --git a/pyre/bench/synth/foriter_user_iter_kept_stack.cranelift.jitstats b/pyre/bench/synth/foriter_user_iter_kept_stack.cranelift.jitstats index d3045567d7a..9027589c310 100644 --- a/pyre/bench/synth/foriter_user_iter_kept_stack.cranelift.jitstats +++ b/pyre/bench/synth/foriter_user_iter_kept_stack.cranelift.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2421 +guard_failures=2427 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/foriter_user_iter_kept_stack.dynasm.jitstats b/pyre/bench/synth/foriter_user_iter_kept_stack.dynasm.jitstats index d3045567d7a..9027589c310 100644 --- a/pyre/bench/synth/foriter_user_iter_kept_stack.dynasm.jitstats +++ b/pyre/bench/synth/foriter_user_iter_kept_stack.dynasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2421 +guard_failures=2427 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/foriter_user_iter_kept_stack.wasm.jitstats b/pyre/bench/synth/foriter_user_iter_kept_stack.wasm.jitstats index d3045567d7a..9027589c310 100644 --- a/pyre/bench/synth/foriter_user_iter_kept_stack.wasm.jitstats +++ b/pyre/bench/synth/foriter_user_iter_kept_stack.wasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2421 +guard_failures=2427 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.cranelift.jitstats b/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.cranelift.jitstats index 64ed64f79bf..4bcc8fd0ff6 100644 --- a/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.cranelift.jitstats +++ b/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.cranelift.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=6 +bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=1085 +guard_failures=1038 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.dynasm.jitstats b/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.dynasm.jitstats index 64b436badac..4bcc8fd0ff6 100644 --- a/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.dynasm.jitstats +++ b/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.dynasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=6 +bridges_compiled=5 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1054 +guard_failures=1038 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 +retraces_compiled=0 diff --git a/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats b/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats index 50ab5fb5140..4bcc8fd0ff6 100644 --- a/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats +++ b/pyre/bench/synth/gc_bug_bridge_flavor_traceback_names.wasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=6 +bridges_compiled=5 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=1054 +guard_failures=1038 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/gc_iterator_source_drop.cranelift.jitstats b/pyre/bench/synth/gc_iterator_source_drop.cranelift.jitstats index 31a9aed9445..285f31fbc4f 100644 --- a/pyre/bench/synth/gc_iterator_source_drop.cranelift.jitstats +++ b/pyre/bench/synth/gc_iterator_source_drop.cranelift.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=5 +bridges_compiled=7 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1015 +guard_failures=1416 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/gc_iterator_source_drop.dynasm.jitstats b/pyre/bench/synth/gc_iterator_source_drop.dynasm.jitstats index 31a9aed9445..285f31fbc4f 100644 --- a/pyre/bench/synth/gc_iterator_source_drop.dynasm.jitstats +++ b/pyre/bench/synth/gc_iterator_source_drop.dynasm.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=5 +bridges_compiled=7 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1015 +guard_failures=1416 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats b/pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats index 31a9aed9445..9fab830575f 100644 --- a/pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats +++ b/pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=5 +bridges_compiled=6 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1015 +guard_failures=4880 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats index 2ebe9ec52ca..570ed4af4be 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.cranelift.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=5 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 +retraces_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats index 2ebe9ec52ca..570ed4af4be 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.dynasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=5 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 +retraces_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats index 2ebe9ec52ca..570ed4af4be 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent.wasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=5 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 +retraces_compiled=0 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.cranelift.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.cranelift.jitstats index d7b142df687..66ac1bea494 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.cranelift.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.cranelift.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=5 +guard_failures=26 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.dynasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.dynasm.jitstats index d7b142df687..66ac1bea494 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.dynasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.dynasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=5 +guard_failures=26 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.wasm.jitstats b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.wasm.jitstats index d7b142df687..66ac1bea494 100644 --- a/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.wasm.jitstats +++ b/pyre/bench/synth/getframe_root_loop_force_blackhole_crn_nonidempotent_declined.wasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=5 +guard_failures=26 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/inlined_helper_mutation.cranelift.jitstats b/pyre/bench/synth/inlined_helper_mutation.cranelift.jitstats index 9adec961e20..79c4f12c921 100644 --- a/pyre/bench/synth/inlined_helper_mutation.cranelift.jitstats +++ b/pyre/bench/synth/inlined_helper_mutation.cranelift.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=3 +guard_failures=4 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/inlined_helper_mutation.dynasm.jitstats b/pyre/bench/synth/inlined_helper_mutation.dynasm.jitstats index 9adec961e20..79c4f12c921 100644 --- a/pyre/bench/synth/inlined_helper_mutation.dynasm.jitstats +++ b/pyre/bench/synth/inlined_helper_mutation.dynasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=3 +guard_failures=4 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/inlined_helper_mutation.wasm.jitstats b/pyre/bench/synth/inlined_helper_mutation.wasm.jitstats index 9adec961e20..0cae0b48996 100644 --- a/pyre/bench/synth/inlined_helper_mutation.wasm.jitstats +++ b/pyre/bench/synth/inlined_helper_mutation.wasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=3 +guard_failures=5 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/kept_stack_deep_var_shortcircuit.cranelift.jitstats b/pyre/bench/synth/kept_stack_deep_var_shortcircuit.cranelift.jitstats index c0f95f739f3..6b5c7bcedd2 100644 --- a/pyre/bench/synth/kept_stack_deep_var_shortcircuit.cranelift.jitstats +++ b/pyre/bench/synth/kept_stack_deep_var_shortcircuit.cranelift.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2027 +guard_failures=2046 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/kept_stack_deep_var_shortcircuit.dynasm.jitstats b/pyre/bench/synth/kept_stack_deep_var_shortcircuit.dynasm.jitstats index c0f95f739f3..6b5c7bcedd2 100644 --- a/pyre/bench/synth/kept_stack_deep_var_shortcircuit.dynasm.jitstats +++ b/pyre/bench/synth/kept_stack_deep_var_shortcircuit.dynasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2027 +guard_failures=2046 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats b/pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats index c0f95f739f3..6b5c7bcedd2 100644 --- a/pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats +++ b/pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=2027 +guard_failures=2046 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_append_funcentry_helper.cranelift.jitstats b/pyre/bench/synth/list_append_funcentry_helper.cranelift.jitstats index 5130e30b5e8..becd33b254a 100644 --- a/pyre/bench/synth/list_append_funcentry_helper.cranelift.jitstats +++ b/pyre/bench/synth/list_append_funcentry_helper.cranelift.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=3 +guard_failures=5 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_append_funcentry_helper.dynasm.jitstats b/pyre/bench/synth/list_append_funcentry_helper.dynasm.jitstats index 5130e30b5e8..becd33b254a 100644 --- a/pyre/bench/synth/list_append_funcentry_helper.dynasm.jitstats +++ b/pyre/bench/synth/list_append_funcentry_helper.dynasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=3 +guard_failures=5 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_append_funcentry_helper.wasm.jitstats b/pyre/bench/synth/list_append_funcentry_helper.wasm.jitstats index 5130e30b5e8..becd33b254a 100644 --- a/pyre/bench/synth/list_append_funcentry_helper.wasm.jitstats +++ b/pyre/bench/synth/list_append_funcentry_helper.wasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=3 +guard_failures=5 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_append_subscr_fresh.py b/pyre/bench/synth/list_append_subscr_fresh.py index 1a1408184f7..e91e7dca771 100644 --- a/pyre/bench/synth/list_append_subscr_fresh.py +++ b/pyre/bench/synth/list_append_subscr_fresh.py @@ -1,7 +1,7 @@ # pyre-check: max-pypy-ratio=20 -# A fresh empty list promoted by append has a trace-allocated typed backing -# block. The following subscript may revisit that symbolic block through -# W_ListObject.int_items.block before compiled execution gives it a real pointer. +# A fresh empty list promoted by append takes RPython's first 0 -> 4 backing +# grow. The following subscript revisits that block through +# W_ListObject.int_items.block after the grow helper updates the owner field. def f(n): diff --git a/pyre/bench/synth/list_append_virtual_payload.cranelift.jitstats b/pyre/bench/synth/list_append_virtual_payload.cranelift.jitstats index 4bf8f965d99..517c5643acb 100644 --- a/pyre/bench/synth/list_append_virtual_payload.cranelift.jitstats +++ b/pyre/bench/synth/list_append_virtual_payload.cranelift.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=7 +bridges_compiled=6 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1403 +guard_failures=1203 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/list_append_virtual_payload.dynasm.jitstats b/pyre/bench/synth/list_append_virtual_payload.dynasm.jitstats index 4bf8f965d99..517c5643acb 100644 --- a/pyre/bench/synth/list_append_virtual_payload.dynasm.jitstats +++ b/pyre/bench/synth/list_append_virtual_payload.dynasm.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=7 +bridges_compiled=6 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1403 +guard_failures=1203 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/list_append_virtual_payload.wasm.jitstats b/pyre/bench/synth/list_append_virtual_payload.wasm.jitstats index 24359bd38a2..517c5643acb 100644 --- a/pyre/bench/synth/list_append_virtual_payload.wasm.jitstats +++ b/pyre/bench/synth/list_append_virtual_payload.wasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=7 +bridges_compiled=6 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=1403 +guard_failures=1203 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/list_bound_method_mutation.cranelift.jitstats b/pyre/bench/synth/list_bound_method_mutation.cranelift.jitstats index e39afd359ac..df9c835fa44 100644 --- a/pyre/bench/synth/list_bound_method_mutation.cranelift.jitstats +++ b/pyre/bench/synth/list_bound_method_mutation.cranelift.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=10 +guard_failures=47 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_bound_method_mutation.dynasm.jitstats b/pyre/bench/synth/list_bound_method_mutation.dynasm.jitstats index e39afd359ac..df9c835fa44 100644 --- a/pyre/bench/synth/list_bound_method_mutation.dynasm.jitstats +++ b/pyre/bench/synth/list_bound_method_mutation.dynasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=10 +guard_failures=47 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_bound_method_mutation.wasm.jitstats b/pyre/bench/synth/list_bound_method_mutation.wasm.jitstats index e39afd359ac..df9c835fa44 100644 --- a/pyre/bench/synth/list_bound_method_mutation.wasm.jitstats +++ b/pyre/bench/synth/list_bound_method_mutation.wasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=10 +guard_failures=47 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_reverse.cranelift.jitstats b/pyre/bench/synth/list_reverse.cranelift.jitstats index 0b06f746a22..a7a09fb8d8b 100644 --- a/pyre/bench/synth/list_reverse.cranelift.jitstats +++ b/pyre/bench/synth/list_reverse.cranelift.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=10 +guard_failures=56 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_reverse.dynasm.jitstats b/pyre/bench/synth/list_reverse.dynasm.jitstats index 0b06f746a22..a7a09fb8d8b 100644 --- a/pyre/bench/synth/list_reverse.dynasm.jitstats +++ b/pyre/bench/synth/list_reverse.dynasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=10 +guard_failures=56 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 +retraces_compiled=0 diff --git a/pyre/bench/synth/list_reverse.wasm.jitstats b/pyre/bench/synth/list_reverse.wasm.jitstats index 0b06f746a22..a7a09fb8d8b 100644 --- a/pyre/bench/synth/list_reverse.wasm.jitstats +++ b/pyre/bench/synth/list_reverse.wasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=10 +guard_failures=56 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 +retraces_compiled=0 diff --git a/pyre/bench/synth/loop_callee_shared_mutation.cranelift.jitstats b/pyre/bench/synth/loop_callee_shared_mutation.cranelift.jitstats index bfc58e66bf5..0e369b7d34c 100644 --- a/pyre/bench/synth/loop_callee_shared_mutation.cranelift.jitstats +++ b/pyre/bench/synth/loop_callee_shared_mutation.cranelift.jitstats @@ -11,7 +11,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=202 +guard_failures=203 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/loop_callee_shared_mutation.dynasm.jitstats b/pyre/bench/synth/loop_callee_shared_mutation.dynasm.jitstats index bfc58e66bf5..0e369b7d34c 100644 --- a/pyre/bench/synth/loop_callee_shared_mutation.dynasm.jitstats +++ b/pyre/bench/synth/loop_callee_shared_mutation.dynasm.jitstats @@ -11,7 +11,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=202 +guard_failures=203 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/loop_callee_shared_mutation.wasm.jitstats b/pyre/bench/synth/loop_callee_shared_mutation.wasm.jitstats index bfc58e66bf5..0e369b7d34c 100644 --- a/pyre/bench/synth/loop_callee_shared_mutation.wasm.jitstats +++ b/pyre/bench/synth/loop_callee_shared_mutation.wasm.jitstats @@ -11,7 +11,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=202 +guard_failures=203 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/mapdict_frozen_unboxing_fold.cranelift.jitstats b/pyre/bench/synth/mapdict_frozen_unboxing_fold.cranelift.jitstats index fa0e7c8b47a..b9b89831ca6 100644 --- a/pyre/bench/synth/mapdict_frozen_unboxing_fold.cranelift.jitstats +++ b/pyre/bench/synth/mapdict_frozen_unboxing_fold.cranelift.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=13 +guard_failures=67 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/mapdict_frozen_unboxing_fold.dynasm.jitstats b/pyre/bench/synth/mapdict_frozen_unboxing_fold.dynasm.jitstats index fa0e7c8b47a..b9b89831ca6 100644 --- a/pyre/bench/synth/mapdict_frozen_unboxing_fold.dynasm.jitstats +++ b/pyre/bench/synth/mapdict_frozen_unboxing_fold.dynasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=13 +guard_failures=67 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/mapdict_frozen_unboxing_fold.wasm.jitstats b/pyre/bench/synth/mapdict_frozen_unboxing_fold.wasm.jitstats index fa0e7c8b47a..b9b89831ca6 100644 --- a/pyre/bench/synth/mapdict_frozen_unboxing_fold.wasm.jitstats +++ b/pyre/bench/synth/mapdict_frozen_unboxing_fold.wasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=13 +guard_failures=67 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/minmax_key_rooting.cranelift.jitstats b/pyre/bench/synth/minmax_key_rooting.cranelift.jitstats index a8c7216aeec..0178dd2d923 100644 --- a/pyre/bench/synth/minmax_key_rooting.cranelift.jitstats +++ b/pyre/bench/synth/minmax_key_rooting.cranelift.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=2 +bridges_compiled=1 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=409 +guard_failures=245 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/minmax_key_rooting.dynasm.jitstats b/pyre/bench/synth/minmax_key_rooting.dynasm.jitstats index a8c7216aeec..0178dd2d923 100644 --- a/pyre/bench/synth/minmax_key_rooting.dynasm.jitstats +++ b/pyre/bench/synth/minmax_key_rooting.dynasm.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=2 +bridges_compiled=1 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=409 +guard_failures=245 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/minmax_key_rooting.wasm.jitstats b/pyre/bench/synth/minmax_key_rooting.wasm.jitstats index a8c7216aeec..0178dd2d923 100644 --- a/pyre/bench/synth/minmax_key_rooting.wasm.jitstats +++ b/pyre/bench/synth/minmax_key_rooting.wasm.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=2 +bridges_compiled=1 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=409 +guard_failures=245 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/pickle_terminal_raise_resume.cranelift.jitstats b/pyre/bench/synth/pickle_terminal_raise_resume.cranelift.jitstats index d19f2982780..816deddbfae 100644 --- a/pyre/bench/synth/pickle_terminal_raise_resume.cranelift.jitstats +++ b/pyre/bench/synth/pickle_terminal_raise_resume.cranelift.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=318 +guard_failures=286 internal_compile_panics=0 loops_aborted=1 loops_compiled=30 diff --git a/pyre/bench/synth/pickle_terminal_raise_resume.dynasm.jitstats b/pyre/bench/synth/pickle_terminal_raise_resume.dynasm.jitstats index d19f2982780..816deddbfae 100644 --- a/pyre/bench/synth/pickle_terminal_raise_resume.dynasm.jitstats +++ b/pyre/bench/synth/pickle_terminal_raise_resume.dynasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=318 +guard_failures=286 internal_compile_panics=0 loops_aborted=1 loops_compiled=30 diff --git a/pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats b/pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats index 5ac97ec4e12..4c8c63bd801 100644 --- a/pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats +++ b/pyre/bench/synth/pickle_terminal_raise_resume.wasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=336 +guard_failures=304 internal_compile_panics=0 loops_aborted=9 loops_compiled=70 diff --git a/pyre/bench/synth/range_ctor_in_loop.cranelift.jitstats b/pyre/bench/synth/range_ctor_in_loop.cranelift.jitstats index 9d94498bfa5..8921b1c6d5a 100644 --- a/pyre/bench/synth/range_ctor_in_loop.cranelift.jitstats +++ b/pyre/bench/synth/range_ctor_in_loop.cranelift.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=812 +guard_failures=857 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/range_ctor_in_loop.dynasm.jitstats b/pyre/bench/synth/range_ctor_in_loop.dynasm.jitstats index 9d94498bfa5..8921b1c6d5a 100644 --- a/pyre/bench/synth/range_ctor_in_loop.dynasm.jitstats +++ b/pyre/bench/synth/range_ctor_in_loop.dynasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=812 +guard_failures=857 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/range_ctor_in_loop.wasm.jitstats b/pyre/bench/synth/range_ctor_in_loop.wasm.jitstats index 9d94498bfa5..8921b1c6d5a 100644 --- a/pyre/bench/synth/range_ctor_in_loop.wasm.jitstats +++ b/pyre/bench/synth/range_ctor_in_loop.wasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=812 +guard_failures=857 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats b/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats index 48671b2d5a0..0bee31c0323 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.cranelift.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=800 +guard_failures=600 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats b/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats index 48671b2d5a0..0bee31c0323 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.dynasm.jitstats @@ -1,4 +1,4 @@ -bridges_compiled=4 +bridges_compiled=3 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 @@ -11,7 +11,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=800 +guard_failures=600 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats b/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats index a24cab8f67b..51b7c8375b4 100644 --- a/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats +++ b/pyre/bench/synth/recursive_forced_frame_kept_stack.wasm.jitstats @@ -1,14 +1,17 @@ -bridges_compiled=4 +bridges_compiled=3 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=726 +guard_failures=526 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/selfrec_bridge_nontail_promote.cranelift.jitstats b/pyre/bench/synth/selfrec_bridge_nontail_promote.cranelift.jitstats index 9352e9e4f9b..dc8523967c7 100644 --- a/pyre/bench/synth/selfrec_bridge_nontail_promote.cranelift.jitstats +++ b/pyre/bench/synth/selfrec_bridge_nontail_promote.cranelift.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=805 +guard_failures=832 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/selfrec_bridge_nontail_promote.dynasm.jitstats b/pyre/bench/synth/selfrec_bridge_nontail_promote.dynasm.jitstats index 9352e9e4f9b..dc8523967c7 100644 --- a/pyre/bench/synth/selfrec_bridge_nontail_promote.dynasm.jitstats +++ b/pyre/bench/synth/selfrec_bridge_nontail_promote.dynasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=805 +guard_failures=832 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/selfrec_bridge_nontail_promote.wasm.jitstats b/pyre/bench/synth/selfrec_bridge_nontail_promote.wasm.jitstats index 9352e9e4f9b..dc8523967c7 100644 --- a/pyre/bench/synth/selfrec_bridge_nontail_promote.wasm.jitstats +++ b/pyre/bench/synth/selfrec_bridge_nontail_promote.wasm.jitstats @@ -4,11 +4,14 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=805 +guard_failures=832 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/sre_pattern_methods.cranelift.jitstats b/pyre/bench/synth/sre_pattern_methods.cranelift.jitstats index cc6fef9cfe5..b2d7bc4560c 100644 --- a/pyre/bench/synth/sre_pattern_methods.cranelift.jitstats +++ b/pyre/bench/synth/sre_pattern_methods.cranelift.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=5 +bridges_compiled=4 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1012 +guard_failures=812 internal_compile_panics=0 loops_aborted=0 loops_compiled=7 +retraces_compiled=0 diff --git a/pyre/bench/synth/sre_pattern_methods.dynasm.jitstats b/pyre/bench/synth/sre_pattern_methods.dynasm.jitstats index cc6fef9cfe5..b2d7bc4560c 100644 --- a/pyre/bench/synth/sre_pattern_methods.dynasm.jitstats +++ b/pyre/bench/synth/sre_pattern_methods.dynasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=5 +bridges_compiled=4 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1012 +guard_failures=812 internal_compile_panics=0 loops_aborted=0 loops_compiled=7 +retraces_compiled=0 diff --git a/pyre/bench/synth/sre_pattern_methods.wasm.jitstats b/pyre/bench/synth/sre_pattern_methods.wasm.jitstats index cc6fef9cfe5..b2d7bc4560c 100644 --- a/pyre/bench/synth/sre_pattern_methods.wasm.jitstats +++ b/pyre/bench/synth/sre_pattern_methods.wasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=5 +bridges_compiled=4 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=1012 +guard_failures=812 internal_compile_panics=0 loops_aborted=0 loops_compiled=7 +retraces_compiled=0 diff --git a/pyre/bench/synth/sre_wasm_min.cranelift.jitstats b/pyre/bench/synth/sre_wasm_min.cranelift.jitstats index be59c7df0d1..9609ee4ff11 100644 --- a/pyre/bench/synth/sre_wasm_min.cranelift.jitstats +++ b/pyre/bench/synth/sre_wasm_min.cranelift.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=4 +bridges_compiled=3 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=803 +guard_failures=603 internal_compile_panics=0 loops_aborted=0 loops_compiled=5 +retraces_compiled=0 diff --git a/pyre/bench/synth/sre_wasm_min.dynasm.jitstats b/pyre/bench/synth/sre_wasm_min.dynasm.jitstats index be59c7df0d1..9609ee4ff11 100644 --- a/pyre/bench/synth/sre_wasm_min.dynasm.jitstats +++ b/pyre/bench/synth/sre_wasm_min.dynasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=4 +bridges_compiled=3 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=803 +guard_failures=603 internal_compile_panics=0 loops_aborted=0 loops_compiled=5 +retraces_compiled=0 diff --git a/pyre/bench/synth/sre_wasm_min.wasm.jitstats b/pyre/bench/synth/sre_wasm_min.wasm.jitstats index be59c7df0d1..9609ee4ff11 100644 --- a/pyre/bench/synth/sre_wasm_min.wasm.jitstats +++ b/pyre/bench/synth/sre_wasm_min.wasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=4 +bridges_compiled=3 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=803 +guard_failures=603 internal_compile_panics=0 loops_aborted=0 loops_compiled=5 +retraces_compiled=0 diff --git a/pyre/bench/synth/sre_wasm_min1.cranelift.jitstats b/pyre/bench/synth/sre_wasm_min1.cranelift.jitstats index ea26102e561..182eac87026 100644 --- a/pyre/bench/synth/sre_wasm_min1.cranelift.jitstats +++ b/pyre/bench/synth/sre_wasm_min1.cranelift.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=3 +bridges_compiled=2 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=603 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 +retraces_compiled=0 diff --git a/pyre/bench/synth/sre_wasm_min1.dynasm.jitstats b/pyre/bench/synth/sre_wasm_min1.dynasm.jitstats index ea26102e561..182eac87026 100644 --- a/pyre/bench/synth/sre_wasm_min1.dynasm.jitstats +++ b/pyre/bench/synth/sre_wasm_min1.dynasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=3 +bridges_compiled=2 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=603 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 +retraces_compiled=0 diff --git a/pyre/bench/synth/sre_wasm_min1.wasm.jitstats b/pyre/bench/synth/sre_wasm_min1.wasm.jitstats index ea26102e561..182eac87026 100644 --- a/pyre/bench/synth/sre_wasm_min1.wasm.jitstats +++ b/pyre/bench/synth/sre_wasm_min1.wasm.jitstats @@ -1,14 +1,18 @@ -bridges_compiled=3 +bridges_compiled=2 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_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=603 +guard_failures=403 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 +retraces_compiled=0 diff --git a/pyre/bench/synth/str_index_bytes_iter_surface.cranelift.jitstats b/pyre/bench/synth/str_index_bytes_iter_surface.cranelift.jitstats index d3652efe513..7c00f5a07c4 100644 --- a/pyre/bench/synth/str_index_bytes_iter_surface.cranelift.jitstats +++ b/pyre/bench/synth/str_index_bytes_iter_surface.cranelift.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=640 +guard_failures=808 internal_compile_panics=0 loops_aborted=0 loops_compiled=10 +retraces_compiled=0 diff --git a/pyre/bench/synth/str_index_bytes_iter_surface.dynasm.jitstats b/pyre/bench/synth/str_index_bytes_iter_surface.dynasm.jitstats index d3652efe513..7c00f5a07c4 100644 --- a/pyre/bench/synth/str_index_bytes_iter_surface.dynasm.jitstats +++ b/pyre/bench/synth/str_index_bytes_iter_surface.dynasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=640 +guard_failures=808 internal_compile_panics=0 loops_aborted=0 loops_compiled=10 +retraces_compiled=0 diff --git a/pyre/bench/synth/str_index_bytes_iter_surface.wasm.jitstats b/pyre/bench/synth/str_index_bytes_iter_surface.wasm.jitstats index d3652efe513..7c00f5a07c4 100644 --- a/pyre/bench/synth/str_index_bytes_iter_surface.wasm.jitstats +++ b/pyre/bench/synth/str_index_bytes_iter_surface.wasm.jitstats @@ -4,11 +4,15 @@ descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_blackhole_adopted_multi_frame=0 fbw_blackhole_adopted_single_frame=0 +fbw_escape_plain_fallback=0 +fbw_escape_plain_fallback_unclean=0 +fbw_midbody_latch_new_unjournaled=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=640 +guard_failures=808 internal_compile_panics=0 loops_aborted=0 loops_compiled=10 +retraces_compiled=0 diff --git a/pyre/extra_tests/snippets/pypy_list_sizehint.py b/pyre/extra_tests/snippets/pypy_list_sizehint.py new file mode 100644 index 00000000000..0f03cc34163 --- /dev/null +++ b/pyre/extra_tests/snippets/pypy_list_sizehint.py @@ -0,0 +1,69 @@ +import __pypy__ + + +def assert_shape(value, strategy, length, physical_size): + assert __pypy__.strategy(value) == strategy + assert len(value) == length + assert __pypy__.list_get_physical_size(value) == physical_size + + +# pypy/objspace/std/listobject.py SizeListStrategy keeps no backing storage. +# Its first append selects the concrete strategy and consumes the exact hint. +for hint, value, strategy in [ + (13, 7, "IntegerListStrategy"), + (5, 1.5, "FloatListStrategy"), + (6, b"x", "BytesListStrategy"), + (7, "x", "AsciiListStrategy"), + (8, None, "ObjectListStrategy"), +]: + items = __pypy__.newlist_hint(hint) + assert_shape(items, "SizeListStrategy", 0, 0) + items.append(value) + assert_shape(items, strategy, 1, hint) + + +items = __pypy__.newlist_hint(13) +assert_shape(items.copy(), "SizeListStrategy", 0, 0) +assert_shape(items * 3, "SizeListStrategy", 0, 0) +items *= 0 +assert_shape(items, "SizeListStrategy", 0, 0) +assert_shape(__pypy__.newlist_hint(13)[:], "EmptyListStrategy", 0, 0) + + +# EmptyListStrategy.clone retains the strategy object itself. Since Size is +# the one per-list strategy instance, its mutable hint is shared by clones. +for clone in [lambda value: value.copy(), lambda value: value * 3, lambda value: [] + value]: + items = __pypy__.newlist_hint(5) + copied = clone(items) + __pypy__.resizelist_hint(items, 9) + copied.append(1) + assert_shape(copied, "IntegerListStrategy", 1, 9) + + +items = [] +__pypy__.resizelist_hint(items, 13) +assert_shape(items, "SizeListStrategy", 0, 0) +items.append(1) +assert_shape(items, "IntegerListStrategy", 1, 13) + + +items = [1, 2] +__pypy__.resizelist_hint(items, 10) +assert_shape(items, "IntegerListStrategy", 2, 17) +assert items == [1, 2] + + +# rpython/rtyper/lltypesystem/rlist.py _ll_list_resize_hint_really uses the +# same 0, 4, 8, 16, 25, ... capacity policy for every resizable strategy. +for value, strategy in [ + (1, "IntegerListStrategy"), + (1.5, "FloatListStrategy"), + (b"x", "BytesListStrategy"), + ("x", "AsciiListStrategy"), + (None, "ObjectListStrategy"), +]: + items = [] + for length, physical_size in [(1, 4), (4, 4), (5, 8), (8, 8), (9, 16), (17, 25)]: + while len(items) < length: + items.append(value) + assert_shape(items, strategy, length, physical_size) diff --git a/pyre/extra_tests/snippets/stdlib_collections_deque_repeat.py b/pyre/extra_tests/snippets/stdlib_collections_deque_repeat.py new file mode 100644 index 00000000000..11723391d30 --- /dev/null +++ b/pyre/extra_tests/snippets/stdlib_collections_deque_repeat.py @@ -0,0 +1,40 @@ +# pyre-check: gate=1 +# `W_Deque.mul` builds its answer by extending a `maxlen`-bounded copy, and +# every `append` behind that `extend` runs `trimleft`. So repeating a bounded +# deque holds `maxlen` items however large the count is, and the only bound on +# the count itself is `ovfcheck(self.len * num)` -- a machine-signed product, +# which is why the two-item case below raises where the one-item case does not +# even look. A version that materialises the whole product before trimming +# runs out of memory on the large counts here instead of answering. +from collections import deque + +from testutils import assert_raises + +assert deque([1], maxlen=1) * 20_000_000 == deque([1], maxlen=1) +assert deque([1, 2, 3], maxlen=5) * 1_000_000 == deque([2, 3, 1, 2, 3], maxlen=5) +assert 1_000_000 * deque([1, 2, 3], maxlen=5) == deque([2, 3, 1, 2, 3], maxlen=5) +assert deque([1, 2, 3], maxlen=4) * 1_000_000 == deque([3, 1, 2, 3], maxlen=4) +assert_raises(MemoryError, lambda: deque([1, 2], maxlen=2) * (2**62)) + +d = deque([1], maxlen=1) +d *= 20_000_000 +assert d == deque([1], maxlen=1) + +d = deque([1, 2, 3], maxlen=5) +d *= 1_000_000 +assert d == deque([2, 3, 1, 2, 3], maxlen=5) + + +def imul_overflow(): + d = deque([1, 2], maxlen=2) + d *= 2**62 + + +assert_raises(MemoryError, imul_overflow) + +# An unbounded deque keeps the ordinary product, and the counts that short +# circuit are unaffected. +assert deque([1, 2]) * 3 == deque([1, 2, 1, 2, 1, 2]) +assert deque([1, 2, 3]) * 0 == deque([]) +assert deque([1, 2, 3], maxlen=5) * 1 == deque([1, 2, 3], maxlen=5) +assert deque([1, 2, 3], maxlen=5) * -5 == deque([]) diff --git a/pyre/pyre-interpreter/src/baseobjspace.rs b/pyre/pyre-interpreter/src/baseobjspace.rs index 7ea626556ff..900978b8310 100644 --- a/pyre/pyre-interpreter/src/baseobjspace.rs +++ b/pyre/pyre-interpreter/src/baseobjspace.rs @@ -1814,6 +1814,10 @@ unsafe fn getitem_list(obj: PyObjectRef, index: PyObjectRef) -> PyResult { let len = w_list_len(obj) as i64; let (start, _stop, step, slicelength) = crate::sliceobject::slice_adjust_indices(rs, rp, st, len); + // BaseRangeListStrategy.getslice materialises the receiver before + // delegating to IntegerListStrategy, even though slicing is otherwise + // a read-only operation. + obj = pyre_object::listobject::w_list_materialize_range(obj); let mut items = Vec::new(); let mut i = start; for n in 0..slicelength { @@ -4668,17 +4672,17 @@ pub fn is_w(w_one: PyObjectRef, w_two: PyObjectRef) -> bool { && pyre_object::bytesobject::w_bytes_getitem(w_one, 0) == pyre_object::bytesobject::w_bytes_getitem(w_two, 0); } - // `W_UnicodeObject.is_w` (unicodeobject.py): `_len()` is the - // codepoint count. When it is > 1, upstream returns `s1 is s2` - // (utf8 storage identity) — distinct `str`s never share storage, so - // `false`; when it is <= 1 (unique-ified) it returns `s1 == s2`, - // i.e. WTF-8 byte equality. `str` subclasses keep pointer identity - // through the exact-type gate. + // `W_UnicodeObject.is_w` (unicodeobject.py): strings longer than one + // code point use `_utf8` storage identity; AsciiListStrategy + // deliberately re-wraps that same storage. Zero- and one-code-point + // strings are unique-ified and compare by value. + // `str` subclasses keep pointer identity through the exact-type gate. if pyre_object::pyobject::is_exact_type(w_one, &pyre_object::pyobject::STR_TYPE) && pyre_object::pyobject::is_exact_type(w_two, &pyre_object::pyobject::STR_TYPE) { if pyre_object::unicodeobject::w_str_len(w_one) > 1 { - return false; + return pyre_object::unicodeobject::w_str_storage(w_one) + == pyre_object::unicodeobject::w_str_storage(w_two); } return pyre_object::unicodeobject::w_str_get_wtf8(w_one) == pyre_object::unicodeobject::w_str_get_wtf8(w_two); @@ -13887,8 +13891,10 @@ fn _unpackiterable_unknown_length( let w_iterator = || pyre_object::gc_roots::shadow_stack_get(root_base); // baseobjspace.py — `try: items = newlist_hint(length_hint(...)) // except MemoryError: items = []`. - let _ = length_hint(w_iterable, 0)?; - let _ = pyre_object::gc_roots::pin_root(pyre_object::listobject::w_list_new_empty()); + let sizehint = length_hint(w_iterable, 0)?; + let _ = pyre_object::gc_roots::pin_root( + pyre_object::listobject::w_list_new_object_with_sizehint(sizehint), + ); // The slot index is computed once here, not at the append below. Spelled // `root_base + 1` inside the drain's `Ok` arm, the addition lands in the // arm's own block ahead of the call, and the link out of that block then @@ -17968,6 +17974,34 @@ pub(crate) unsafe fn generator_frame_is_finished( crate::executioncontext::may_ignore_finalizer(gen_obj); } +/// CPython 3.14 `gen_close` / `_PyFrame_ClearExceptCode`: releasing the +/// generator frame is an observable refcount boundary, so an object whose +/// last reference was a frame local runs `__del__` before the following +/// opcode. PyPy's `GeneratorIterator.frame_is_finished` only drops +/// `self.frame` and lets its tracing GC discover the local later. Keep that +/// frame-clearing shape, then run the existing non-moving reachability pass +/// and drain its queue before returning to the caller. Deferring this through +/// `UserDelAction.fire()` is too late: a test method can read the class flag in +/// the very next opcode. The non-moving collector follows `do_collect_full`'s +/// explicit-collection shape — finish an older incremental cycle, then take a +/// fresh root snapshot — so the pass observes the frame release even when a +/// major was already in progress. The pending-finalizer census avoids paying +/// for a collection when no application callback could be observed. +/// +/// The PyPy load-bearing-hint census around `GeneratorIterator` finds only +/// `_immutable_fields_ = ['pycode']` and `rgc.may_ignore_finalizer(self)`; +/// neither governs the released locals or their finalization timing. +pub(crate) fn generator_close_finalizer_boundary() { + if !majit_gc::gc_has_pending_finalizers() { + return; + } + let action = crate::executioncontext::space_user_del_action(); + if !action.is_null() { + pyre_object::gc_hook::try_gc_collect_oldgen(); + unsafe { (*action)._run_finalizers() }; + } +} + /// generator.py `_invoke_execute_frame`: install the generator's /// exception state, execute its already-entered frame resume, finish the frame /// on errors, and perform the common frame/running/EC cleanup in `finally`. @@ -18507,6 +18541,7 @@ pub(crate) fn generator_close_method(args: &[PyObjectRef]) -> PyResult { w_generator_set_exhausted(gen_obj); } else { generator_frame_is_finished(gen_obj, &mut *frame_ptr); + generator_close_finalizer_boundary(); } return Ok(w_none()); } @@ -18532,6 +18567,7 @@ pub(crate) fn generator_close_method(args: &[PyObjectRef]) -> PyResult { // unlike a Python handler, no PUSH_EXC_INFO will clear the // temporary propagation root for us. crate::eval::set_in_flight_exception(PY_NULL); + generator_close_finalizer_boundary(); value } Err(e) if e.kind == PyErrorKind::GeneratorExit => { @@ -18539,6 +18575,7 @@ pub(crate) fn generator_close_method(args: &[PyObjectRef]) -> PyResult { // GeneratorExit after matching it. Mirror PUSH_EXC_INFO's // ownership transfer by ending pyre's propagation root here. crate::eval::set_in_flight_exception(PY_NULL); + generator_close_finalizer_boundary(); Ok(w_none()) } Err(e) => Err(e), @@ -19901,6 +19938,29 @@ pub fn dict_move_to_end(obj: PyObjectRef, key: PyObjectRef, last: bool) -> Resul mod tests { use super::*; + #[test] + fn unicode_is_w_uses_codepoint_count_and_shared_storage() { + crate::test_hooks::install_hash_hook(); + + // PyPy unique-ifies one-code-point strings by value even when their + // UTF-8 representation occupies multiple bytes. + let non_ascii_one = pyre_object::unicodeobject::w_str_new_managed("é"); + let non_ascii_one_again = pyre_object::unicodeobject::w_str_new_managed("é"); + assert!(is_w(non_ascii_one, non_ascii_one_again)); + + // Longer strings compare their erased `_utf8` storage, which is what + // AsciiListStrategy preserves while allocating fresh wrappers. + let original = pyre_object::unicodeobject::w_str_new_managed("ascii"); + let shared = unsafe { + pyre_object::unicodeobject::w_str_from_storage( + pyre_object::unicodeobject::w_str_storage(original), + ) + }; + let distinct = pyre_object::unicodeobject::w_str_new_managed("ascii"); + assert!(is_w(original, shared)); + assert!(!is_w(original, distinct)); + } + #[test] fn call_expands_packed_arguments_and_keywords() { crate::typedef::init_typeobjects(); diff --git a/pyre/pyre-interpreter/src/builtins.rs b/pyre/pyre-interpreter/src/builtins.rs index a88775a2df1..1c543c43c85 100644 --- a/pyre/pyre-interpreter/src/builtins.rs +++ b/pyre/pyre-interpreter/src/builtins.rs @@ -10281,7 +10281,7 @@ pub fn is_build_class_builtin(obj: PyObjectRef) -> bool { return false; } let func = unsafe { crate::gateway::builtin_code_get(code) }; - std::ptr::fn_addr_eq(func, builtin_build_class as crate::gateway::BuiltinCodeFn) + crate::gateway::builtin_code_fn_eq(func, builtin_build_class as crate::gateway::BuiltinCodeFn) } /// `str(obj)` → convert to string @@ -16031,6 +16031,10 @@ pub(crate) fn sort_list_in_place( if key_fn.is_none() { let list = pyre_object::gc_roots::shadow_stack_get(list_slot); unsafe { + if pyre_object::listobject::w_list_sort_range(list, reverse) { + return Ok(()); + } + let list = pyre_object::gc_roots::shadow_stack_get(list_slot); if let Some((items, len)) = pyre_object::listobject::w_list_int_items_raw(list) { sort_scalars(std::slice::from_raw_parts_mut(items, len), reverse)?; return Ok(()); @@ -16042,6 +16046,9 @@ pub(crate) fn sort_list_in_place( if pyre_object::listobject::w_list_sort_int_or_float(list, reverse) { return Ok(()); } + if pyre_object::listobject::w_list_sort_strings(list, reverse) { + return Ok(()); + } } } unsafe { diff --git a/pyre/pyre-interpreter/src/function.rs b/pyre/pyre-interpreter/src/function.rs index b654f2732f9..4739e7ad2f2 100644 --- a/pyre/pyre-interpreter/src/function.rs +++ b/pyre/pyre-interpreter/src/function.rs @@ -2981,6 +2981,7 @@ pub unsafe fn fdel_func_doc(obj: PyObjectRef) -> Result<(), crate::PyError> { /// `tuple`/`frozenset`) use `IDTAG_SPECIAL`. const IDTAG_SHIFT: i64 = 4; const IDTAG_INT: i64 = 1; +const IDTAG_ALT_UID: i64 = 2; const IDTAG_FLOAT: i64 = 5; const IDTAG_SPECIAL: i64 = 11; @@ -3062,28 +3063,24 @@ pub fn immutable_unique_id(obj: PyObjectRef) -> Option { return Some(pyre_object::intobject::w_int_new(uid)); } if is_exact_type(obj, &STR_TYPE) { - // `W_UnicodeObject.immutable_unique_id` (unicodeobject.py). - // `l` is the codepoint count (`_len()`), not the byte length. - // `l > 1` is address-based (upstream `compute_unique_id(_utf8) + - // IDTAG_ALT_UID`); returning `None` falls back to the object - // address, invariant-preserving with `is_w` returning `false` - // for distinct len>1 strings. `l <= 1` is unique-ified: for a - // single codepoint `base = ~codepoint_at_pos(_utf8, 0)` - // (negative), and `base = 257` for the empty string. - let l = pyre_object::unicodeobject::w_str_len(obj); - if l > 1 { - return None; + // `W_UnicodeObject.immutable_unique_id` (unicodeobject.py): more + // than one code point uses the `_utf8` storage identity, which + // AsciiListStrategy preserves across wrapper allocation. Empty + // and one-code-point strings are unique-ified from the code point. + let len = pyre_object::unicodeobject::w_str_len(obj); + if len > 1 { + let storage = pyre_object::unicodeobject::w_str_storage(obj); + return Some(pyre_object::intobject::w_int_new( + pyre_object::gc_hook::gc_identity_hash(storage as usize) as i64 + IDTAG_ALT_UID, + )); } - let base: i64 = if l == 1 { - // `code_points()` yields the codepoint regardless of - // surrogates, matching `rutf8.codepoint_at_pos`. - let cp = pyre_object::unicodeobject::w_str_get_wtf8(obj) + let base: i64 = if len == 1 { + let codepoint = pyre_object::unicodeobject::w_str_get_wtf8(obj) .code_points() .next() .expect("len==1 str has a code point") .to_u32(); - // `(neg << IDTAG_SHIFT) | IDTAG_SPECIAL` == `+` (low 4 bits 0). - !(cp as i64) + !(codepoint as i64) } else { 257 }; @@ -4028,6 +4025,28 @@ fn _flat_pycall_defaults( mod tests { use super::*; + #[test] + fn unicode_unique_id_matches_pypy_storage_and_codepoint_rules() { + crate::test_hooks::install_hash_hook(); + + let ascii = pyre_object::unicodeobject::w_str_new_managed("ascii"); + let ascii_uid = immutable_unique_id(ascii).expect("exact str has a unique id"); + let storage = unsafe { pyre_object::unicodeobject::w_str_storage(ascii) }; + assert_eq!( + unsafe { pyre_object::intobject::w_int_get_value(ascii_uid) }, + pyre_object::gc_hook::gc_identity_hash(storage as usize) as i64 + IDTAG_ALT_UID + ); + + // The branch is based on `_len()`, so one non-ASCII code point uses + // the value-derived special id even though UTF-8 encodes it in bytes. + let one = pyre_object::unicodeobject::w_str_new_managed("é"); + let one_uid = immutable_unique_id(one).expect("exact str has a unique id"); + assert_eq!( + unsafe { pyre_object::intobject::w_int_get_value(one_uid) }, + (!(0xe9_i64) << IDTAG_SHIFT) + IDTAG_SPECIAL + ); + } + #[test] fn test_function_create() { crate::test_hooks::install_hash_hook(); diff --git a/pyre/pyre-interpreter/src/jit_fnaddr.rs b/pyre/pyre-interpreter/src/jit_fnaddr.rs index 491a6e1eadf..8eff8658a8b 100644 --- a/pyre/pyre-interpreter/src/jit_fnaddr.rs +++ b/pyre/pyre-interpreter/src/jit_fnaddr.rs @@ -857,8 +857,10 @@ pub fn jit_trace_fnaddrs() -> Vec<(&'static str, i64)> { pyre_object::listobject::jit_drain_list_append, ); - // The drain's prologue (`w_list_new_empty`) wraps opaque host plumbing and - // has a one-word return, so publish it as a residual-call target. + // The drain's prologue (`w_list_new_object_with_sizehint`) wraps opaque + // host plumbing and has a one-word return, so publish it as a + // residual-call target. Keep `w_list_new_empty` registered for its other + // residual sites. // `w_list_new_object` is residualized (`#[dont_look_inside]`) but was // unregistered; bind it too so any direct residual site resolves. let w_list_new_empty: fn() -> pyre_object::PyObjectRef = @@ -870,6 +872,19 @@ pub fn jit_trace_fnaddrs() -> Vec<(&'static str, i64)> { w_list_new_empty, ); p0(&mut entries, "w_list_new_empty", w_list_new_empty); + let w_list_new_object_with_sizehint: fn(i64) -> pyre_object::PyObjectRef = + pyre_object::listobject::w_list_new_object_with_sizehint; + pa1( + &mut entries, + "pyre_object::listobject::w_list_new_object_with_sizehint", + "pyre_object::w_list_new_object_with_sizehint", + w_list_new_object_with_sizehint, + ); + p1( + &mut entries, + "w_list_new_object_with_sizehint", + w_list_new_object_with_sizehint, + ); let w_none: fn() -> pyre_object::PyObjectRef = pyre_object::noneobject::w_none; pa0( &mut entries, diff --git a/pyre/pyre-interpreter/src/module/__pypy__/mod.rs b/pyre/pyre-interpreter/src/module/__pypy__/mod.rs index 5f3cd28de7c..1fb8ab64d14 100644 --- a/pyre/pyre-interpreter/src/module/__pypy__/mod.rs +++ b/pyre/pyre-interpreter/src/module/__pypy__/mod.rs @@ -59,13 +59,43 @@ fn set_contextvar_context(args: &[pyre_object::PyObjectRef]) -> crate::PyResult /// `sizehint` items. /// /// The hint names a storage length, not a length, so what comes back is `[]`. -/// A list here grows its block as it is appended to and carries no capacity -/// that can be set before the first item, so the number is read -- upstream -/// unwraps it as an `int` and a caller passing something else is owed the -/// error -- and nothing else is done with it. +/// SizeListStrategy retains it without allocating; the first append chooses +/// the concrete strategy and asks its `get_empty_storage(sizehint)` for the +/// exact backing capacity. fn newlist_hint(args: &[pyre_object::PyObjectRef]) -> crate::PyResult { - let _sizehint = crate::baseobjspace::int_w(args[0])?; - Ok(pyre_object::w_list_new(Vec::new())) + let sizehint = crate::baseobjspace::int_w(args[0])?; + isize::try_from(sizehint) + .map_err(|_| crate::PyError::overflow_error("integer does not fit in signed word"))?; + Ok(pyre_object::listobject::w_list_new_with_sizehint(sizehint)) +} + +/// `interp_magic.py resizelist_hint`: forward to the live list strategy. +fn resizelist_hint(args: &[pyre_object::PyObjectRef]) -> crate::PyResult { + let w_list = args[0]; + if !unsafe { pyre_object::is_list(w_list) } { + return Err(crate::PyError::type_error("arg 1 must be a 'list'")); + } + let sizehint = crate::baseobjspace::int_w(args[1])?; + isize::try_from(sizehint) + .map_err(|_| crate::PyError::overflow_error("integer does not fit in signed word"))?; + if !unsafe { pyre_object::listobject::w_list_resize_hint(w_list, sizehint) } { + return Err(crate::PyError::memory_error("")); + } + Ok(pyre_object::w_none()) +} + +/// `interp_magic.py list_get_physical_size`. +fn list_get_physical_size(args: &[pyre_object::PyObjectRef]) -> crate::PyResult { + let w_list = args[0]; + if !unsafe { pyre_object::is_list(w_list) } { + return Err(crate::PyError::type_error("expected list")); + } + let Some(size) = (unsafe { pyre_object::listobject::w_list_physical_size(w_list) }) else { + return Err(crate::PyError::value_error( + "can't get physical size of list", + )); + }; + Ok(pyre_object::w_int_new(size as i64)) } /// `interp_magic.py add_memory_pressure`: report a raw allocation to @@ -276,6 +306,8 @@ crate::py_module! { "set_contextvar_context" / 1 = set_contextvar_context, "add_memory_pressure" / 1 = add_memory_pressure, "newlist_hint" / 1 = newlist_hint, + "resizelist_hint" / 2 = resizelist_hint, + "list_get_physical_size" / 1 = list_get_physical_size, "reversed_dict" / 1 = reversed_dict, "move_to_end" / * = move_to_end, "objects_in_repr" / 0 = objects_in_repr, diff --git a/pyre/pyre-interpreter/src/module/_collections/mod.rs b/pyre/pyre-interpreter/src/module/_collections/mod.rs index 37c95be3ee5..bfac1a4caee 100644 --- a/pyre/pyre-interpreter/src/module/_collections/mod.rs +++ b/pyre/pyre-interpreter/src/module/_collections/mod.rs @@ -842,18 +842,33 @@ pub(crate) fn deque_repeat( let self_obj = pyre_object::gc_roots::shadow_stack_get(roots); let base = snapshot(self_obj); // interp_deque.py W_Deque.mul: ovfcheck(self.len * num) raises - // MemoryError. `try_reserve_exact` is the implicit allocation edge that - // follows the explicit overflow check in the RPython body. - let total = base - .len() - .checked_mul(num) - .ok_or_else(|| crate::PyError::memory_error(""))?; + // MemoryError. `ovfcheck` guards a machine-signed multiplication, so the + // edge is `isize` rather than the `usize` the accumulator counts in. + if num > 0 && base.len() > (isize::MAX as usize) / num { + return Err(crate::PyError::memory_error("")); + } + // `copied = W_Deque(space); copied.maxlen = self.maxlen`, then + // `for _ in range(num): copied.extend(self)`. Every `append` behind that + // `extend` runs `trimleft`, so the copy never holds more than `maxlen` + // items at any point; materialising the whole product and trimming once + // at the end is what makes a bounded deque times a large count exhaust + // memory instead of answering. + let maxlen = maxlen_bound(self_obj); + let total = base.len() * num; let mut items = Vec::new(); items - .try_reserve_exact(total) + .try_reserve_exact(match maxlen { + Some(m) => total.min(m.saturating_add(base.len())), + None => total, + }) .map_err(|_| crate::PyError::memory_error(""))?; for _ in 0..num { items.extend_from_slice(&base); + if let Some(m) = maxlen + && items.len() > m + { + items.drain(0..items.len() - m); + } } let ty = unsafe { w_instance_get_type(self_obj) }; let list = w_list_new(items); @@ -1345,24 +1360,33 @@ impl W_Deque { store(self_obj, vec![]); return Ok(self_obj); } - // interp_deque.py W_Deque.imul: ovfcheck(self.len * num), followed by - // the allocation's implicit MemoryError edge. + // interp_deque.py W_Deque.imul: ovfcheck(self.len * num), whose + // machine-signed multiplication puts the edge at `isize`. let repeat = num as usize; - let total = base - .len() - .checked_mul(repeat) - .ok_or_else(|| crate::PyError::memory_error(""))?; + if base.len() > (isize::MAX as usize) / repeat { + return Err(crate::PyError::memory_error("")); + } + // `copy` is a `maxlen`-bounded copy of self and `self.extend(copy)` + // runs `num - 1` times. self is already trimmed, and every `append` + // behind `extend` trims again, so it never holds more than `maxlen` + // items — the product is never materialised. + let maxlen = maxlen_bound(self_obj); + let total = base.len() * repeat; let mut items = Vec::new(); items - .try_reserve_exact(total) + .try_reserve_exact(match maxlen { + Some(m) => total.min(m.saturating_add(base.len())), + None => total, + }) .map_err(|_| crate::PyError::memory_error(""))?; - for _ in 0..repeat { + items.extend_from_slice(&base); + for _ in 0..repeat - 1 { items.extend_from_slice(&base); - } - if let Some(m) = maxlen_bound(self_obj) - && items.len() > m - { - items.drain(0..items.len() - m); + if let Some(m) = maxlen + && items.len() > m + { + items.drain(0..items.len() - m); + } } store(self_obj, items); Ok(self_obj) diff --git a/pyre/pyre-interpreter/src/module/_pickle/pickler.rs b/pyre/pyre-interpreter/src/module/_pickle/pickler.rs index aefa2b9a8ca..b08d75d252e 100644 --- a/pyre/pyre-interpreter/src/module/_pickle/pickler.rs +++ b/pyre/pyre-interpreter/src/module/_pickle/pickler.rs @@ -132,6 +132,14 @@ struct PickleCtx { memo_slot: usize, /// `gc_identity_hash(obj)` → memo indices sharing that hash. index: HashMap>, + /// `str_memo` / `bytes_memo` (interp_pickle.py): value-keyed memos for the + /// exact `str` and `bytes` types, consulted before the identity memo. An + /// unboxing list strategy stores the erased rpython string and wraps a + /// fresh object per read, so repeated references to one logical value + /// reach the identity memo as different objects and would each be written + /// where the second belongs as a GET. + str_memo: HashMap, usize>, + bytes_memo: HashMap, usize>, /// `persistent_id` callable resolved off the pickler (subclass override /// or set attribute), or `PY_NULL` when not defined. pers_func: PinnedRef, @@ -165,6 +173,40 @@ impl PickleCtx { pyre_object::gc_roots::shadow_stack_get(self.memo_slot) } + /// The `str_memo` / `bytes_memo` key for an exact `str` or `bytes`: + /// `space.utf8_w(w_obj)` and `space.bytes_w(w_obj)` respectively. + fn value_memo_key(w_obj: PyObjectRef) -> Option<(bool, Vec)> { + unsafe { + if pyre_object::is_exact_type(w_obj, &pyre_object::STR_TYPE) { + return Some(( + true, + pyre_object::unicodeobject::w_str_get_wtf8(w_obj) + .as_bytes() + .to_vec(), + )); + } + if pyre_object::is_exact_type(w_obj, &pyre_object::bytesobject::BYTES_TYPE) { + return Some(( + false, + pyre_object::bytesobject::w_bytes_data(w_obj).to_vec(), + )); + } + } + None + } + + /// `interp_pickle.py W_Pickler.save`'s value-based memo lookup, which + /// precedes the identity memo. + fn value_memo_get(&self, w_obj: PyObjectRef) -> Option { + let (is_str, key) = Self::value_memo_key(w_obj)?; + let memo = if is_str { + &self.str_memo + } else { + &self.bytes_memo + }; + memo.get(&key).copied() + } + fn memo_get(&self, w_obj: PyObjectRef) -> Option { let h = pyre_object::gc_hook::gc_identity_hash(w_obj as usize); let list = self.memo_list(); @@ -1079,6 +1121,8 @@ fn pickle_core_impl( let w_memo = pyre_object::gc_roots::pin_root(w_memo); let memo_slot = pyre_object::gc_roots::shadow_stack_len() - 1; let mut index: HashMap> = HashMap::new(); + let mut str_memo: HashMap, usize> = HashMap::new(); + let mut bytes_memo: HashMap, usize> = HashMap::new(); let n = unsafe { pyre_object::listobject::w_list_len(w_memo) }; for i in 0..n { let o = unsafe { pyre_object::listobject::w_list_getitem(w_memo, i as i64) }.unwrap(); @@ -1091,6 +1135,14 @@ fn pickle_core_impl( .entry(pyre_object::gc_hook::gc_identity_hash(o as usize)) .or_default() .push(i); + if let Some((is_str, key)) = PickleCtx::value_memo_key(o) { + let memo = if is_str { + &mut str_memo + } else { + &mut bytes_memo + }; + memo.insert(key, i); + } } let mut ctx = PickleCtx { @@ -1099,6 +1151,8 @@ fn pickle_core_impl( fix_imports, memo_slot, index, + str_memo, + bytes_memo, pers_func, buffer_callback, fast, @@ -1270,8 +1324,9 @@ fn save_object(ctx: &mut PickleCtx, buf: &mut Framer, w_obj: PyObjectRef) -> Res || pyre_object::is_exact_type(w_obj, &pyre_object::INT_TYPE) || pyre_object::is_exact_type(w_obj, &pyre_object::FLOAT_TYPE) }; - // Identity memo — a repeated reference becomes a GET back-reference. - if !is_atom && let Some(idx) = ctx.memo_get(w_obj) { + // Value memo, then identity memo — a repeated reference becomes a GET + // back-reference. + if !is_atom && let Some(idx) = ctx.value_memo_get(w_obj).or_else(|| ctx.memo_get(w_obj)) { write_get(ctx, buf, idx); return Ok(()); } @@ -2502,8 +2557,17 @@ fn memoize(ctx: &mut PickleCtx, buf: &mut Framer, w_obj: PyObjectRef) { // Compute the move-stable hash before the append, whose growth could // relocate `w_obj` and leave the local stale. let h = pyre_object::gc_hook::gc_identity_hash(w_obj as usize); + let value_key = PickleCtx::value_memo_key(w_obj); unsafe { pyre_object::listobject::w_list_append(list, w_obj) }; ctx.index.entry(h).or_default().push(idx); + if let Some((is_str, key)) = value_key { + let memo = if is_str { + &mut ctx.str_memo + } else { + &mut ctx.bytes_memo + }; + memo.insert(key, idx); + } if ctx.proto >= 4 { buf.push(op::MEMOIZE); } else if ctx.bin { diff --git a/pyre/pyre-interpreter/src/module/gc/mod.rs b/pyre/pyre-interpreter/src/module/gc/mod.rs index f55394b2d35..5a3cce9be1a 100644 --- a/pyre/pyre-interpreter/src/module/gc/mod.rs +++ b/pyre/pyre-interpreter/src/module/gc/mod.rs @@ -705,7 +705,9 @@ fn pin_unboxed_container_referents(source_slot: usize) { let list = &*(w_obj as *const listobject::W_ListObject); if matches!( list.strategy, - listobject::ListStrategy::Empty | listobject::ListStrategy::Object + listobject::ListStrategy::Empty + | listobject::ListStrategy::Size + | listobject::ListStrategy::Object ) { return; } diff --git a/pyre/pyre-interpreter/src/objspace/descroperation.rs b/pyre/pyre-interpreter/src/objspace/descroperation.rs index c47a740cf3b..c1e597512b0 100644 --- a/pyre/pyre-interpreter/src/objspace/descroperation.rs +++ b/pyre/pyre-interpreter/src/objspace/descroperation.rs @@ -2133,6 +2133,13 @@ pub(crate) unsafe fn bytes_repeat(s: PyObjectRef, n: PyObjectRef) -> PyResult { pub(crate) unsafe fn list_concat(a: PyObjectRef, b: PyObjectRef) -> PyResult { let len_a = w_list_len(a); let len_b = w_list_len(b); + // W_ListObject.descr_add: when the left operand is empty, clone the right + // operand. A SizeListStrategy clone retains that exact strategy instance. + if len_a == 0 + && let Some(clone) = pyre_object::listobject::w_list_clone_if_shared_strategy(b) + { + return Ok(clone); + } let mut items = Vec::with_capacity(len_a + len_b); for i in 0..len_a { if let Some(item) = w_list_getitem(a, i as i64) { @@ -2167,6 +2174,9 @@ pub(crate) unsafe fn tuple_concat(a: PyObjectRef, b: PyObjectRef) -> PyResult { /// listobject.py descr_mul pub(crate) unsafe fn list_repeat(list: PyObjectRef, n: PyObjectRef) -> PyResult { let count = repeat_count(n)?; + if let Some(clone) = pyre_object::listobject::w_list_clone_if_size(list) { + return Ok(clone); + } let len = w_list_len(list); let cap = len .checked_mul(count) @@ -2196,6 +2206,12 @@ pub(crate) unsafe fn list_repeat(list: PyObjectRef, n: PyObjectRef) -> PyResult /// storage instead of building a fresh list. pub(crate) unsafe fn list_inplace_repeat(list: PyObjectRef, n: PyObjectRef) -> Result<(), PyError> { let count = repeat_count(n)?; + // BaseRangeListStrategy.inplace_mul materialises before delegating for + // every multiplier, including 0 and 1. + let list = pyre_object::listobject::w_list_materialize_range(list); + if pyre_object::listobject::w_list_sizehint(list).is_some() { + return Ok(()); + } if count == 0 { w_list_clear(list); return Ok(()); diff --git a/pyre/pyre-interpreter/src/objspace/std/formatting.rs b/pyre/pyre-interpreter/src/objspace/std/formatting.rs index f53d85e23d2..28aa0adb235 100644 --- a/pyre/pyre-interpreter/src/objspace/std/formatting.rs +++ b/pyre/pyre-interpreter/src/objspace/std/formatting.rs @@ -4,15 +4,330 @@ use majit_rlib::rbigint::RBigInt as BigInt; use num_traits::ToPrimitive; use rustpython_common::cformat::{ - CCharacterType, CConversionFlags, CFormatBytes, CFormatConversion, CFormatPart, - CFormatPrecision, CFormatQuantity, CFormatSpec, CFormatSpecKeyed, CFormatType, CFormatWtf8, - CNumberType, + CCharacterType, CConversionFlags, CFormatBytes, CFormatConversion, CFormatError, + CFormatErrorType, CFormatPart, CFormatPrecision, CFormatQuantity, CFormatSpec, + CFormatSpecKeyed, CFormatType, CFormatWtf8, CNumberType, }; use crate::objspace::descroperation::{int_value, is_int_like}; use crate::{PyError, PyErrorKind, PyResult}; use pyre_object::*; -use rustpython_wtf8::{CodePoint, Wtf8Buf}; +use rustpython_wtf8::{CodePoint, Wtf8, Wtf8Buf}; + +#[derive(Clone, Copy)] +enum DeferredPercentError { + Unsupported(CFormatError), + Incomplete(CFormatError), + IncompleteMappingKey(CFormatError), + Quantity(CFormatError), +} + +impl DeferredPercentError { + fn unsupported(self) -> Option { + match self { + Self::Unsupported(error) => Some(error), + _ => None, + } + } + + fn before_conversion(self) -> Option { + match self { + Self::Incomplete(error) | Self::Quantity(error) => Some(error), + _ => None, + } + } + + fn after_parts(self, is_mapping: bool) -> Result<(), PyError> { + let error = match self { + Self::Unsupported(_) => { + unreachable!("the recovered unsupported spec raises after operand acquisition") + } + Self::Incomplete(_) | Self::Quantity(_) => { + unreachable!("the recovered spec raises before conversion acquisition") + } + Self::IncompleteMappingKey(_) if !is_mapping => { + return Err(PyError::type_error("format requires a mapping")); + } + Self::IncompleteMappingKey(error) => error, + }; + Err(PyError::value_error(error.to_string())) + } +} + +fn is_format_char(codepoint: CodePoint, expected: char) -> bool { + codepoint.to_u32() == expected as u32 +} + +fn wtf8_prefix(fmt: &Wtf8, codepoints: usize) -> Wtf8Buf { + let mut prefix = Wtf8Buf::new(); + for codepoint in fmt.code_points().take(codepoints) { + prefix.push(codepoint); + } + prefix +} + +fn wtf8_acquisition_prefix( + fmt: &Wtf8, + error: CFormatError, + search_end: usize, + include_precision_star: bool, +) -> Wtf8Buf { + let codepoints: Vec<_> = fmt.code_points().collect(); + let spec_start = (0..search_end) + .filter(|&index| is_format_char(codepoints[index], '%')) + .filter(|&index| { + let prefix = wtf8_prefix(fmt, index + 1); + matches!( + CFormatWtf8::parse_from_wtf8(&prefix), + Err(CFormatError { + typ: CFormatErrorType::IncompleteFormat, + index: error_index, + }) if error_index == index + 1 + ) + }) + .next_back() + .expect("a width or precision error belongs to a conversion spec"); + + let mut recovered = Wtf8Buf::new(); + recovered.extend(codepoints[..=spec_start].iter().copied()); + let mut cursor = spec_start + 1; + if codepoints + .get(cursor) + .is_some_and(|&c| is_format_char(c, '(')) + { + let mapping_start = cursor; + let mut nesting = 1; + cursor += 1; + while nesting != 0 { + let codepoint = codepoints[cursor]; + cursor += 1; + if is_format_char(codepoint, '(') { + nesting += 1; + } else if is_format_char(codepoint, ')') { + nesting -= 1; + } + } + recovered.extend(codepoints[mapping_start..cursor].iter().copied()); + } + while codepoints.get(cursor).is_some_and(|&c| { + ['#', '0', '-', ' ', '+'] + .into_iter() + .any(|flag| is_format_char(c, flag)) + }) { + cursor += 1; + } + if !matches!(error.typ, CFormatErrorType::WidthTooBig) + && codepoints + .get(cursor) + .is_some_and(|&c| is_format_char(c, '*')) + { + recovered.push_char('*'); + cursor += 1; + } else { + while codepoints + .get(cursor) + .is_some_and(|c| c.to_u32() >= '0' as u32 && c.to_u32() <= '9' as u32) + { + cursor += 1; + } + } + if include_precision_star + && codepoints + .get(cursor) + .is_some_and(|&c| is_format_char(c, '.')) + && codepoints + .get(cursor + 1) + .is_some_and(|&c| is_format_char(c, '*')) + { + recovered.push_char('.'); + recovered.push_char('*'); + } + recovered.push_char('s'); + recovered +} + +fn bytes_acquisition_prefix( + fmt: &[u8], + error: CFormatError, + search_end: usize, + include_precision_star: bool, +) -> Vec { + let spec_start = (0..search_end) + .filter(|&index| fmt[index] == b'%') + .filter(|&index| { + matches!( + CFormatBytes::parse_from_bytes(&fmt[..=index]), + Err(CFormatError { + typ: CFormatErrorType::IncompleteFormat, + index: error_index, + }) if error_index == index + 1 + ) + }) + .next_back() + .expect("a width or precision error belongs to a conversion spec"); + + let mut recovered = fmt[..=spec_start].to_vec(); + let mut cursor = spec_start + 1; + if fmt.get(cursor) == Some(&b'(') { + let mapping_start = cursor; + let mut nesting = 1; + cursor += 1; + while nesting != 0 { + match fmt[cursor] { + b'(' => nesting += 1, + b')' => nesting -= 1, + _ => {} + } + cursor += 1; + } + recovered.extend_from_slice(&fmt[mapping_start..cursor]); + } + while fmt.get(cursor).is_some_and(|c| b"#0- +".contains(c)) { + cursor += 1; + } + if !matches!(error.typ, CFormatErrorType::WidthTooBig) && fmt.get(cursor) == Some(&b'*') { + recovered.push(b'*'); + cursor += 1; + } else { + while fmt.get(cursor).is_some_and(u8::is_ascii_digit) { + cursor += 1; + } + } + if include_precision_star + && fmt.get(cursor) == Some(&b'.') + && fmt.get(cursor + 1) == Some(&b'*') + { + recovered.extend_from_slice(b".*"); + } + recovered.push(b's'); + recovered +} + +/// Parse a unicode percent format, retaining the first deferred parser error. +/// +/// PyPy's `StringFormatter.format` parses one spec at a time: `parse_fmt` +/// performs mapping lookup and consumes `*` operands, then the loop validates +/// the conversion character. CPython 3.14 additionally consumes the conversion +/// operand before reporting an unsupported character. The shared RustPython +/// parser instead validates the entire format eagerly, which used to report +/// the `ValueError` before either upstream's operand-side effects occurred. +/// +/// Replace only that unsupported character with `s` and stop there. The caller +/// can execute every preceding spec and the recovered spec's operand-acquisition +/// path, then surface the saved 3.14 error without formatting the operand. +/// An incomplete mapping key retains only the complete prefix. Other incomplete +/// specs and oversized quantities retain a synthetic current spec through the +/// mapping lookup and the `*` operands which precede their error stage. This is +/// the `parse_fmt` acquisition order without asking for the absent conversion +/// operand. +fn parse_wtf8_incremental( + fmt: &Wtf8, +) -> Result<(CFormatWtf8, Option), PyError> { + match CFormatWtf8::parse_from_wtf8(fmt) { + Ok(format) => Ok((format, None)), + Err(error) => { + if matches!( + error.typ, + CFormatErrorType::WidthTooBig | CFormatErrorType::PrecisionTooBig + ) { + let recovered = CFormatWtf8::parse_from_wtf8(&wtf8_acquisition_prefix( + fmt, + error, + error.index, + false, + )) + .expect("the acquisition prefix of a deferred quantity error must parse"); + return Ok((recovered, Some(DeferredPercentError::Quantity(error)))); + } + if matches!(error.typ, CFormatErrorType::IncompleteFormat) { + let recovered = CFormatWtf8::parse_from_wtf8(&wtf8_acquisition_prefix( + fmt, + error, + fmt.code_points().count(), + true, + )) + .expect("the acquisition prefix of an incomplete format must parse"); + return Ok((recovered, Some(DeferredPercentError::Incomplete(error)))); + } + let (prefix_len, replacement, deferred) = match error.typ { + CFormatErrorType::UnsupportedFormatChar(_) => { + (error.index, true, DeferredPercentError::Unsupported(error)) + } + CFormatErrorType::UnmatchedKeyParentheses => ( + error + .index + .checked_sub(1) + .expect("an incomplete mapping key follows its percent sign"), + false, + DeferredPercentError::IncompleteMappingKey(error), + ), + _ => return Err(PyError::value_error(error.to_string())), + }; + let mut prefix = wtf8_prefix(fmt, prefix_len); + if replacement { + prefix.push_char('s'); + } + let recovered = CFormatWtf8::parse_from_wtf8(&prefix) + .expect("the complete prefix of a deferred percent-format error must parse"); + Ok((recovered, Some(deferred))) + } + } +} + +/// Bytes counterpart of [`parse_wtf8_incremental`]. +fn parse_bytes_incremental( + fmt: &[u8], +) -> Result<(CFormatBytes, Option), PyError> { + match CFormatBytes::parse_from_bytes(fmt) { + Ok(format) => Ok((format, None)), + Err(error) => { + if matches!( + error.typ, + CFormatErrorType::WidthTooBig | CFormatErrorType::PrecisionTooBig + ) { + let recovered = CFormatBytes::parse_from_bytes(&bytes_acquisition_prefix( + fmt, + error, + error.index, + false, + )) + .expect("the acquisition prefix of a deferred quantity error must parse"); + return Ok((recovered, Some(DeferredPercentError::Quantity(error)))); + } + if matches!(error.typ, CFormatErrorType::IncompleteFormat) { + let recovered = CFormatBytes::parse_from_bytes(&bytes_acquisition_prefix( + fmt, + error, + fmt.len(), + true, + )) + .expect("the acquisition prefix of an incomplete format must parse"); + return Ok((recovered, Some(DeferredPercentError::Incomplete(error)))); + } + let (prefix_len, replacement, deferred) = match error.typ { + CFormatErrorType::UnsupportedFormatChar(_) => { + (error.index, true, DeferredPercentError::Unsupported(error)) + } + CFormatErrorType::UnmatchedKeyParentheses => ( + error + .index + .checked_sub(1) + .expect("an incomplete mapping key follows its percent sign"), + false, + DeferredPercentError::IncompleteMappingKey(error), + ), + _ => return Err(PyError::value_error(error.to_string())), + }; + let mut prefix = fmt[..prefix_len].to_vec(); + if replacement { + prefix.push(b's'); + } + let recovered = CFormatBytes::parse_from_bytes(&prefix) + .expect("the complete prefix of a deferred percent-format error must parse"); + Ok((recovered, Some(deferred))) + } + } +} /// `str % args` — printf-style string formatting. /// @@ -36,8 +351,7 @@ pub(crate) unsafe fn str_format_percent(fmt: PyObjectRef, args: PyObjectRef) -> let args_slot = pyre_object::gc_roots::shadow_stack_len(); let args = pyre_object::gc_roots::pin_root(args); let fmt_str = w_str_get_wtf8(fmt); - let format = CFormatWtf8::parse_from_wtf8(fmt_str) - .map_err(|err| PyError::value_error(err.to_string()))?; + let (format, deferred_error) = parse_wtf8_incremental(fmt_str)?; // `unicodeobject.c PyUnicode_Format` — the operand is usable as a // mapping (for `%(key)s` lookups) when it exposes `__getitem__` and is @@ -66,7 +380,8 @@ pub(crate) unsafe fn str_format_percent(fmt: PyObjectRef, args: PyObjectRef) -> let mut result = Wtf8Buf::new(); let mut saw_specifier = false; - for (idx, part) in format { + let mut parts = format.into_iter().peekable(); + while let Some((idx, part)) = parts.next() { match part { CFormatPart::Literal(literal) => result.push_wtf8(&literal), CFormatPart::Spec(CFormatSpecKeyed { @@ -74,6 +389,7 @@ pub(crate) unsafe fn str_format_percent(fmt: PyObjectRef, args: PyObjectRef) -> mut spec, }) => { saw_specifier = true; + let current_deferred = deferred_error.filter(|_| parts.peek().is_none()); let value = if let Some(key) = mapping_key { let Some(dict_slot) = dict else { return Err(PyError::type_error("format requires a mapping")); @@ -85,6 +401,19 @@ pub(crate) unsafe fn str_format_percent(fmt: PyObjectRef, args: PyObjectRef) -> // A keyed spec still consumes a positional slot when one // is available (`%(k)s %s` leaves nothing for the `%s`). let _ = pos.next(); + let w_value = pyre_object::gc_roots::pin_root(w_value); + mapping_star_operands( + &mut spec, + w_value, + current_deferred + .and_then(DeferredPercentError::before_conversion) + .is_none(), + )?; + if let Some(error) = + current_deferred.and_then(DeferredPercentError::before_conversion) + { + return Err(PyError::value_error(error.to_string())); + } w_value } else { update_quantity_from_tuple( @@ -93,6 +422,11 @@ pub(crate) unsafe fn str_format_percent(fmt: PyObjectRef, args: PyObjectRef) -> &mut spec.flags, )?; update_precision_from_tuple(&mut pos, &mut spec.precision)?; + if let Some(error) = + current_deferred.and_then(DeferredPercentError::before_conversion) + { + return Err(PyError::value_error(error.to_string())); + } let Some(v) = pos.next() else { return Err(PyError::type_error( "not enough arguments for format string", @@ -100,11 +434,18 @@ pub(crate) unsafe fn str_format_percent(fmt: PyObjectRef, args: PyObjectRef) -> }; v }; + if let Some(error) = current_deferred.and_then(DeferredPercentError::unsupported) { + return Err(PyError::value_error(error.to_string())); + } result.push_wtf8(&spec_format_string(&spec, value, idx)?); } } } + if let Some(error) = deferred_error { + error.after_parts(dict.is_some())?; + } + // `checkconsumed` — surplus positional values are converted to an error // only when the operand is not a mapping. With no specifiers at all, an // empty tuple / a mapping is allowed but any other non-empty operand is @@ -150,21 +491,21 @@ unsafe fn bytes_format_percent_inner(fmt: PyObjectRef, args: PyObjectRef) -> PyR let args_slot = pyre_object::gc_roots::shadow_stack_len(); let args = pyre_object::gc_roots::pin_root(args); let fmt_bytes = pyre_object::bytesobject::bytes_like_data(fmt); - let format = CFormatBytes::parse_from_bytes(fmt_bytes) - .map_err(|err| PyError::value_error(err.to_string()))?; + let (format, deferred_error) = parse_bytes_incremental(fmt_bytes)?; let (num_specifiers, mapping_required) = format .check_specifiers() .ok_or_else(|| PyError::type_error("format requires a mapping"))?; + let mut parts = format.into_iter().peekable(); let is_mapping = bytes_format_is_mapping(args); let mut result = Vec::new(); - if num_specifiers == 0 { + if num_specifiers == 0 && deferred_error.is_none() { if !is_mapping && !bytes_format_empty_tuple(args) { return Err(PyError::type_error( "not all arguments converted during bytes formatting", )); } - for (_, part) in format { + for (_, part) in parts { match part { CFormatPart::Literal(literal) => result.extend(literal), CFormatPart::Spec(_) => unreachable!(), @@ -177,19 +518,44 @@ unsafe fn bytes_format_percent_inner(fmt: PyObjectRef, args: PyObjectRef) -> PyR if !is_mapping { return Err(PyError::type_error("format requires a mapping")); } - for (_, part) in format { + while let Some((_, part)) = parts.next() { match part { CFormatPart::Literal(literal) => result.extend(literal), - CFormatPart::Spec(CFormatSpecKeyed { mapping_key, spec }) => { + CFormatPart::Spec(CFormatSpecKeyed { + mapping_key, + mut spec, + }) => { + let current_deferred = deferred_error.filter(|_| parts.peek().is_none()); let key = mapping_key.expect("mapping spec carries a key"); let value = crate::baseobjspace::getitem( pyre_object::gc_roots::shadow_stack_get(args_slot), pyre_object::w_bytes_from_bytes(&key), )?; + let value = pyre_object::gc_roots::pin_root(value); + mapping_star_operands( + &mut spec, + value, + current_deferred + .and_then(DeferredPercentError::before_conversion) + .is_none(), + )?; + if let Some(error) = + current_deferred.and_then(DeferredPercentError::before_conversion) + { + return Err(PyError::value_error(error.to_string())); + } + if let Some(error) = + current_deferred.and_then(DeferredPercentError::unsupported) + { + return Err(PyError::value_error(error.to_string())); + } result.extend(spec_format_bytes(&spec, value)?); } } } + if let Some(error) = deferred_error { + error.after_parts(is_mapping)?; + } return Ok(bytes_format_result(fmt, &result)); } @@ -207,22 +573,35 @@ unsafe fn bytes_format_percent_inner(fmt: PyObjectRef, args: PyObjectRef) -> PyR cursor: 0, }; - for (_, part) in format { + while let Some((_, part)) = parts.next() { match part { CFormatPart::Literal(literal) => result.extend(literal), CFormatPart::Spec(CFormatSpecKeyed { mut spec, .. }) => { + let current_deferred = deferred_error.filter(|_| parts.peek().is_none()); update_quantity_from_tuple(&mut pos, &mut spec.min_field_width, &mut spec.flags)?; update_precision_from_tuple(&mut pos, &mut spec.precision)?; + if let Some(error) = + current_deferred.and_then(DeferredPercentError::before_conversion) + { + return Err(PyError::value_error(error.to_string())); + } let Some(value) = pos.next() else { return Err(PyError::type_error( "not enough arguments for format string", )); }; + if let Some(error) = current_deferred.and_then(DeferredPercentError::unsupported) { + return Err(PyError::value_error(error.to_string())); + } result.extend(spec_format_bytes(&spec, value)?); } } } + if let Some(error) = deferred_error { + error.after_parts(is_mapping)?; + } + if pos.has_next() { Err(PyError::type_error( "not all arguments converted during bytes formatting", @@ -720,6 +1099,51 @@ unsafe fn update_precision_from_tuple( Ok(()) } +/// Consume `*` fields on a keyed conversion in CPython 3.14 order. +/// +/// PyPy `StringFormatter.parse_fmt` obtains `w_value` from +/// `getmappingvalue`, then `peel_num` asks `nextinputvalue` for each star. +/// CPython 3.14's `PyUnicode_Format` observably uses the mapped value as that +/// first star operand: `'%(x)*s' % {'x': 'a'}` raises `* wants int`, while an +/// integer mapped value is consumed and the conversion then raises `not enough +/// arguments for format string`. Keep PyPy's lookup-then-star control-flow, +/// with the 3.14 operand source at this spec-deviation site. +unsafe fn mapping_star_operands( + spec: &mut CFormatSpec, + mapped_value: PyObjectRef, + conversion_follows: bool, +) -> Result<(), PyError> { + let has_width_star = matches!(spec.min_field_width, Some(CFormatQuantity::FromValuesTuple)); + let has_precision_star = matches!( + spec.precision, + Some(CFormatPrecision::Quantity(CFormatQuantity::FromValuesTuple)) + ); + if !has_width_star && !has_precision_star { + return Ok(()); + } + + let base = pyre_object::gc_roots::shadow_stack_len(); + let _ = pyre_object::gc_roots::pin_root(mapped_value); + let mut mapped = OperandColumn { + base, + len: 1, + cursor: 0, + }; + update_quantity_from_tuple(&mut mapped, &mut spec.min_field_width, &mut spec.flags)?; + update_precision_from_tuple(&mut mapped, &mut spec.precision)?; + + if !conversion_follows { + return Ok(()); + } + + // At least one star consumed the sole mapped value. The conversion still + // requires its own operand, exactly like BaseStringFormatter.format's + // `nextinputvalue` after conversion-character validation. + Err(PyError::type_error( + "not enough arguments for format string", + )) +} + #[derive(Clone, Copy)] enum StarField { Width, @@ -760,3 +1184,221 @@ unsafe fn star_int(arg: Option, field: StarField) -> Result Option Result Result { require_list_receiver(args, "extend", true)?; arity_exact(args, "extend", 1)?; - let list = args[0]; - let other = args[1]; + let mut list = args[0]; + let mut other = args[1]; unsafe { // listobject.py:1019-1033 only takes the storage-copy path when a // list/tuple uses its inherited iterator. An overridden subclass // must use the generic incremental iterator path below. if is_exact_list(other) { + // BaseRangeListStrategy.extend switches its receiver before even + // an empty donor is examined. Keep both operands rooted across + // that materialisation and the append loop it feeds. + let _roots = pyre_object::gc_roots::push_roots(); + let root_base = pyre_object::gc_roots::pin_roots(&[list, other]); + list = pyre_object::listobject::w_list_materialize_range(list); + other = pyre_object::gc_roots::shadow_stack_get(root_base + 1); let n = w_list_len(other); + if w_list_len(list) == 0 && pyre_object::listobject::w_list_is_range_strategy(other) { + // EmptyListStrategy._extend_from_list delegates to the + // donor's copy_into; BaseRangeListStrategy shares its + // immutable erased tuple rather than appending boxed ints. + pyre_object::listobject::w_list_setslice(list, 0, 0, other) + .expect("range copy_into an empty exact list"); + return Ok(w_none()); + } pyre_object::listobject::w_list_reserve_for_extend(list, n); for i in 0..n { + list = pyre_object::gc_roots::shadow_stack_get(root_base); + other = pyre_object::gc_roots::shadow_stack_get(root_base + 1); if let Some(item) = w_list_getitem(other, i as i64) { pyre_object::listobject::w_list_append_preallocated(list, item); } @@ -786,6 +803,9 @@ pub fn list_method_copy(args: &[PyObjectRef]) -> Result = LazyLock::new(|| { false, false, ), + // AsciiListStrategy stores exact ASCII `_utf8` rpython-string + // pointers in the same GcArray(GCREF) representation. + ( + "ascii_items.len", + std::mem::offset_of!(W_ListObject, ascii_items) + + pyre_object::unicode_array::UNICODE_ARRAY_LEN_OFFSET, + std::mem::size_of::(), + Type::Int, + false, + false, + false, + ), + ( + "ascii_items.block", + std::mem::offset_of!(W_ListObject, ascii_items) + + pyre_object::unicode_array::UNICODE_ARRAY_BLOCK_OFFSET, + std::mem::size_of::(), + Type::Ref, + false, + false, + false, + ), ], "W_ListObject", "listobject::W_ListObject", @@ -4162,6 +4184,14 @@ pub fn list_bytes_items_block_descr() -> DescrRef { field_descr_from_group(&W_LIST_DESCR_GROUP, 11) } +pub fn list_ascii_items_len_descr() -> DescrRef { + field_descr_from_group(&W_LIST_DESCR_GROUP, 12) +} + +pub fn list_ascii_items_block_descr() -> DescrRef { + field_descr_from_group(&W_LIST_DESCR_GROUP, 13) +} + pub fn list_w_class_descr() -> DescrRef { field_descr_from_group(&W_LIST_DESCR_GROUP, 7) } @@ -6155,6 +6185,12 @@ mod tests { list_bytes_items_block_descr(), Type::Ref, ), + ("ascii_items.len", list_ascii_items_len_descr(), Type::Int), + ( + "ascii_items.block", + list_ascii_items_block_descr(), + Type::Ref, + ), ] { let descr = make_descr_from_bh(&BhDescr::Field { offset: 0, @@ -6194,6 +6230,7 @@ mod tests { ("int_items", list_int_items_block_descr()), ("float_items", list_float_items_block_descr()), ("bytes_items", list_bytes_items_block_descr()), + ("ascii_items", list_ascii_items_block_descr()), ] { let descr = make_descr_from_bh(&BhDescr::Field { offset: 0, @@ -6277,6 +6314,10 @@ mod tests { &(std::mem::offset_of!(W_ListObject, bytes_items) + pyre_object::bytes_array::BYTES_ARRAY_BLOCK_OFFSET) )); + assert!(list_gc_offsets.contains( + &(std::mem::offset_of!(W_ListObject, ascii_items) + + pyre_object::unicode_array::UNICODE_ARRAY_BLOCK_OFFSET) + )); } #[test] @@ -7429,6 +7470,8 @@ pub fn make_descr_from_bh(bh: &majit_translate::jitcode::BhDescr) -> DescrRef { "float_items.block" => return list_float_items_block_descr(), "bytes_items.len" => return list_bytes_items_len_descr(), "bytes_items.block" => return list_bytes_items_block_descr(), + "ascii_items.len" => return list_ascii_items_len_descr(), + "ascii_items.block" => return list_ascii_items_block_descr(), // A bare `int_items` / `float_items` read addresses the // typed-storage struct base, which is its first field // (`block`, `INT_ARRAY_BLOCK_OFFSET == 0`) — the same @@ -7439,6 +7482,7 @@ pub fn make_descr_from_bh(bh: &majit_translate::jitcode::BhDescr) -> DescrRef { "int_items" => return list_int_items_block_descr(), "float_items" => return list_float_items_block_descr(), "bytes_items" => return list_bytes_items_block_descr(), + "ascii_items" => return list_ascii_items_block_descr(), // The `w_list_append` body's `match list.strategy` reads the // header `strategy` field directly. The codewriter resolves // its offset but produces a `SimpleFieldDescr` with no diff --git a/pyre/pyre-jit-trace/src/helpers.rs b/pyre/pyre-jit-trace/src/helpers.rs index 857905f48fd..1d43919fa96 100644 --- a/pyre/pyre-jit-trace/src/helpers.rs +++ b/pyre/pyre-jit-trace/src/helpers.rs @@ -928,8 +928,9 @@ pub fn emit_mapdict_add_unboxed_attr_inline( /// emit reproduces it for any element types and for zero arguments. pub fn emit_object_list_inline(ctx: &mut TraceCtx, items: &[OpRef]) -> OpRef { use crate::descr::{ - list_bytes_items_len_descr, list_float_items_len_descr, list_int_items_len_descr, - list_items_descr, list_length_descr, list_strategy_descr, w_list_size_descr, + list_ascii_items_len_descr, list_bytes_items_len_descr, list_float_items_len_descr, + list_int_items_len_descr, list_items_descr, list_length_descr, list_strategy_descr, + w_list_size_descr, }; use crate::state::pyobject_gcarray_descr; @@ -972,6 +973,7 @@ pub fn emit_object_list_inline(ctx: &mut TraceCtx, items: &[OpRef]) -> OpRef { list_int_items_len_descr(), list_float_items_len_descr(), list_bytes_items_len_descr(), + list_ascii_items_len_descr(), ] { let inactive_len_idx = inactive_len_descr.index(); ctx.record_op_with_descr(OpCode::SetfieldGc, &[list, zero], inactive_len_descr); @@ -1007,8 +1009,8 @@ pub fn emit_object_list_inline(ctx: &mut TraceCtx, items: &[OpRef]) -> OpRef { /// OptVirtualize folds the whole wrapper when the list never escapes. pub fn emit_empty_list_inline(ctx: &mut TraceCtx) -> OpRef { use crate::descr::{ - list_bytes_items_len_descr, list_float_items_len_descr, list_int_items_len_descr, - list_length_descr, list_strategy_descr, w_list_size_descr, + list_ascii_items_len_descr, list_bytes_items_len_descr, list_float_items_len_descr, + list_int_items_len_descr, list_length_descr, list_strategy_descr, w_list_size_descr, }; let list = ctx.record_op_with_descr(OpCode::NewWithVtable, &[], w_list_size_descr()); @@ -1024,6 +1026,7 @@ pub fn emit_empty_list_inline(ctx: &mut TraceCtx) -> OpRef { list_int_items_len_descr(), list_float_items_len_descr(), list_bytes_items_len_descr(), + list_ascii_items_len_descr(), ] { let inactive_len_idx = inactive_len_descr.index(); ctx.record_op_with_descr(OpCode::SetfieldGc, &[list, zero], inactive_len_descr); @@ -1150,8 +1153,8 @@ pub fn emit_typed_list_inline( strategy: pyre_object::listobject::ListStrategy, ) -> OpRef { use crate::descr::{ - list_bytes_items_len_descr, list_float_items_len_descr, list_int_items_len_descr, - list_length_descr, list_strategy_descr, w_list_size_descr, + list_ascii_items_len_descr, list_bytes_items_len_descr, list_float_items_len_descr, + list_int_items_len_descr, list_length_descr, list_strategy_descr, w_list_size_descr, }; let len = raws.len(); @@ -1189,6 +1192,7 @@ pub fn emit_typed_list_inline( list_int_items_len_descr(), list_float_items_len_descr(), list_bytes_items_len_descr(), + list_ascii_items_len_descr(), ] { let scalar_idx = scalar_descr.index(); ctx.record_op_with_descr(OpCode::SetfieldGc, &[list, zero], scalar_descr); @@ -1214,10 +1218,11 @@ pub fn emit_typed_list_inline( /// Empty->typed in-place promotion of an existing `W_ListObject` wrapper (the /// comprehension accumulator). Mirrors `switch_to_correct_strategy`'s concrete -/// effect as field mutations on `list_op`: allocate the capacity-1 typed -/// backing block, then set strategy and the matching empty storage fields. -/// Length stays 0; the subsequent append body sub-walk fills slot 0 through -/// the spare-capacity leg. +/// effect as field mutations on `list_op`: set the strategy and stage the +/// capacity-4 storage produced by the first RPython `_ll_list_resize_ge`. +/// `w_list_switch_to_strategy_for` applies the same concrete pre-grow before +/// this emitter runs; the following append sub-walk therefore records only +/// the length/item stores against identical concrete and symbolic shapes. pub fn emit_promote_empty_list_inline( ctx: &mut TraceCtx, list_op: OpRef, @@ -1229,7 +1234,7 @@ pub fn emit_promote_empty_list_inline( }; use crate::state::{float_gcarray_descr, int_gcarray_descr, pyobject_gcarray_descr}; - let cap_ref = ctx.const_int(1); + let cap_ref = ctx.const_int(4); let zero_ref = ctx.const_int(0); match strategy { @@ -1257,14 +1262,6 @@ pub fn emit_promote_empty_list_inline( let items_block_idx = items_block_descr.index(); ctx.record_op_with_descr(OpCode::SetfieldGc, &[list_op, block], items_block_descr); ctx.heapcache_setfield_cached(list_op, items_block_idx, block); - // Seed the block's capacity getfield cache with the const (1). The - // block is a fresh const-size allocation whose capacity is known, - // matching the heapcache length tracking a `new_array` gets for a - // const-length array (heapcache.py `new_array` → - // `arraylen_now_known`). The append body sub-walk reads - // `ItemsBlock.capacity` via a getfield (not arraylen), so seed that - // field-index channel explicitly; otherwise the read stays symbolic - // and the spare-capacity `0 < capacity` branch cannot fold. let cap_idx = crate::descr::items_block_capacity_descr().index(); ctx.heapcache_setfield_cached(block, cap_idx, cap_ref); } @@ -1292,9 +1289,6 @@ pub fn emit_promote_empty_list_inline( let items_block_idx = items_block_descr.index(); ctx.record_op_with_descr(OpCode::SetfieldGc, &[list_op, block], items_block_descr); ctx.heapcache_setfield_cached(list_op, items_block_idx, block); - // Seed the block's capacity getfield cache with the const (1); see - // the Integer arm above for the rationale (const-size block, getfield - // capacity channel distinct from the `new_array` arraylen seed). let cap_idx = crate::descr::items_block_capacity_descr().index(); ctx.heapcache_setfield_cached(block, cap_idx, cap_ref); } @@ -1322,21 +1316,28 @@ pub fn emit_promote_empty_list_inline( let items_idx = items_descr.index(); ctx.record_op_with_descr(OpCode::SetfieldGc, &[list_op, block], items_descr); ctx.heapcache_setfield_cached(list_op, items_idx, block); - // Object storage needs no capacity seed: the append body reads - // capacity through `list.items` (list_items_descr), a path that - // already resolves to the concrete block. } pyre_object::listobject::ListStrategy::Empty + | pyre_object::listobject::ListStrategy::Size + | pyre_object::listobject::ListStrategy::SimpleRange + | pyre_object::listobject::ListStrategy::Range | pyre_object::listobject::ListStrategy::IntOrFloat - | pyre_object::listobject::ListStrategy::Bytes => { + | pyre_object::listobject::ListStrategy::Bytes + | pyre_object::listobject::ListStrategy::Ascii => { // The specialized first-append path only admits Integer, Float, // or Object. Exact bytes are declined before this emitter; // IntOrFloat is reached later by a numeric strategy transition. + // BaseRangeListStrategy append materialises first, so compact + // range storage cannot enter this Empty-to-typed emitter. debug_assert!(matches!( strategy, pyre_object::listobject::ListStrategy::Empty + | pyre_object::listobject::ListStrategy::Size + | pyre_object::listobject::ListStrategy::SimpleRange + | pyre_object::listobject::ListStrategy::Range | pyre_object::listobject::ListStrategy::IntOrFloat | pyre_object::listobject::ListStrategy::Bytes + | pyre_object::listobject::ListStrategy::Ascii )); } } diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs index 6ec9ca950bd..ea8be3366af 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/fbw_state.rs @@ -1395,7 +1395,11 @@ pub(crate) fn fbw_store_journal_rollback() { } pyre_object::listobject::ListStrategy::Float | pyre_object::listobject::ListStrategy::Empty - | pyre_object::listobject::ListStrategy::Bytes => { + | pyre_object::listobject::ListStrategy::Size + | pyre_object::listobject::ListStrategy::SimpleRange + | pyre_object::listobject::ListStrategy::Range + | pyre_object::listobject::ListStrategy::Bytes + | pyre_object::listobject::ListStrategy::Ascii => { crate::trace::fbw_diag::bump( crate::trace::fbw_diag::STORE_JOURNAL_ROLLBACK_FAILED, ); @@ -1442,9 +1446,15 @@ pub(crate) fn fbw_store_journal_rollback() { // Empty never enters the append journal (no spare-capacity // fold path records it); nothing to rewind. pyre_object::listobject::ListStrategy::Empty => {} + pyre_object::listobject::ListStrategy::Size => {} + // BaseRangeListStrategy append materialises before the + // append, so compact range storage is never journalled. + pyre_object::listobject::ListStrategy::SimpleRange => {} + pyre_object::listobject::ListStrategy::Range => {} // Bytes append does not enter this journal until the // walker has a BytesBlock store emitter. pyre_object::listobject::ListStrategy::Bytes => {} + pyre_object::listobject::ListStrategy::Ascii => {} } pyre_object::listobject::w_list_set_allocated(list, allocated_before); } diff --git a/pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs b/pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs index 75882d2faa3..37334acf2dc 100644 --- a/pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs +++ b/pyre/pyre-jit-trace/src/jitcode_dispatch/specialize.rs @@ -5079,8 +5079,16 @@ pub(crate) fn try_walker_specialize_newlist( // The generic residual constructs the erased rpython-string array. // The walker has no BytesBlock payload emitter yet. ListStrategy::Bytes => return Ok(None), - // Empty is impossible here (len >= 1); decline defensively. - ListStrategy::Empty => return Ok(None), + // The generic residual constructs AsciiListStrategy's erased UTF-8 + // storage; the walker has no raw UnicodeValueStorage emitter yet. + ListStrategy::Ascii => return Ok(None), + // Empty is impossible here (len >= 1); decline defensively. Range + // storage is built only by the interpreter-internal `make_range_list` + // seam and has no walker-native erased-tuple emitter yet. + ListStrategy::Empty + | ListStrategy::Size + | ListStrategy::SimpleRange + | ListStrategy::Range => return Ok(None), }; // Concrete shadow: a fresh list built from the element shadows @@ -13292,7 +13300,8 @@ unsafe fn orthodox_list_append_recognize( let obj_ok = !value.is_null() && !pyre_object::is_plain_int1(value) && !pyre_object::is_float_strategy_item(value) - && !pyre_object::pyobject::is_exact_type(value, &pyre_object::bytesobject::BYTES_TYPE); + && !pyre_object::is_bytes_strategy_item(value) + && !pyre_object::is_ascii_strategy_item(value); if !int_ok && !float_ok && !obj_ok { return None; } @@ -13520,7 +13529,7 @@ pub(crate) fn orthodox_list_append_commit( sub_body: &SubJitCodeBody, self_ref: OpRef, value_op: OpRef, - inner_self: pyre_object::PyObjectRef, + mut inner_self: pyre_object::PyObjectRef, value: pyre_object::PyObjectRef, len_before: usize, ) -> Result<(), DispatchError> { @@ -13577,12 +13586,16 @@ pub(crate) fn orthodox_list_append_commit( .heap_cache_mut() .replace_box(strategy_ref, expected); // Emit the transition IR mutating the existing wrapper (helpers.rs). - // The emitter seeds the new block's capacity getfield cache so the - // append body sub-walk's spare-capacity `0 < capacity` check folds. + // It stages the same first 0 -> 4 RPython grow as the concrete helper, + // leaving the append body to record the length/item stores. crate::helpers::emit_promote_empty_list_inline(ctx.trace_ctx, self_ref, target); // Concrete promotion of the real list, then journal so a non-commit // walk rolls back to Empty. - unsafe { pyre_object::w_list_switch_to_strategy_for(inner_self, value) }; + inner_self = unsafe { pyre_object::w_list_switch_to_strategy_for(inner_self, value) }; + ctx.trace_ctx.set_opref_concrete( + self_ref, + majit_ir::Value::Ref(majit_ir::GcRef(inner_self as usize)), + ); fbw_append_promote_journal_push(inner_self); } diff --git a/pyre/pyre-jit/src/eval.rs b/pyre/pyre-jit/src/eval.rs index 85ab8e986ed..2db40b1d24d 100644 --- a/pyre/pyre-jit/src/eval.rs +++ b/pyre/pyre-jit/src/eval.rs @@ -1172,7 +1172,7 @@ unsafe fn unicode_user_object_custom_trace( }; } -/// Custom trace for `W_ListObject` under the Object strategy. `items` +/// Custom trace for `W_ListObject`. Under the Object strategy, `items` /// points at an off-GC `std::alloc`'d `ItemsBlock` /// (`object_array::alloc_items_block`), so the element slots are /// unreachable through inline `gc_ptr_offsets` — the collector would see @@ -1180,17 +1180,33 @@ unsafe fn unicode_user_object_custom_trace( /// untraced (a major collection then sweeps an element reachable only via /// the list). Forward each live element slot in place, exactly as /// `tuple_object_custom_trace`, so a moving collector relocates young -/// elements and a major collection marks them. Only the Object strategy -/// stores `PyObjectRef`s; Integer/Float/Bytes keep typed arrays (`items` null) -/// and Empty has no block. Trace `length` live slots, not capacity — the -/// spare tail past the live length may hold stale pointers a shrink left -/// behind. +/// elements and a major collection marks them. SizeListStrategy uses the same +/// inactive field as one direct edge to its shared strategy-state box; +/// Integer/Float/Bytes keep typed arrays (`items` null) and Empty has no block. +/// Trace `length` live slots for Object, not capacity — the spare tail past the +/// live length may hold stale pointers a shrink left behind. unsafe fn list_object_custom_trace(obj_addr: usize, f: &mut dyn FnMut(*mut majit_ir::GcRef)) { let list_ptr = obj_addr as *mut pyre_object::listobject::W_ListObject; let list = unsafe { &mut *list_ptr }; f(&mut list.ob_header.w_class as *mut pyre_object::PyObjectRef as *mut majit_ir::GcRef); f(&mut list.w_slots as *mut pyre_object::PyObjectRef as *mut majit_ir::GcRef); - if list.strategy == pyre_object::listobject::ListStrategy::Object && !list.items.is_null() { + if matches!( + list.strategy, + pyre_object::listobject::ListStrategy::Size + | pyre_object::listobject::ListStrategy::SimpleRange + | pyre_object::listobject::ListStrategy::Range + ) && !list.items.is_null() + { + // SizeListStrategy is an ordinary shared RPython strategy instance; + // the range strategies share their immutable erased tuple. Pyre keeps + // either state box in the otherwise inactive `items` edge. + let items_slot = unsafe { std::ptr::addr_of_mut!((*list_ptr).items) }; + if pyre_object::gc_hook::try_gc_owns_object(unsafe { *items_slot } as *mut u8) { + f(items_slot as *mut majit_ir::GcRef); + } + } else if list.strategy == pyre_object::listobject::ListStrategy::Object + && !list.items.is_null() + { if pyre_object::gc_hook::try_gc_owns_object(list.items as *mut u8) { // A GC-managed (moving) block is forwarded by handing the // collector the `items` field slot itself; the type-9 varsize walker @@ -1246,6 +1262,20 @@ unsafe fn list_object_custom_trace(obj_addr: usize, f: &mut dyn FnMut(*mut majit } } } + // AsciiListStrategy has the same `GcArray(GCREF)` shape, with each entry + // naming the shared `W_UnicodeObject._utf8` storage rather than a wrapper. + let ascii_block_slot = unsafe { std::ptr::addr_of_mut!((*list_ptr).ascii_items.block) }; + let ascii_block = unsafe { *ascii_block_slot }; + if !ascii_block.is_null() { + if pyre_object::gc_hook::try_gc_owns_object(ascii_block as *mut u8) { + f(ascii_block_slot as *mut majit_ir::GcRef); + } else { + let base = unsafe { pyre_object::object_array::items_block_items_base(ascii_block) }; + for i in 0..list.ascii_items.len() { + f(unsafe { base.add(i) } as *mut majit_ir::GcRef); + } + } + } } /// Custom trace for `W_MemoryView`. Its geometry and backing live in an diff --git a/pyre/pyre-object/src/bytes_array.rs b/pyre/pyre-object/src/bytes_array.rs index 2b4082e9a98..6dc322fd841 100644 --- a/pyre/pyre-object/src/bytes_array.rs +++ b/pyre/pyre-object/src/bytes_array.rs @@ -47,6 +47,19 @@ impl BytesArray { } } + /// `AbstractUnwrappedStrategy.get_empty_storage(sizehint)` for bytes. + pub fn with_capacity(capacity: usize) -> Self { + if capacity == 0 { + return Self::empty(); + } + Self { + block: unsafe { + crate::object_array::grow_list_items_block_gc(std::ptr::null_mut(), capacity, 0) + }, + len: 0, + } + } + #[must_use] pub fn pin_block(&self) -> usize { let slot = crate::gc_roots::shadow_stack_len(); diff --git a/pyre/pyre-object/src/float_array.rs b/pyre/pyre-object/src/float_array.rs index 54bcc9ee1a9..b514d5fe4b1 100644 --- a/pyre/pyre-object/src/float_array.rs +++ b/pyre/pyre-object/src/float_array.rs @@ -7,7 +7,7 @@ use crate::object_array::{ typed_items_block_capacity, }; -pub const FLOAT_ARRAY_INLINE_CAP: usize = 8; +pub const FLOAT_ARRAY_INLINE_CAP: usize = 4; /// Unboxed `float` list storage — `listobject.py` FloatListStrategy /// `lstorage = erase([float])`, i.e. a `Ptr(GcArray(Float))`. @@ -88,6 +88,17 @@ impl FloatArray { arr } + /// `AbstractUnwrappedStrategy.get_empty_storage(sizehint)` for floats. + pub fn with_capacity(capacity: usize) -> Self { + if capacity == 0 { + return Self::empty(); + } + Self { + block: unsafe { alloc_typed_items_block(capacity, gc_float_array_gc_type_id()) }, + len: AtomicUsize::new(0), + } + } + /// Pin `block` on the shadow stack and return its slot. The /// [`crate::int_array::IntArray::pin_block`] twin — see it for why an /// old-gen, non-moving block still has to be rooted before the next GC @@ -160,8 +171,10 @@ impl FloatArray { } fn grow(&mut self, min_cap: usize) { + let extra = if min_cap < 9 { 3 } else { 6 }; let target_cap = min_cap - .max(self.capacity().saturating_mul(2)) + .saturating_add(extra) + .saturating_add(min_cap >> 3) .max(FLOAT_ARRAY_INLINE_CAP); self.block = unsafe { grow_typed_items_block( diff --git a/pyre/pyre-object/src/int_array.rs b/pyre/pyre-object/src/int_array.rs index fbe0d7835e5..8f2d9390a87 100644 --- a/pyre/pyre-object/src/int_array.rs +++ b/pyre/pyre-object/src/int_array.rs @@ -10,7 +10,7 @@ use crate::object_array::{ /// Small-buffer capacity constant retained for the append/pop inline-capacity /// trace path (`is_inline()` is always false, so it is never consulted at /// runtime). -pub const INT_ARRAY_INLINE_CAP: usize = 8; +pub const INT_ARRAY_INLINE_CAP: usize = 4; /// Unboxed `int` list storage — `listobject.py` IntegerListStrategy /// `lstorage = erase([int])`, i.e. a `Ptr(GcArray(Signed))`. @@ -106,6 +106,18 @@ impl IntArray { arr } + /// `AbstractUnwrappedStrategy.get_empty_storage(sizehint)`: allocate the + /// exact hinted RPython items array while keeping its live length zero. + pub fn with_capacity(capacity: usize) -> Self { + if capacity == 0 { + return Self::empty(); + } + Self { + block: unsafe { alloc_typed_items_block(capacity, gc_int_array_gc_type_id()) }, + len: AtomicUsize::new(0), + } + } + /// Pin `block` on the shadow stack and return its slot, so the block stays /// live across a following GC operation. /// @@ -203,8 +215,12 @@ impl IntArray { } fn grow(&mut self, min_cap: usize) { + // rlist.py `_ll_list_resize_hint_really(overallocate=True)`: + // 0, 4, 8, 16, 25, 35, ... + let extra = if min_cap < 9 { 3 } else { 6 }; let target_cap = min_cap - .max(self.capacity().saturating_mul(2)) + .saturating_add(extra) + .saturating_add(min_cap >> 3) .max(INT_ARRAY_INLINE_CAP); self.block = unsafe { grow_typed_items_block( diff --git a/pyre/pyre-object/src/lib.rs b/pyre/pyre-object/src/lib.rs index 23a47bdf737..d287189058f 100644 --- a/pyre/pyre-object/src/lib.rs +++ b/pyre/pyre-object/src/lib.rs @@ -64,6 +64,7 @@ pub mod tagged_int; pub mod tupleobject; pub mod typedef; pub mod typeobject; +pub mod unicode_array; pub mod unicodeobject; pub mod weakref; diff --git a/pyre/pyre-object/src/listobject.rs b/pyre/pyre-object/src/listobject.rs index 1372846dbab..fb9f64af966 100644 --- a/pyre/pyre-object/src/listobject.rs +++ b/pyre/pyre-object/src/listobject.rs @@ -9,8 +9,9 @@ #![allow(dead_code)] use crate::object_array::{ - ItemsBlock, alloc_list_items_block_gc, dealloc_list_items_block, grow_list_items_block_gc, - items_block_capacity, items_block_items_base, + ItemsBlock, TypedItemsBlock, alloc_list_items_block_gc, alloc_typed_items_block, + dealloc_list_items_block, gc_int_array_gc_type_id, grow_list_items_block_gc, + items_block_capacity, items_block_items_base, typed_items_block_items_base, }; use crate::pyobject::*; use crate::{ @@ -19,12 +20,13 @@ use crate::{ bytesobject::{BYTES_TYPE, w_bytes_block, w_bytes_from_block}, floatobject::w_float_get_value, floatobject::w_float_new, - intobject::w_int_get_value, - intobject::w_int_new, + intobject::{w_int_get_value, w_int_new}, longobject::jit_bigint_to_i64_value, longobject::w_long_fits_int, longobject::w_long_get_value, tupleobject::is_plain_float_strict, + unicode_array::UnicodeArray, + unicodeobject::{w_str_from_storage, w_str_is_ascii, w_str_storage}, }; use std::cell::UnsafeCell; use std::sync::LazyLock; @@ -104,6 +106,18 @@ pub enum ListStrategy { IntOrFloat = 4, /// listobject.py BytesListStrategy — erased `[rpython str]` payloads. Bytes = 5, + /// listobject.py AsciiListStrategy — erased UTF-8 `[rpython str]` + /// payloads, restricted to exact ASCII strings. + Ascii = 6, + /// listobject.py SizeListStrategy — empty storage carrying the allocation + /// hint used when the first item selects a concrete strategy. + Size = 7, + /// listobject.py SimpleRangeListStrategy — the immutable storage tuple + /// contains only the positive length for the sequence 0..length. + SimpleRange = 8, + /// listobject.py RangeListStrategy — the immutable storage tuple contains + /// start, step, and positive length. + Range = 9, } impl ListStrategy { @@ -119,6 +133,10 @@ impl ListStrategy { Self::Empty => "EmptyListStrategy", Self::IntOrFloat => "IntOrFloatListStrategy", Self::Bytes => "BytesListStrategy", + Self::Ascii => "AsciiListStrategy", + Self::Size => "SizeListStrategy", + Self::SimpleRange => "SimpleRangeListStrategy", + Self::Range => "RangeListStrategy", } } } @@ -132,7 +150,8 @@ impl ListStrategy { /// offset-0 header holds the allocated capacity /// (upstream `len(l.items)` per rlist.py:251). /// -/// `strategy`, `int_items`, `float_items`, `bytes_items` implement PyPy's list +/// `strategy`, `int_items`, `float_items`, `bytes_items`, `ascii_items` +/// implement PyPy's list /// strategy split (`pypy/objspace/std/listobject.py`). Only the Object strategy /// reads/writes `length` + `items`; Integer/IntOrFloat/Float/Bytes strategies /// operate on their own typed arrays and keep `length = 0`, `items = null`. @@ -169,11 +188,15 @@ pub struct W_ListObject { /// capacity (= upstream `len(l.items)` per rlist.py:251). Null /// when the list is in a non-Object strategy (Empty/Integer/ /// IntOrFloat/Float/Bytes); lazily allocated on strategy switch. + /// SizeListStrategy instead uses this otherwise inactive traced slot for + /// its shared strategy-state box, matching `EmptyListStrategy.clone` + /// retaining the same `SizeListStrategy` instance. pub items: *mut ItemsBlock, pub strategy: ListStrategy, pub int_items: IntArray, pub float_items: FloatArray, pub bytes_items: BytesArray, + pub ascii_items: UnicodeArray, /// PyPy `BaseUserClassMapdict` indexed instance storage for a native /// `list` subclass declaring `__slots__`. Kept on the object itself, /// just like `W_UnicodeObject.w_slots`; `PY_NULL` means that no slot has @@ -191,6 +214,79 @@ pub struct W_ListObject { pub const W_LIST_GC_TYPE_ID: u32 = 7; pub const W_LIST_OBJECT_SIZE: usize = std::mem::size_of::(); +/// Allocate the translated `SizeListStrategy` instance payload. The object +/// space reference is process-global in pyre, leaving its sole per-instance +/// field (`sizehint`) as one Signed cell. This is GC state, not a Python int: +/// diagnostic APIs must not expose the strategy implementation as a W_Root. +unsafe fn alloc_sizehint_state(sizehint: i64) -> *mut TypedItemsBlock { + let state = alloc_typed_items_block(1, gc_int_array_gc_type_id()); + *(typed_items_block_items_base(state) as *mut i64) = sizehint; + state +} + +#[inline] +unsafe fn sizehint_state_value(state: *mut ItemsBlock) -> i64 { + *(typed_items_block_items_base(state as *mut TypedItemsBlock) as *const i64) +} + +#[inline] +unsafe fn set_sizehint_state_value(state: *mut ItemsBlock, sizehint: i64) { + *(typed_items_block_items_base(state as *mut TypedItemsBlock) as *mut i64) = sizehint; +} + +/// Allocate the immutable erased tuple used by PyPy's range list strategies. +/// `SimpleRangeListStrategy` stores `(length,)`; `RangeListStrategy` stores +/// `(start, step, length)`. These are RPython Signed cells, not Python ints. +unsafe fn alloc_range_state(values: &[i64]) -> *mut TypedItemsBlock { + let state = alloc_typed_items_block(values.len(), gc_int_array_gc_type_id()); + std::ptr::copy_nonoverlapping( + values.as_ptr(), + typed_items_block_items_base(state) as *mut i64, + values.len(), + ); + state +} + +#[inline] +unsafe fn range_state_value(state: *mut ItemsBlock, index: usize) -> i64 { + *((typed_items_block_items_base(state as *mut TypedItemsBlock) as *const i64).add(index)) +} + +#[inline] +unsafe fn range_list_length(list: &W_ListObject) -> usize { + let index = if list.strategy == ListStrategy::SimpleRange { + 0 + } else { + 2 + }; + usize::try_from(range_state_value(list.items, index)).unwrap() +} + +#[inline] +unsafe fn range_list_start_step(list: &W_ListObject) -> (i64, i64) { + if list.strategy == ListStrategy::SimpleRange { + (0, 1) + } else { + ( + range_state_value(list.items, 0), + range_state_value(list.items, 1), + ) + } +} + +#[inline] +unsafe fn range_list_item_unchecked(list: &W_ListObject, index: usize) -> i64 { + let (start, step) = range_list_start_step(list); + start + (index as i64) * step +} + +unsafe fn range_list_values(list: &W_ListObject) -> Vec { + let length = range_list_length(list); + (0..length) + .map(|index| range_list_item_unchecked(list, index)) + .collect() +} + impl W_ListObject { /// `_Py_atomic_load_ssize_relaxed(&ob_size)` on the Object-strategy length. /// @@ -211,7 +307,8 @@ impl W_ListObject { #[inline] fn live_len(&self) -> usize { match self.strategy { - ListStrategy::Empty => 0, + ListStrategy::Empty | ListStrategy::Size => 0, + ListStrategy::SimpleRange | ListStrategy::Range => unsafe { range_list_length(self) }, ListStrategy::Object => self.length_relaxed(), // Direct rlist `length` field reads keep this helper in the // annotator's structural subset; the public `.len()` wrappers are @@ -220,6 +317,7 @@ impl W_ListObject { ListStrategy::IntOrFloat => self.int_items.len(), ListStrategy::Float => self.float_items.len(), ListStrategy::Bytes => self.bytes_items.len(), + ListStrategy::Ascii => self.ascii_items.len(), } } @@ -279,9 +377,8 @@ impl W_ListObject { let _roots = crate::gc_roots::push_roots(); let obj_slot = crate::gc_roots::shadow_stack_len(); let obj = crate::gc_roots::pin_root(obj); - let list = &mut *(obj as *mut W_ListObject); - let current_cap = list.object_items_capacity(); - let target_cap = min_cap.max(current_cap.saturating_mul(2).max(4)); + let extra = if min_cap < 9 { 3 } else { 6 }; + let target_cap = min_cap.saturating_add(extra).saturating_add(min_cap >> 3); // The GC rewrite emits COND_CALL_GC_WB before SETFIELD_GC. Keep that // ordering on the host path too: the grow below allocates in the moving // nursery and may collect, so this old list has to be on the remembered @@ -311,6 +408,42 @@ impl W_ListObject { crate::gc_roots::shadow_stack_get(obj_slot) } + /// `AbstractUnwrappedStrategy.get_empty_storage(sizehint)` for the Object + /// strategy: publish an exact-capacity, zero-length RPython list backing. + unsafe fn object_resize_capacity(obj: PyObjectRef, capacity: usize) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let list = &*(obj as *const W_ListObject); + assert!(capacity >= list.length_relaxed()); + if capacity == list.object_items_capacity() { + return obj; + } + if capacity == 0 { + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + let old = list.items; + list.items = std::ptr::null_mut(); + dealloc_list_items_block(old); + return crate::gc_roots::shadow_stack_get(obj_slot); + } + list_write_barrier(obj); + let block_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &*(obj as *const W_ListObject); + let block = grow_list_items_block_gc(list.items, capacity, list.length_relaxed()); + let _ = crate::gc_roots::pin_root(block as PyObjectRef); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + let old = list.items; + list.items = crate::gc_roots::shadow_stack_get(block_slot) as *mut ItemsBlock; + dealloc_list_items_block(old); + crate::gc_roots::shadow_stack_get(obj_slot) + } + /// Grow `bytes_items` to accommodate at least `min_cap` slots — the /// Bytes-strategy counterpart of [`W_ListObject::object_grow`], and the /// only place a fresh `bytes_items` block may be published. @@ -328,9 +461,8 @@ impl W_ListObject { let _roots = crate::gc_roots::push_roots(); let obj_slot = crate::gc_roots::shadow_stack_len(); let obj = crate::gc_roots::pin_root(obj); - let list = &*(obj as *const W_ListObject); - let current_cap = list.bytes_items.heap_capacity(); - let target_cap = min_cap.max(current_cap.saturating_mul(2).max(4)); + let extra = if min_cap < 9 { 3 } else { 6 }; + let target_cap = min_cap.saturating_add(extra).saturating_add(min_cap >> 3); list_write_barrier(obj); let new_block_slot = crate::gc_roots::shadow_stack_len(); let obj = crate::gc_roots::shadow_stack_get(obj_slot); @@ -349,6 +481,41 @@ impl W_ListObject { crate::gc_roots::shadow_stack_get(obj_slot) } + /// RPython `_ll_list_resize_hint_really` for BytesListStrategy. + unsafe fn bytes_resize_capacity(obj: PyObjectRef, capacity: usize) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let list = &*(obj as *const W_ListObject); + assert!(capacity >= list.bytes_items.len()); + if capacity == list.bytes_items.heap_capacity() { + return obj; + } + if capacity == 0 { + let list = &mut *(obj as *mut W_ListObject); + let old = list.bytes_items.block; + list.bytes_items.block = std::ptr::null_mut(); + list.bytes_items.set_len(0); + dealloc_list_items_block(old); + return crate::gc_roots::shadow_stack_get(obj_slot); + } + list_write_barrier(obj); + let block_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &*(obj as *const W_ListObject); + let block = + grow_list_items_block_gc(list.bytes_items.block, capacity, list.bytes_items.len()); + let _ = crate::gc_roots::pin_root(block as PyObjectRef); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + let old = list.bytes_items.block; + list.bytes_items.block = crate::gc_roots::shadow_stack_get(block_slot) as *mut ItemsBlock; + dealloc_list_items_block(old); + crate::gc_roots::shadow_stack_get(obj_slot) + } + /// Publish an already-built `BytesArray` as this list's `bytes_items`, /// under [`W_ListObject::bytes_grow`]'s barrier discipline. /// @@ -370,6 +537,82 @@ impl W_ListObject { crate::gc_roots::shadow_stack_get(obj_slot) } + /// AsciiListStrategy counterpart of [`W_ListObject::bytes_grow`]. + unsafe fn ascii_grow(obj: PyObjectRef, min_cap: usize) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let extra = if min_cap < 9 { 3 } else { 6 }; + let target_cap = min_cap.saturating_add(extra).saturating_add(min_cap >> 3); + list_write_barrier(obj); + let new_block_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &*(obj as *const W_ListObject); + let new_block = + grow_list_items_block_gc(list.ascii_items.block, target_cap, list.ascii_items.len()); + let _ = crate::gc_roots::pin_root(new_block as PyObjectRef); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + let old_block = list.ascii_items.block; + list.ascii_items.block = + crate::gc_roots::shadow_stack_get(new_block_slot) as *mut ItemsBlock; + dealloc_list_items_block(old_block); + crate::gc_roots::shadow_stack_get(obj_slot) + } + + /// RPython `_ll_list_resize_hint_really` for AsciiListStrategy. + unsafe fn ascii_resize_capacity(obj: PyObjectRef, capacity: usize) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let list = &*(obj as *const W_ListObject); + assert!(capacity >= list.ascii_items.len()); + if capacity == list.ascii_items.heap_capacity() { + return obj; + } + if capacity == 0 { + let list = &mut *(obj as *mut W_ListObject); + let old = list.ascii_items.block; + list.ascii_items.block = std::ptr::null_mut(); + list.ascii_items.set_len(0); + dealloc_list_items_block(old); + return crate::gc_roots::shadow_stack_get(obj_slot); + } + list_write_barrier(obj); + let block_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &*(obj as *const W_ListObject); + let block = + grow_list_items_block_gc(list.ascii_items.block, capacity, list.ascii_items.len()); + let _ = crate::gc_roots::pin_root(block as PyObjectRef); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + let old = list.ascii_items.block; + list.ascii_items.block = crate::gc_roots::shadow_stack_get(block_slot) as *mut ItemsBlock; + dealloc_list_items_block(old); + crate::gc_roots::shadow_stack_get(obj_slot) + } + + /// Publish an already-built AsciiListStrategy backing array with the + /// owner barrier directly before its field store. + unsafe fn install_ascii_items(obj: PyObjectRef, fresh: UnicodeArray) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let block_slot = fresh.pin_block(); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + let mut fresh = fresh; + fresh.reload_block(block_slot); + list.ascii_items.install(fresh); + crate::gc_roots::shadow_stack_get(obj_slot) + } + /// Upstream list.append equivalent for the object strategy. /// (listobject.py `AbstractUnwrappedStrategy.append` for the /// Object case: no unwrap, just append.) @@ -661,6 +904,19 @@ pub unsafe fn w_list_grow_bytes_block(obj: PyObjectRef, value: PyObjectRef) -> P crate::gc_roots::shadow_stack_get(save + 1) } +/// [`w_list_grow_items_block`] for AsciiListStrategy's erased UTF-8 storage. +#[majit_macros::dont_look_inside] +pub unsafe fn w_list_grow_ascii_block(obj: PyObjectRef, value: PyObjectRef) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let save = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(obj); + let _ = crate::gc_roots::pin_root(value); + let obj = crate::gc_roots::shadow_stack_get(save); + let list = &*(obj as *const W_ListObject); + W_ListObject::ascii_grow(obj, list.ascii_items.len() + 1); + crate::gc_roots::shadow_stack_get(save + 1) +} + /// listobject.py is_plain_int1(w_obj) /// /// Accepts exact W_IntObject (not bool, not int subclass) or W_LongObject @@ -888,7 +1144,7 @@ fn boxed_from_floats(values: &[f64]) -> Vec { } #[inline] -fn is_bytes_strategy_item(item: PyObjectRef) -> bool { +pub fn is_bytes_strategy_item(item: PyObjectRef) -> bool { unsafe { is_exact_type(item, &BYTES_TYPE) } } @@ -896,6 +1152,15 @@ fn all_bytes(items: &[PyObjectRef]) -> bool { items.iter().all(|&item| is_bytes_strategy_item(item)) } +#[inline] +pub fn is_ascii_strategy_item(item: PyObjectRef) -> bool { + unsafe { is_exact_type(item, &STR_TYPE) && w_str_is_ascii(item) } +} + +fn all_ascii(items: &[PyObjectRef]) -> bool { + items.iter().all(|&item| is_ascii_strategy_item(item)) +} + /// Box each erased `rpython str` of the list pinned at `obj_slot`. /// /// Unlike the int/float pair this cannot walk a slice taken once: every @@ -921,6 +1186,24 @@ unsafe fn boxed_from_bytes(obj_slot: usize) -> Vec { .collect() } +/// Box each erased UTF-8 `rpython str` of the Ascii strategy, re-reading the +/// array through the rooted list after every allocating wrap. +unsafe fn boxed_from_ascii(obj_slot: usize) -> Vec { + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + let ascii_items = |slot: usize| -> &UnicodeArray { + &(*(crate::gc_roots::shadow_stack_get(slot) as *const W_ListObject)).ascii_items + }; + let len = ascii_items(obj_slot).len(); + for i in 0..len { + let value = ascii_items(obj_slot).as_slice()[i]; + let _ = crate::gc_roots::pin_root(w_str_from_storage(value as *mut _)); + } + (0..len) + .map(|i| crate::gc_roots::shadow_stack_get(root_base + i)) + .collect() +} + /// Cold list strategy dehomogenization: a typed int/float list gained a /// non-numeric element, so its unboxed backing storage is bulk re-boxed into /// an Object-strategy items block one time. @@ -946,14 +1229,25 @@ pub unsafe fn switch_to_object_strategy(list: &mut W_ListObject) -> PyObjectRef let obj = crate::gc_roots::shadow_stack_get(obj_slot); let list = &mut *(obj as *mut W_ListObject); let seed: Vec = match list.strategy { + ListStrategy::SimpleRange | ListStrategy::Range => { + let values = range_list_values(list); + boxed_from_ints(&values) + } ListStrategy::Integer => boxed_from_ints(list.int_items.as_slice()), ListStrategy::IntOrFloat => boxed_from_int_or_float(list.int_items.as_slice()), ListStrategy::Float => boxed_from_floats(list.float_items.as_slice()), ListStrategy::Bytes => boxed_from_bytes(obj_slot), - ListStrategy::Object | ListStrategy::Empty => Vec::new(), + ListStrategy::Ascii => boxed_from_ascii(obj_slot), + ListStrategy::Object | ListStrategy::Empty | ListStrategy::Size => Vec::new(), }; let obj = crate::gc_roots::shadow_stack_get(obj_slot); let list = &mut *(obj as *mut W_ListObject); + if matches!( + list.strategy, + ListStrategy::Size | ListStrategy::SimpleRange | ListStrategy::Range + ) { + list.items = std::ptr::null_mut(); + } list.set_object_items_from_vec(seed); let obj = crate::gc_roots::shadow_stack_get(obj_slot); let list = &mut *(obj as *mut W_ListObject); @@ -982,9 +1276,83 @@ pub unsafe fn switch_to_object_strategy(list: &mut W_ListObject) -> PyObjectRef let obj = crate::gc_roots::shadow_stack_get(obj_slot); let list = &mut *(obj as *mut W_ListObject); list.bytes_items.install(BytesArray::empty()); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + list.ascii_items.install(UnicodeArray::empty()); crate::gc_roots::shadow_stack_get(obj_slot) } +/// `BaseRangeListStrategy.switch_to_integer_strategy`: materialise the +/// arithmetic progression into IntegerListStrategy exactly once before an +/// operation that destroys the range representation. +#[majit_macros::dont_look_inside] +unsafe fn switch_range_to_integer_strategy(list: &mut W_ListObject) -> PyObjectRef { + debug_assert!(matches!( + list.strategy, + ListStrategy::SimpleRange | ListStrategy::Range + )); + let _roots = crate::gc_roots::push_roots(); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(list as *mut W_ListObject as PyObjectRef); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let values = range_list_values(&*(obj as *const W_ListObject)); + let fresh = IntArray::from_vec(values); + let fresh_slot = fresh.pin_block(); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + list.int_items.install(fresh); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + list.int_items.reload_block(fresh_slot); + list.items = std::ptr::null_mut(); + list.strategy = ListStrategy::Integer; + obj +} + +/// Public `BaseRangeListStrategy.switch_to_integer_strategy` dispatch used by +/// interpreter-level operations whose generic body otherwise only reads the +/// list (slice, empty extend, and in-place repeat). Returns the possibly moved +/// list header after materialisation. +#[majit_macros::dont_look_inside] +pub unsafe fn w_list_materialize_range(obj: PyObjectRef) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let _list_guard = w_list_lock(obj); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &mut *(obj as *mut W_ListObject); + if matches!( + list.strategy, + ListStrategy::SimpleRange | ListStrategy::Range + ) { + switch_range_to_integer_strategy(list) + } else { + obj + } +} + +/// Replace a range strategy's immutable erased tuple. Sharing means the old +/// tuple is never mutated; a pop publishes a fresh tuple on this list only. +unsafe fn install_range_state( + obj: PyObjectRef, + strategy: ListStrategy, + values: &[i64], +) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(obj); + let state_slot = crate::gc_roots::shadow_stack_len(); + let state = alloc_range_state(values); + let _ = crate::gc_roots::pin_root(state as PyObjectRef); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + let list = &mut *(obj as *mut W_ListObject); + list.strategy = strategy; + list.items = crate::gc_roots::shadow_stack_get(state_slot) as *mut ItemsBlock; + obj +} + /// listobject.py EmptyListStrategy.switch_to_correct_strategy. /// /// First append on an empty list picks the typed strategy that matches @@ -998,8 +1366,17 @@ unsafe fn switch_to_correct_strategy(list: &mut W_ListObject, w_item: PyObjectRe let obj = crate::gc_roots::shadow_stack_get(root_base); let w_item = crate::gc_roots::shadow_stack_get(root_base + 1); let list = &mut *(obj as *mut W_ListObject); + // SizeListStrategy.get_sizehint; EmptyListStrategy inherits the zero + // default. The strategy object is replaced below, so consume the hint. + let sizehint = if list.strategy == ListStrategy::Size { + usize::try_from(sizehint_state_value(list.items)).unwrap_or(0) + } else { + 0 + }; + list.set_length_relaxed(0); + list.items = std::ptr::null_mut(); if is_plain_int1(w_item) { - let fresh = IntArray::from_vec(Vec::new()); + let fresh = IntArray::with_capacity(sizehint); let obj = crate::gc_roots::shadow_stack_get(root_base); let list = &mut *(obj as *mut W_ListObject); list.int_items.install(fresh); @@ -1007,7 +1384,7 @@ unsafe fn switch_to_correct_strategy(list: &mut W_ListObject, w_item: PyObjectRe let list = &mut *(obj as *mut W_ListObject); list.strategy = ListStrategy::Integer; } else if is_float_strategy_item(w_item) { - let fresh = FloatArray::from_vec(Vec::new()); + let fresh = FloatArray::with_capacity(sizehint); let obj = crate::gc_roots::shadow_stack_get(root_base); let list = &mut *(obj as *mut W_ListObject); list.float_items.install(fresh); @@ -1018,18 +1395,26 @@ unsafe fn switch_to_correct_strategy(list: &mut W_ListObject, w_item: PyObjectRe // The immediately following append grows this null/zero rlist form // before storing. Avoiding a bulk Vec conversion here keeps the // generated append graph on PyPy's look-inside path. - let fresh = BytesArray::empty(); + let fresh = BytesArray::with_capacity(sizehint); let obj = crate::gc_roots::shadow_stack_get(root_base); - let list = &mut *(obj as *mut W_ListObject); - list.bytes_items.install(fresh); + let _ = W_ListObject::install_bytes_items(obj, fresh); let obj = crate::gc_roots::shadow_stack_get(root_base); let list = &mut *(obj as *mut W_ListObject); list.strategy = ListStrategy::Bytes; + } else if is_ascii_strategy_item(w_item) { + let fresh = UnicodeArray::with_capacity(sizehint); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let _ = W_ListObject::install_ascii_items(obj, fresh); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &mut *(obj as *mut W_ListObject); + list.strategy = ListStrategy::Ascii; } else { - list.set_object_items_from_vec(Vec::new()); let obj = crate::gc_roots::shadow_stack_get(root_base); let list = &mut *(obj as *mut W_ListObject); + list.set_length_relaxed(0); + list.items = std::ptr::null_mut(); list.strategy = ListStrategy::Object; + let _ = W_ListObject::object_resize_capacity(obj, sizehint); } crate::gc_roots::shadow_stack_get(root_base) } @@ -1049,6 +1434,8 @@ pub fn list_strategy_for(items: &[PyObjectRef]) -> ListStrategy { ListStrategy::IntOrFloat } else if all_bytes(items) { ListStrategy::Bytes + } else if all_ascii(items) { + ListStrategy::Ascii } else { ListStrategy::Object } @@ -1186,6 +1573,137 @@ pub fn w_list_new_empty() -> PyObjectRef { w_list_new_object(Vec::new()) } +/// `rpython.rlib.objectmodel.newlist_hint` for interpreter-level temporary +/// lists of wrapped objects. +/// +/// This is deliberately an Object-strategy list, not a Size-strategy list: +/// `BaseObjSpace._unpackiterable_unknown_length` builds an RPython +/// `list[W_Root]`, whereas [`w_list_new_with_sizehint`] implements PyPy's +/// separate `space.newlist_hint` / `SizeListStrategy` object-space API. An +/// unrepresentable allocation mirrors the upstream `except MemoryError: +/// items = []` fallback by returning an exact zero-capacity temporary. +#[majit_macros::dont_look_inside] +pub fn w_list_new_object_with_sizehint(sizehint: i64) -> PyObjectRef { + let obj = w_list_new_object(Vec::new()); + let capacity = usize::try_from(sizehint) + .ok() + .filter(|&capacity| capacity <= (isize::MAX as usize) / std::mem::size_of::()) + .unwrap_or(0); + unsafe { W_ListObject::object_resize_capacity(obj, capacity) } +} + +/// listobject.py `make_empty_list_with_size` / `SizeListStrategy`. +/// The hint belongs to the strategy while the list has no backing storage; +/// the first append consumes it when selecting the concrete strategy. +#[majit_macros::dont_look_inside] +pub fn w_list_new_with_sizehint(sizehint: i64) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let state_slot = crate::gc_roots::shadow_stack_len(); + let state = unsafe { alloc_sizehint_state(sizehint) }; + let _ = crate::gc_roots::pin_root(state as PyObjectRef); + let obj = w_list_new_with_strategy(Vec::new(), ListStrategy::Size); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(obj); + unsafe { + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + (*(obj as *mut W_ListObject)).items = + crate::gc_roots::shadow_stack_get(state_slot) as *mut ItemsBlock; + obj + } +} + +/// `listobject.py make_range_list`: build the erased immutable storage used by +/// `SimpleRangeListStrategy` or `RangeListStrategy` without materialising its +/// integer elements. +#[majit_macros::dont_look_inside] +pub fn w_list_new_range(start: i64, step: i64, length: i64) -> PyObjectRef { + if length <= 0 { + return w_list_new(Vec::new()); + } + let (strategy, values): (ListStrategy, &[i64]) = if start == 0 && step == 1 { + (ListStrategy::SimpleRange, std::slice::from_ref(&length)) + } else { + (ListStrategy::Range, &[start, step, length]) + }; + let _roots = crate::gc_roots::push_roots(); + let state_slot = crate::gc_roots::shadow_stack_len(); + let state = unsafe { alloc_range_state(values) }; + let _ = crate::gc_roots::pin_root(state as PyObjectRef); + let obj = w_list_new_with_strategy(Vec::new(), strategy); + let obj_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(obj); + unsafe { + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(obj_slot); + (*(obj as *mut W_ListObject)).items = + crate::gc_roots::shadow_stack_get(state_slot) as *mut ItemsBlock; + obj + } +} + +/// Strategy clone for storage PyPy shares by identity: Size retains the same +/// mutable strategy instance, while both range strategies retain their +/// immutable erased tuple. The check and clone are one list-locked operation +/// so a free-threaded mutation cannot switch the strategy between them. +#[majit_macros::dont_look_inside] +pub unsafe fn w_list_clone_if_shared_strategy(obj: PyObjectRef) -> Option { + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let _list_guard = w_list_lock(obj); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &*(obj as *const W_ListObject); + if !matches!( + list.strategy, + ListStrategy::Size | ListStrategy::SimpleRange | ListStrategy::Range + ) { + return None; + } + let strategy = list.strategy; + let state_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(list.items as PyObjectRef); + let clone = w_list_new_with_strategy(Vec::new(), strategy); + let clone_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(clone); + let clone = crate::gc_roots::shadow_stack_get(clone_slot); + list_write_barrier(clone); + let clone = crate::gc_roots::shadow_stack_get(clone_slot); + (*(clone as *mut W_ListObject)).items = + crate::gc_roots::shadow_stack_get(state_slot) as *mut ItemsBlock; + Some(clone) +} + +/// `SizeListStrategy` is the sole shared-storage strategy whose `mul` is a +/// no-op: it represents an empty list with a future allocation hint. Range +/// strategies instead inherit `ListStrategy.mul`, whose cloned receiver is +/// immediately materialised by `BaseRangeListStrategy.inplace_mul`. +#[majit_macros::dont_look_inside] +pub unsafe fn w_list_clone_if_size(obj: PyObjectRef) -> Option { + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let _list_guard = w_list_lock(obj); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &*(obj as *const W_ListObject); + if list.strategy != ListStrategy::Size { + return None; + } + let state_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(list.items as PyObjectRef); + let clone = w_list_new_with_strategy(Vec::new(), ListStrategy::Size); + let clone_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(clone); + let clone = crate::gc_roots::shadow_stack_get(clone_slot); + list_write_barrier(clone); + let clone = crate::gc_roots::shadow_stack_get(clone_slot); + (*(clone as *mut W_ListObject)).items = + crate::gc_roots::shadow_stack_get(state_slot) as *mut ItemsBlock; + Some(clone) +} + /// Build the backing storage a `strategy`-strategy list holding `items` needs, /// without installing it anywhere: the typed blocks (empty unless the matching /// strategy) first, then the Object-strategy items block. @@ -1231,6 +1749,17 @@ unsafe fn build_list_storage(items: &[PyObjectRef], strategy: ListStrategy) -> L BytesArray::empty() }; let bytes_block_root = bytes_items.pin_block(); + let ascii_items = if let ListStrategy::Ascii = strategy { + UnicodeArray::from_vec( + items + .iter() + .map(|&item| w_str_storage(item) as *const _) + .collect(), + ) + } else { + UnicodeArray::empty() + }; + let ascii_block_root = ascii_items.pin_block(); let (length, block) = if let ListStrategy::Object = strategy { (items.len(), alloc_list_items_block_gc(items)) } else { @@ -1242,9 +1771,11 @@ unsafe fn build_list_storage(items: &[PyObjectRef], strategy: ListStrategy) -> L int_items, float_items, bytes_items, + ascii_items, int_block_root, float_block_root, bytes_block_root, + ascii_block_root, } } @@ -1256,9 +1787,11 @@ struct ListStorage { int_items: IntArray, float_items: FloatArray, bytes_items: BytesArray, + ascii_items: UnicodeArray, int_block_root: usize, float_block_root: usize, bytes_block_root: usize, + ascii_block_root: usize, } impl ListStorage { @@ -1269,6 +1802,7 @@ impl ListStorage { self.int_items.reload_block(self.int_block_root); self.float_items.reload_block(self.float_block_root); self.bytes_items.reload_block(self.bytes_block_root); + self.ascii_items.reload_block(self.ascii_block_root); } } @@ -1286,11 +1820,15 @@ pub fn w_list_new_with_strategy(items: Vec, strategy: ListStrategy) // exists to express and only rejects the unboxing that has no payload. debug_assert!( match strategy { - ListStrategy::Empty => items.is_empty(), + ListStrategy::Empty + | ListStrategy::Size + | ListStrategy::SimpleRange + | ListStrategy::Range => items.is_empty(), ListStrategy::Integer => all_ints(&items), ListStrategy::Float => all_floats(&items), ListStrategy::IntOrFloat => all_int_or_float(&items), ListStrategy::Bytes => all_bytes(&items), + ListStrategy::Ascii => all_ascii(&items), ListStrategy::Object => true, }, "list items do not support the requested storage strategy", @@ -1347,7 +1885,11 @@ pub fn w_list_new_with_strategy(items: Vec, strategy: ListStrategy) ListStrategy::Integer | ListStrategy::IntOrFloat => storage.int_items.block as *mut u8, ListStrategy::Float => storage.float_items.block as *mut u8, ListStrategy::Bytes => storage.bytes_items.block as *mut u8, - ListStrategy::Empty => std::ptr::null_mut(), + ListStrategy::Ascii => storage.ascii_items.block as *mut u8, + ListStrategy::Empty + | ListStrategy::Size + | ListStrategy::SimpleRange + | ListStrategy::Range => std::ptr::null_mut(), }; let mut needs_write_barrier = true; let raw = unsafe { @@ -1371,6 +1913,7 @@ pub fn w_list_new_with_strategy(items: Vec, strategy: ListStrategy) int_items, float_items, bytes_items, + ascii_items, .. } = storage; // Re-read the (possibly relocated) nursery items block before either the @@ -1388,6 +1931,7 @@ pub fn w_list_new_with_strategy(items: Vec, strategy: ListStrategy) int_items, float_items, bytes_items, + ascii_items, w_slots: PY_NULL, }); return Box::into_raw(boxed) as PyObjectRef; @@ -1404,6 +1948,7 @@ pub fn w_list_new_with_strategy(items: Vec, strategy: ListStrategy) int_items, float_items, bytes_items, + ascii_items, w_slots: PY_NULL, }, ); @@ -1412,7 +1957,11 @@ pub fn w_list_new_with_strategy(items: Vec, strategy: ListStrategy) // spill to old-gen (for example around pinned nursery gaps); only that // placement needs remembering for its young Object-strategy items edge. // Integer/Float blocks are old-gen leaf arrays and need no barrier. - if matches!(strategy, ListStrategy::Object | ListStrategy::Bytes) && needs_write_barrier { + if matches!( + strategy, + ListStrategy::Object | ListStrategy::Bytes | ListStrategy::Ascii + ) && needs_write_barrier + { list_write_barrier_impl(raw as PyObjectRef, true); } raw as PyObjectRef @@ -1621,7 +2170,15 @@ pub unsafe fn w_list_getitem(obj: PyObjectRef, index: i64) -> Option None, + ListStrategy::Empty | ListStrategy::Size => None, + ListStrategy::SimpleRange | ListStrategy::Range => { + let len = range_list_length(list) as i64; + let idx = if index < 0 { index + len } else { index }; + if idx < 0 || idx >= len { + return None; + } + Some(w_int_new(range_list_item_unchecked(list, idx as usize))) + } ListStrategy::Object => { let items = list.object_items_slice(); let len = items.len() as i64; @@ -1670,6 +2227,14 @@ pub unsafe fn w_list_getitem(obj: PyObjectRef, index: i64) -> Option { + let len = list.ascii_items.len() as i64; + let idx = if index < 0 { index + len } else { index }; + if idx < 0 || idx >= len { + return None; + } + Some(w_str_from_storage(list.ascii_items[idx as usize] as *mut _)) + } } } @@ -1691,7 +2256,11 @@ pub unsafe fn w_list_setitem(obj: PyObjectRef, index: i64, value: PyObjectRef) - let list = &mut *(obj as *mut W_ListObject); match list.strategy { // listobject.py EmptyListStrategy.setitem raises IndexError. - ListStrategy::Empty => false, + ListStrategy::Empty | ListStrategy::Size => false, + ListStrategy::SimpleRange | ListStrategy::Range => { + let obj = switch_range_to_integer_strategy(list); + w_list_setitem(obj, index, crate::gc_roots::shadow_stack_get(root_base + 1)) + } ListStrategy::Object => { let len = list.length_relaxed() as i64; let idx = if index < 0 { index + len } else { index }; @@ -1793,6 +2362,25 @@ pub unsafe fn w_list_setitem(obj: PyObjectRef, index: i64, value: PyObjectRef) - ) } } + ListStrategy::Ascii => { + let len = list.ascii_items.len() as i64; + let idx = if index < 0 { index + len } else { index }; + if idx < 0 || idx >= len { + return false; + } + if is_ascii_strategy_item(value) { + list.ascii_items + .set(idx as usize, w_str_storage(value) as *const _); + true + } else { + switch_to_object_strategy(list); + w_list_setitem( + crate::gc_roots::shadow_stack_get(root_base), + index, + crate::gc_roots::shadow_stack_get(root_base + 1), + ) + } + } } } @@ -1876,11 +2464,20 @@ pub unsafe fn w_list_append_inner(obj: PyObjectRef, value: PyObjectRef) { match list.strategy { // listobject.py EmptyListStrategy.append: pick the matching // typed strategy first, then fall through to its append. - ListStrategy::Empty => { + ListStrategy::Empty | ListStrategy::Size => { let obj = switch_to_correct_strategy(list, value); let value = current_gc_ref(value); w_list_append_inner(obj, value); } + ListStrategy::SimpleRange | ListStrategy::Range => { + let obj = if is_plain_int1(value) { + switch_range_to_integer_strategy(list) + } else { + switch_to_object_strategy(list) + }; + let value = current_gc_ref(value); + w_list_append_inner(obj, value); + } // AbstractUnwrappedStrategy.append (listobject.py): // if self.is_correct_type(w_item): l.append(self.unwrap(w_item)); return // self.switch_to_next_strategy(w_list, w_item); w_list.append(w_item) @@ -1997,6 +2594,26 @@ pub unsafe fn w_list_append_inner(obj: PyObjectRef, value: PyObjectRef) { list.object_push(value); } } + ListStrategy::Ascii => { + if is_ascii_strategy_item(value) { + let value = prepare_list_ref_store(obj, value); + let obj = current_gc_ref(obj); + let list = &*(obj as *const W_ListObject); + let value = if list.ascii_items.spare_capacity() == 0 { + w_list_grow_ascii_block(obj, value) + } else { + value + }; + let obj = current_gc_ref(obj); + let list = &mut *(obj as *mut W_ListObject); + list.ascii_items.push(w_str_storage(value) as *const _); + } else { + let obj = switch_to_object_strategy(list); + let value = current_gc_ref(value); + let list = &mut *(obj as *mut W_ListObject); + list.object_push(value); + } + } } } @@ -2103,12 +2720,14 @@ pub unsafe fn w_list_len(obj: PyObjectRef) -> usize { let list = &*(obj as *const W_ListObject); match list.strategy { // listobject.py EmptyListStrategy.length returns 0. - ListStrategy::Empty => 0, + ListStrategy::Empty | ListStrategy::Size => 0, + ListStrategy::SimpleRange | ListStrategy::Range => range_list_length(list), ListStrategy::Object => list.length_relaxed(), ListStrategy::Integer => ll_list_int_length(list), ListStrategy::IntOrFloat => list.int_items.len(), ListStrategy::Float => list.float_items.len(), ListStrategy::Bytes => list.bytes_items.len(), + ListStrategy::Ascii => list.ascii_items.len(), } } @@ -2125,6 +2744,159 @@ pub unsafe fn w_list_allocated(obj: PyObjectRef) -> isize { (*(obj as *const W_ListObject)).allocated } +/// listobject.py `ListStrategy.physical_size` and the strategy-specific +/// overrides used by `__pypy__.list_get_physical_size`. +pub unsafe fn w_list_physical_size(obj: PyObjectRef) -> Option { + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let _list_guard = w_list_lock(obj); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &*(obj as *const W_ListObject); + match list.strategy { + ListStrategy::Empty | ListStrategy::Size => Some(0), + ListStrategy::Object => Some(list.object_items_capacity()), + ListStrategy::Integer | ListStrategy::IntOrFloat => Some(list.int_items.heap_capacity()), + ListStrategy::Float => Some(list.float_items.heap_capacity()), + ListStrategy::Bytes => Some(list.bytes_items.heap_capacity()), + ListStrategy::Ascii => Some(list.ascii_items.heap_capacity()), + // BaseRangeListStrategy inherits ListStrategy.physical_size, whose + // diagnostic contract is to raise rather than invent an allocation. + ListStrategy::SimpleRange | ListStrategy::Range => None, + } +} + +/// `W_ListObject._resize_hint` → strategy `_resize_hint`. +/// +/// Returns false only when the RPython over-allocation arithmetic cannot be +/// represented. A shrink hint is clamped to the live length: translated PyPy +/// permits a lying private hint to truncate the backing below live elements, +/// but Rust slices require the same `length <= capacity` invariant that the +/// real caller is documented to uphold. +pub unsafe fn w_list_resize_hint(obj: PyObjectRef, newsize: i64) -> bool { + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let _list_guard = w_list_lock(obj); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &mut *(obj as *mut W_ListObject); + match list.strategy { + ListStrategy::Empty => { + if newsize != 0 { + let Ok(newsize) = isize::try_from(newsize) else { + return false; + }; + let state_slot = crate::gc_roots::shadow_stack_len(); + let state = alloc_sizehint_state(newsize as i64); + let _ = crate::gc_roots::pin_root(state as PyObjectRef); + let obj = crate::gc_roots::shadow_stack_get(root_base); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &mut *(obj as *mut W_ListObject); + list.strategy = ListStrategy::Size; + list.items = crate::gc_roots::shadow_stack_get(state_slot) as *mut ItemsBlock; + } + return true; + } + ListStrategy::Size => { + let Ok(newsize) = isize::try_from(newsize) else { + return false; + }; + set_sizehint_state_value(list.items, newsize as i64); + return true; + } + // BaseRangeListStrategy._resize_hint: supported as a deliberate no-op. + ListStrategy::SimpleRange | ListStrategy::Range => return true, + _ => {} + } + + let newsize = usize::try_from(newsize).unwrap_or(0); + + let current = match list.strategy { + ListStrategy::Object => list.object_items_capacity(), + ListStrategy::Integer | ListStrategy::IntOrFloat => list.int_items.heap_capacity(), + ListStrategy::Float => list.float_items.heap_capacity(), + ListStrategy::Bytes => list.bytes_items.heap_capacity(), + ListStrategy::Ascii => list.ascii_items.heap_capacity(), + ListStrategy::Empty + | ListStrategy::Size + | ListStrategy::SimpleRange + | ListStrategy::Range => unreachable!(), + }; + let requested = newsize.max(list.live_len()); + let target = if requested > current { + let extra = if requested < 9 { 3 } else { 6 }; + let Some(target) = requested + .checked_add(extra) + .and_then(|n| n.checked_add(requested >> 3)) + else { + return false; + }; + target + } else if requested < (current >> 1).saturating_sub(5) { + requested + } else { + return true; + }; + + match list.strategy { + ListStrategy::Object => { + let _ = W_ListObject::object_resize_capacity(obj, target); + } + ListStrategy::Integer | ListStrategy::IntOrFloat => { + let old = list.int_items.block; + let len = list.int_items.len(); + if target == 0 { + crate::object_array::dealloc_typed_items_block(old); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &mut *(obj as *mut W_ListObject); + list.int_items.block = std::ptr::null_mut(); + list.int_items.set_len(0); + } else { + let fresh = crate::object_array::grow_typed_items_block( + old, + target, + len, + crate::object_array::gc_int_array_gc_type_id(), + ); + let obj = crate::gc_roots::shadow_stack_get(root_base); + (*(obj as *mut W_ListObject)).int_items.block = fresh; + } + } + ListStrategy::Float => { + let old = list.float_items.block; + let len = list.float_items.len(); + if target == 0 { + crate::object_array::dealloc_typed_items_block(old); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &mut *(obj as *mut W_ListObject); + list.float_items.block = std::ptr::null_mut(); + list.float_items.set_len(0); + } else { + let fresh = crate::object_array::grow_typed_items_block( + old, + target, + len, + crate::object_array::gc_float_array_gc_type_id(), + ); + let obj = crate::gc_roots::shadow_stack_get(root_base); + (*(obj as *mut W_ListObject)).float_items.block = fresh; + } + } + ListStrategy::Bytes => { + let _ = W_ListObject::bytes_resize_capacity(obj, target); + } + ListStrategy::Ascii => { + let _ = W_ListObject::ascii_resize_capacity(obj, target); + } + ListStrategy::Empty + | ListStrategy::Size + | ListStrategy::SimpleRange + | ListStrategy::Range => unreachable!(), + } + true +} + /// Reserve CPython's logical slots before `list.extend` consumes its source. /// # Safety /// The caller must uphold every validity, runtime-type, aliasing, and lifetime @@ -2214,12 +2986,14 @@ pub unsafe fn w_list_can_append_without_realloc(obj: PyObjectRef) -> bool { let list = &*(obj as *const W_ListObject); match list.strategy { // EmptyListStrategy holds no array yet — first append always reallocates. - ListStrategy::Empty => false, + ListStrategy::Empty | ListStrategy::Size => false, + ListStrategy::SimpleRange | ListStrategy::Range => false, ListStrategy::Object => list.object_spare_capacity() > 0, ListStrategy::Integer => list.int_items.spare_capacity() > 0, ListStrategy::IntOrFloat => list.int_items.spare_capacity() > 0, ListStrategy::Float => list.float_items.spare_capacity() > 0, ListStrategy::Bytes => list.bytes_items.spare_capacity() > 0, + ListStrategy::Ascii => list.ascii_items.spare_capacity() > 0, } } @@ -2231,7 +3005,8 @@ pub unsafe fn w_list_is_inline_storage(obj: PyObjectRef) -> bool { let list = &*(obj as *const W_ListObject); match list.strategy { // EmptyListStrategy.lstorage = self.erase(None) — no backing array. - ListStrategy::Empty => false, + ListStrategy::Empty | ListStrategy::Size => false, + ListStrategy::SimpleRange | ListStrategy::Range => false, // Object strategy stores items in a GC-shaped `ItemsBlock`, never // an inline allocation — upstream rlist.py doesn't have an // "inline" bit either. @@ -2240,6 +3015,7 @@ pub unsafe fn w_list_is_inline_storage(obj: PyObjectRef) -> bool { ListStrategy::IntOrFloat => list.int_items.is_inline(), ListStrategy::Float => list.float_items.is_inline(), ListStrategy::Bytes => list.bytes_items.is_inline(), + ListStrategy::Ascii => list.ascii_items.is_inline(), } } @@ -2353,7 +3129,11 @@ pub unsafe fn w_list_object_items_ptr_len(obj: PyObjectRef) -> Option<(*const Py unsafe fn temporarily_as_objects(list: &W_ListObject) -> Vec { match list.strategy { // listobject.py EmptyListStrategy.getitems returns []. - ListStrategy::Empty => Vec::new(), + ListStrategy::Empty | ListStrategy::Size => Vec::new(), + ListStrategy::SimpleRange | ListStrategy::Range => { + let values = range_list_values(list); + boxed_from_ints(&values) + } ListStrategy::Object => list.object_to_vec(), ListStrategy::Integer => { let items = list.int_items.as_slice(); @@ -2388,6 +3168,14 @@ unsafe fn temporarily_as_objects(list: &W_ListObject) -> Vec { let obj_slot = crate::gc_roots::shadow_stack_len() - 1; boxed_from_bytes(obj_slot) } + ListStrategy::Ascii => { + let _roots = crate::gc_roots::push_roots(); + let _ = crate::gc_roots::pin_root( + (list as *const W_ListObject as *mut W_ListObject) as PyObjectRef, + ); + let obj_slot = crate::gc_roots::shadow_stack_len() - 1; + boxed_from_ascii(obj_slot) + } } } @@ -2418,7 +3206,7 @@ pub unsafe fn w_list_insert(obj: PyObjectRef, index: i64, value: PyObjectRef) { // EmptyListStrategy doesn't override insert, so it falls through // ListStrategy.insert (listobject.py) → switches to typed strategy // via append. Mirror by switching first then re-dispatching. - ListStrategy::Empty => { + ListStrategy::Empty | ListStrategy::Size => { switch_to_correct_strategy(list, value); w_list_insert( crate::gc_roots::shadow_stack_get(root_base), @@ -2426,6 +3214,10 @@ pub unsafe fn w_list_insert(obj: PyObjectRef, index: i64, value: PyObjectRef) { crate::gc_roots::shadow_stack_get(root_base + 1), ); } + ListStrategy::SimpleRange | ListStrategy::Range => { + let obj = switch_range_to_integer_strategy(list); + w_list_insert(obj, index, crate::gc_roots::shadow_stack_get(root_base + 1)); + } ListStrategy::Integer => { if is_plain_int1(value) { let idx = normalize_insert_index(index, list.int_items.len()); @@ -2522,6 +3314,31 @@ pub unsafe fn w_list_insert(obj: PyObjectRef, index: i64, value: PyObjectRef) { ); } } + ListStrategy::Ascii => { + if is_ascii_strategy_item(value) { + let idx = normalize_insert_index(index, list.ascii_items.len()); + let value = prepare_list_ref_store(obj, value); + let obj = current_gc_ref(obj); + let list = &*(obj as *const W_ListObject); + let value = if list.ascii_items.spare_capacity() == 0 { + w_list_grow_ascii_block(obj, value) + } else { + value + }; + let obj = current_gc_ref(obj); + let list = &mut *(obj as *mut W_ListObject); + list.ascii_items + .insert(idx, w_str_storage(value) as *const _); + list.sync_allocated(old_size); + } else { + switch_to_object_strategy(list); + w_list_insert( + crate::gc_roots::shadow_stack_get(root_base), + index, + crate::gc_roots::shadow_stack_get(root_base + 1), + ); + } + } } } @@ -2540,7 +3357,40 @@ pub unsafe fn w_list_pop(obj: PyObjectRef, index: i64) -> Option { let old_size = list.live_len(); let result = match list.strategy { // listobject.py EmptyListStrategy.pop raises IndexError. - ListStrategy::Empty => None, + ListStrategy::Empty | ListStrategy::Size => None, + ListStrategy::SimpleRange => { + // SimpleRangeListStrategy.pop always materialises; only the + // separate pop_end hook preserves the strategy. + let obj = switch_range_to_integer_strategy(list); + return w_list_pop(obj, index); + } + ListStrategy::Range => { + let len = range_list_length(list) as i64; + let idx = if index < 0 { index + len } else { index }; + if idx < 0 || idx >= len { + return None; + } + let (start, step) = range_list_start_step(list); + if idx == 0 { + let result = w_int_new(start); + let result_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(result); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let _ = + install_range_state(obj, ListStrategy::Range, &[start + step, step, len - 1]); + Some(crate::gc_roots::shadow_stack_get(result_slot)) + } else if idx == len - 1 { + let result = w_int_new(start + idx * step); + let result_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(result); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let _ = install_range_state(obj, ListStrategy::Range, &[start, step, len - 1]); + Some(crate::gc_roots::shadow_stack_get(result_slot)) + } else { + let obj = switch_range_to_integer_strategy(list); + return w_list_pop(obj, index); + } + } ListStrategy::Integer => { let len = list.int_items.len() as i64; if len == 0 { @@ -2600,6 +3450,16 @@ pub unsafe fn w_list_pop(obj: PyObjectRef, index: i64) -> Option { } Some(w_bytes_from_block(list.bytes_items.remove(idx as usize))) } + ListStrategy::Ascii => { + let len = list.ascii_items.len() as i64; + let idx = if index < 0 { index + len } else { index }; + if idx < 0 || idx >= len { + return None; + } + Some(w_str_from_storage( + list.ascii_items.remove(idx as usize) as *mut _ + )) + } }; if result.is_some() { list.sync_allocated(old_size); @@ -2631,12 +3491,14 @@ pub unsafe fn w_list_pop_end(obj: PyObjectRef) -> Option { let obj = crate::gc_roots::shadow_stack_get(root_base); let list = &mut *(obj as *mut W_ListObject); let length = match list.strategy { - ListStrategy::Empty => 0, + ListStrategy::Empty | ListStrategy::Size => 0, + ListStrategy::SimpleRange | ListStrategy::Range => range_list_length(list), ListStrategy::Integer => ll_list_int_length(list), ListStrategy::IntOrFloat => list.int_items.len(), ListStrategy::Float => list.float_items.len(), ListStrategy::Object => list.length_relaxed(), ListStrategy::Bytes => list.bytes_items.len(), + ListStrategy::Ascii => list.ascii_items.len(), }; if length == 0 { None @@ -2660,7 +3522,37 @@ pub unsafe fn w_list_pop_end_inner(obj: PyObjectRef) -> PyObjectRef { match list.strategy { // EmptyListStrategy.pop is unreachable after descr_pop's length check // (pypy/objspace/std/listobject.py). - ListStrategy::Empty => PY_NULL, + ListStrategy::Empty | ListStrategy::Size => PY_NULL, + ListStrategy::SimpleRange => { + let length = range_list_length(list); + let result = w_int_new((length - 1) as i64); + let result_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(result); + if length > 1 { + let obj = current_gc_ref(obj); + let _ = install_range_state(obj, ListStrategy::SimpleRange, &[(length - 1) as i64]); + } else { + let obj = current_gc_ref(obj); + let list = &mut *(obj as *mut W_ListObject); + list.items = std::ptr::null_mut(); + list.strategy = ListStrategy::Empty; + } + crate::gc_roots::shadow_stack_get(result_slot) + } + ListStrategy::Range => { + let length = range_list_length(list); + let (start, step) = range_list_start_step(list); + let result = w_int_new(start + ((length - 1) as i64) * step); + let result_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(result); + let obj = current_gc_ref(obj); + let _ = install_range_state( + obj, + ListStrategy::Range, + &[start, step, (length - 1) as i64], + ); + crate::gc_roots::shadow_stack_get(result_slot) + } ListStrategy::Integer => { let length = ll_list_int_length(list); let index = length - 1; @@ -2679,6 +3571,7 @@ pub unsafe fn w_list_pop_end_inner(obj: PyObjectRef) -> PyObjectRef { ListStrategy::Float => w_float_new(list.float_items.pop()), ListStrategy::Object => list.object_pop(), ListStrategy::Bytes => w_bytes_from_block(list.bytes_items.pop()), + ListStrategy::Ascii => w_str_from_storage(list.ascii_items.pop() as *mut _), } } @@ -2700,6 +3593,27 @@ pub unsafe fn w_list_int_items_raw(obj: PyObjectRef) -> Option<(*mut i64, usize) Some((items.as_mut_ptr(), items.len())) } +/// `BaseRangeListStrategy.sort`: an arithmetic progression already ordered in +/// the requested direction keeps its compact storage. The opposite direction +/// first becomes `IntegerListStrategy`, after which the ordinary scalar sorter +/// must run (reported by returning `false`). +pub unsafe fn w_list_sort_range(obj: PyObjectRef, reverse: bool) -> bool { + let list = &mut *(obj as *mut W_ListObject); + if !matches!( + list.strategy, + ListStrategy::SimpleRange | ListStrategy::Range + ) { + return false; + } + let (_, step) = range_list_start_step(list); + if (step > 0 && reverse) || (step < 0 && !reverse) { + let _ = switch_range_to_integer_strategy(list); + false + } else { + true + } +} + /// The Float-strategy counterpart of [`w_list_int_items_raw`] /// (`FloatListStrategy.sort`, listobject.py). /// @@ -2742,6 +3656,31 @@ pub unsafe fn w_list_sort_int_or_float(obj: PyObjectRef, reverse: bool) -> bool true } +/// `BytesListStrategy.sort` / `AsciiListStrategy.sort` (`listobject.py`): +/// order the erased RPython string payloads directly with `StringSort`, then +/// apply the upstream reverse step without allocating Python wrappers. +pub unsafe fn w_list_sort_strings(obj: PyObjectRef, reverse: bool) -> bool { + let list = &mut *(obj as *mut W_ListObject); + match list.strategy { + ListStrategy::Bytes => list.bytes_items.as_mut_slice().sort_by(|a, b| { + crate::bytesobject::bytes_block_chars(*a).cmp(crate::bytesobject::bytes_block_chars(*b)) + }), + ListStrategy::Ascii => list + .ascii_items + .as_mut_slice() + .sort_by(|a, b| (&**a).as_bytes().cmp((&**b).as_bytes())), + _ => return false, + } + if reverse { + match list.strategy { + ListStrategy::Bytes => list.bytes_items.reverse(), + ListStrategy::Ascii => list.ascii_items.reverse(), + _ => unreachable!(), + } + } + true +} + /// Whether the list still holds the EmptyListStrategy. /// /// `descr_sort` (listobject.py) uses this to tell whether the user mucked @@ -2755,6 +3694,25 @@ pub unsafe fn w_list_is_empty_strategy(obj: PyObjectRef) -> bool { (*(obj as *const W_ListObject)).strategy == ListStrategy::Empty } +/// Whether `obj` uses either compact `BaseRangeListStrategy` storage shape. +pub unsafe fn w_list_is_range_strategy(obj: PyObjectRef) -> bool { + matches!( + (*(obj as *const W_ListObject)).strategy, + ListStrategy::SimpleRange | ListStrategy::Range + ) +} + +/// SizeListStrategy.get_sizehint; `None` for every other strategy. +pub unsafe fn w_list_sizehint(obj: PyObjectRef) -> Option { + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + let obj = crate::gc_roots::pin_root(obj); + let _list_guard = w_list_lock(obj); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &*(obj as *const W_ListObject); + (list.strategy == ListStrategy::Size).then(|| sizehint_state_value(list.items)) +} + /// listobject.py `W_ListObject.__init__` applied to an existing list: /// re-pick the strategy for `items` and install fresh storage, dropping /// whatever the list held. This is how `descr_sort` (listobject.py) puts @@ -2800,7 +3758,11 @@ pub unsafe fn w_list_init_items(obj: PyObjectRef, items: Vec) { // `drop_object_items`' `try_gc_owns_object` query is a safepoint and the // fresh blocks have no heap edge until the stores below, so close their pin // bracket only once it is behind them (`IntArray::install`). - list.drop_object_items(); + if list.strategy == ListStrategy::Object { + list.drop_object_items(); + } else { + list.items = std::ptr::null_mut(); + } storage.reload_typed_blocks(); if let Some(s) = block_root { storage.block = crate::gc_roots::shadow_stack_get(s) as *mut ItemsBlock; @@ -2811,10 +3773,14 @@ pub unsafe fn w_list_init_items(obj: PyObjectRef, items: Vec) { list.int_items = storage.int_items; list.float_items = storage.float_items; list.bytes_items = storage.bytes_items; + list.ascii_items = storage.ascii_items; // Object and Bytes storage both publish a freshly allocated GC block from // an existing list header. Integer/Float blocks are old-generation leaf // arrays and need no remembered-set edge. - if matches!(strategy, ListStrategy::Object | ListStrategy::Bytes) { + if matches!( + strategy, + ListStrategy::Object | ListStrategy::Bytes | ListStrategy::Ascii + ) { list_write_barrier(obj); } } @@ -2840,27 +3806,74 @@ pub unsafe fn w_list_clear(obj: PyObjectRef) { if list.live_len() == 0 && list.allocated == -1 { return; } - list.drop_object_items(); + if list.strategy == ListStrategy::Object { + list.drop_object_items(); + } else { + list.items = std::ptr::null_mut(); + list.set_length_relaxed(0); + } // Empty strategy reads neither typed array; the next append reinstalls the // matching one through `switch_to_correct_strategy`. list.int_items.install(IntArray::empty()); list.float_items.install(FloatArray::empty()); list.bytes_items.install(BytesArray::empty()); + list.ascii_items.install(UnicodeArray::empty()); list.strategy = ListStrategy::Empty; + list.set_length_relaxed(0); list.allocated = 0; } /// listobject.py EmptyListStrategy.switch_to_correct_strategy — -/// public entry for the JIT's empty-append promotion. Installs empty typed -/// storage (capacity-1 block, length 0) matching `value`'s type, WITHOUT -/// appending. The caller performs the append afterward (the typed spare- -/// capacity leg). Only valid on an Empty-strategy list. +/// public entry for the JIT's empty-append staging. It selects the strategy and +/// applies the first `_ll_list_resize_ge` growth (0 -> 4) WITHOUT changing the +/// logical length or storing the item. The trace emitter stages the same array; +/// the caller then performs the append through the spare-capacity leg. Only +/// valid on an Empty-strategy list. /// # Safety /// `obj` must point to a valid Empty-strategy `W_ListObject`; `value` live. -pub unsafe fn w_list_switch_to_strategy_for(obj: PyObjectRef, value: PyObjectRef) { +pub unsafe fn w_list_switch_to_strategy_for(obj: PyObjectRef, value: PyObjectRef) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + crate::gc_roots::publish_roots(&[obj, value]); + crate::gc_roots::normalize_roots(root_base, 2); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let value = crate::gc_roots::shadow_stack_get(root_base + 1); let list = &mut *(obj as *mut W_ListObject); debug_assert_eq!(list.strategy, ListStrategy::Empty); - switch_to_correct_strategy(list, value); + let _ = switch_to_correct_strategy(list, value); + // `switch_to_correct_strategy` owns a nested root scope. Reload the outer + // slot before the first resize, then follow rlist's `_ll_list_resize_ge` + // allocation discipline: retain the old block only as an allocation + // input, and reload the possibly moved list before publishing the result. + let obj = crate::gc_roots::shadow_stack_get(root_base); + let list = &mut *(obj as *mut W_ListObject); + match list.strategy { + ListStrategy::Integer => { + let fresh = crate::object_array::grow_typed_items_block( + list.int_items.block, + 4, + 0, + crate::object_array::gc_int_array_gc_type_id(), + ); + let obj = crate::gc_roots::shadow_stack_get(root_base); + (*(obj as *mut W_ListObject)).int_items.block = fresh; + } + ListStrategy::Float => { + let fresh = crate::object_array::grow_typed_items_block( + list.float_items.block, + 4, + 0, + crate::object_array::gc_float_array_gc_type_id(), + ); + let obj = crate::gc_roots::shadow_stack_get(root_base); + (*(obj as *mut W_ListObject)).float_items.block = fresh; + } + ListStrategy::Object => { + let _ = W_ListObject::object_resize_capacity(obj, 4); + } + _ => unreachable!("orthodox append fold admits int, float, or object storage"), + } + crate::gc_roots::shadow_stack_get(root_base) } /// listobject.py IntegerListStrategy.reverse @@ -2873,11 +3886,16 @@ pub unsafe fn w_list_reverse(obj: PyObjectRef) { match list.strategy { // Empty has nothing to reverse — falls through ListStrategy.reverse // (listobject.py defaults) which is a no-op for length 0. - ListStrategy::Empty => {} + ListStrategy::Empty | ListStrategy::Size => {} + ListStrategy::SimpleRange | ListStrategy::Range => { + let obj = switch_range_to_integer_strategy(list); + w_list_reverse(obj); + } ListStrategy::Integer => list.int_items.as_mut_slice().reverse(), ListStrategy::IntOrFloat => list.int_items.as_mut_slice().reverse(), ListStrategy::Float => list.float_items.as_mut_slice().reverse(), ListStrategy::Bytes => list.bytes_items.reverse(), + ListStrategy::Ascii => list.ascii_items.reverse(), ListStrategy::Object => list.object_reverse(), } } @@ -2893,7 +3911,12 @@ pub unsafe fn w_list_delslice(obj: PyObjectRef, start: usize, end: usize) { let mut changed = false; match list.strategy { // listobject.py EmptyListStrategy.deleteslice is a no-op (pass). - ListStrategy::Empty => {} + ListStrategy::Empty | ListStrategy::Size => {} + ListStrategy::SimpleRange | ListStrategy::Range => { + let obj = switch_range_to_integer_strategy(list); + w_list_delslice(obj, start, end); + return; + } ListStrategy::Integer => { let len = list.int_items.len(); let s = start.min(len); @@ -2930,6 +3953,15 @@ pub unsafe fn w_list_delslice(obj: PyObjectRef, start: usize, end: usize) { changed = true; } } + ListStrategy::Ascii => { + let len = list.ascii_items.len(); + let s = start.min(len); + let e = end.min(len); + if s < e { + list.ascii_items.drain(s..e); + changed = true; + } + } ListStrategy::Object => { let len = list.length_relaxed(); let s = start.min(len); @@ -3006,13 +4038,41 @@ pub unsafe fn w_list_find_or_count_fast( // listobject.py EmptyListStrategy.find_or_count: returns // `0` in count mode and raises ValueError otherwise. Map the // ValueError to NotFound for the find case. - ListStrategy::Empty => { + ListStrategy::Empty | ListStrategy::Size => { if count { ListFindFast::Count(0) } else { ListFindFast::NotFound } } + ListStrategy::SimpleRange | ListStrategy::Range if is_plain_int1(w_item) => { + let target = if is_int(w_item) { + w_int_get_value(w_item) + } else { + i64::try_from(w_long_get_value(w_item)).unwrap_or(0) + }; + let length = range_list_length(list) as i64; + let (range_start, step) = range_list_start_step(list); + let delta = (target as i128) - (range_start as i128); + let step128 = step as i128; + let candidate = if step128 != 0 && delta % step128 == 0 { + let index = delta / step128; + (index >= 0 && index < length as i128).then_some(index as i64) + } else { + None + }; + if let Some(index) = candidate.filter(|&index| start <= index && index < stop) { + if count { + ListFindFast::Count(1) + } else { + ListFindFast::Found(index) + } + } else if count { + ListFindFast::Count(0) + } else { + ListFindFast::NotFound + } + } // listobject.py IntegerListStrategy.find_or_count: fast path // when `is_plain_int1(w_obj)`, else fall back to generic. ListStrategy::Integer if is_plain_int1(w_item) => { @@ -3146,14 +4206,46 @@ unsafe fn w_list_setslice_inner( // A backwards slice (start > stop) is an empty removal, i.e. a pure // insertion at `start` — never a negative-length window. let end = end.max(start); + if matches!( + list.strategy, + ListStrategy::SimpleRange | ListStrategy::Range + ) { + let obj = switch_range_to_integer_strategy(list); + return w_list_setslice_inner( + obj, + start, + end, + crate::gc_roots::shadow_stack_get(root_base + 1), + ); + } if is_list(w_other) { let other = &*(w_other as *const W_ListObject); // listobject.py EmptyListStrategy.setslice: adopt donor's // strategy and storage wholesale. start/end are 0 because list // is empty, so this is just "become a copy of w_other". - if list.strategy == ListStrategy::Empty { + if matches!(list.strategy, ListStrategy::Empty | ListStrategy::Size) { + if matches!(other.strategy, ListStrategy::Empty | ListStrategy::Size) { + // EmptyListStrategy.copy_into is a no-op. SizeListStrategy + // inherits it, so the receiver keeps its current strategy + // object (and shared hint state). + return Ok(()); + } + if list.strategy == ListStrategy::Size { + list.items = std::ptr::null_mut(); + } match other.strategy { - ListStrategy::Empty => return Ok(()), + ListStrategy::Empty | ListStrategy::Size => unreachable!("handled above"), + ListStrategy::SimpleRange | ListStrategy::Range => { + let obj = crate::gc_roots::shadow_stack_get(root_base); + list_write_barrier(obj); + let obj = crate::gc_roots::shadow_stack_get(root_base); + let w_other = crate::gc_roots::shadow_stack_get(root_base + 1); + let list = &mut *(obj as *mut W_ListObject); + let other = &*(w_other as *const W_ListObject); + list.strategy = other.strategy; + list.items = other.items; + return Ok(()); + } ListStrategy::Integer => { let fresh = IntArray::from_vec(other.int_items.to_vec()); list.int_items.install(fresh); @@ -3184,6 +4276,18 @@ unsafe fn w_list_setslice_inner( list.strategy = ListStrategy::Bytes; return Ok(()); } + ListStrategy::Ascii => { + let other = + &*(crate::gc_roots::shadow_stack_get(root_base + 1) as *const W_ListObject); + let fresh = UnicodeArray::from_vec(other.ascii_items.to_vec()); + let obj = W_ListObject::install_ascii_items( + crate::gc_roots::shadow_stack_get(root_base), + fresh, + ); + let list = &mut *(obj as *mut W_ListObject); + list.strategy = ListStrategy::Ascii; + return Ok(()); + } ListStrategy::Object => { list.set_object_items_from_vec(other.object_to_vec()); let obj = crate::gc_roots::shadow_stack_get(root_base); @@ -3197,6 +4301,13 @@ unsafe fn w_list_setslice_inner( // listobject.py/2013 IntegerListStrategy and :2096/2110 // FloatListStrategy first generalize themselves when the donor is a // compatible numeric strategy, then re-dispatch the same setslice. + if list.strategy == ListStrategy::Integer && other.strategy == ListStrategy::Range { + let donated = range_list_values(other); + let s = start.min(list.int_items.len()); + let e = end.min(list.int_items.len()); + list.int_items.splice(s, e - s, &donated); + return Ok(()); + } if list.strategy == ListStrategy::Integer && matches!( other.strategy, @@ -3249,7 +4360,10 @@ unsafe fn w_list_setslice_inner( let other_len = w_list_len(w_other); if list.strategy == other.strategy || other_len == 0 { match list.strategy { - ListStrategy::Empty => unreachable!("handled above"), + ListStrategy::Empty + | ListStrategy::Size + | ListStrategy::SimpleRange + | ListStrategy::Range => unreachable!("handled above"), ListStrategy::Integer => { let new_items = if list.strategy == other.strategy { other.int_items.as_slice() @@ -3342,6 +4456,38 @@ unsafe fn w_list_setslice_inner( list.bytes_items.splice(s, e - s, new_items); return Ok(()); } + ListStrategy::Ascii => { + let obj = crate::gc_roots::shadow_stack_get(root_base); + let w_other = crate::gc_roots::shadow_stack_get(root_base + 1); + let list = &*(obj as *const W_ListObject); + let other = &*(w_other as *const W_ListObject); + let donates = list.strategy == other.strategy; + let s = start.min(list.ascii_items.len()); + let e = end.min(list.ascii_items.len()); + if obj == w_other { + let mut values = list.ascii_items.to_vec(); + let donated = values.clone(); + values.splice(s..e, donated); + W_ListObject::install_ascii_items(obj, UnicodeArray::from_vec(values)); + return Ok(()); + } + let donated = if donates { other.ascii_items.len() } else { 0 }; + let grown = list.ascii_items.len() - (e - s) + donated; + if grown > list.ascii_items.heap_capacity() { + W_ListObject::ascii_grow(obj, grown); + } + let obj = crate::gc_roots::shadow_stack_get(root_base); + let w_other = crate::gc_roots::shadow_stack_get(root_base + 1); + let list = &mut *(obj as *mut W_ListObject); + let other = &*(w_other as *const W_ListObject); + let new_items = if donates { + other.ascii_items.as_slice() + } else { + &[] + }; + list.ascii_items.splice(s, e - s, new_items); + return Ok(()); + } ListStrategy::Object => {} } } @@ -3417,6 +4563,7 @@ mod tests { #[test] fn strategy_class_names_follow_interp_magic_spellings() { assert_eq!(ListStrategy::Empty.class_name(), "EmptyListStrategy"); + assert_eq!(ListStrategy::Size.class_name(), "SizeListStrategy"); assert_eq!(ListStrategy::Object.class_name(), "ObjectListStrategy"); assert_eq!(ListStrategy::Integer.class_name(), "IntegerListStrategy"); assert_eq!(ListStrategy::Float.class_name(), "FloatListStrategy"); @@ -3425,6 +4572,144 @@ mod tests { "IntOrFloatListStrategy" ); assert_eq!(ListStrategy::Bytes.class_name(), "BytesListStrategy"); + assert_eq!(ListStrategy::Ascii.class_name(), "AsciiListStrategy"); + assert_eq!( + ListStrategy::SimpleRange.class_name(), + "SimpleRangeListStrategy" + ); + assert_eq!(ListStrategy::Range.class_name(), "RangeListStrategy"); + } + + #[test] + fn test_range_list_strategy_creation_and_access() { + let simple = w_list_new_range(0, 1, 4); + let range = w_list_new_range(10, -2, 4); + let empty = w_list_new_range(10, -2, 0); + unsafe { + assert_eq!( + (*(simple as *const W_ListObject)).strategy, + ListStrategy::SimpleRange + ); + assert_eq!( + (*(range as *const W_ListObject)).strategy, + ListStrategy::Range + ); + assert_eq!( + (*(empty as *const W_ListObject)).strategy, + ListStrategy::Empty + ); + assert_eq!(w_list_len(simple), 4); + assert_eq!(w_int_get_value(w_list_getitem(simple, -1).unwrap()), 3); + assert_eq!(w_int_get_value(w_list_getitem(range, 0).unwrap()), 10); + assert_eq!(w_int_get_value(w_list_getitem(range, 3).unwrap()), 4); + assert!(w_list_getitem(range, 4).is_none()); + assert_eq!(w_list_physical_size(simple), None); + assert!(w_list_resize_hint(simple, 100)); + assert_eq!(w_list_len(simple), 4); + } + } + + #[test] + fn test_range_clone_shares_immutable_state_and_pop_replaces_one_side() { + let original = w_list_new_range(5, 3, 4); + unsafe { + let original_state = (*(original as *const W_ListObject)).items; + let clone = w_list_clone_if_shared_strategy(original).unwrap(); + assert_eq!((*(clone as *const W_ListObject)).items, original_state); + assert_eq!(w_int_get_value(w_list_pop(clone, 0).unwrap()), 5); + assert_eq!( + (*(clone as *const W_ListObject)).strategy, + ListStrategy::Range + ); + assert_ne!((*(clone as *const W_ListObject)).items, original_state); + assert_eq!(w_list_len(clone), 3); + assert_eq!(w_int_get_value(w_list_getitem(clone, 0).unwrap()), 8); + assert_eq!(w_list_len(original), 4); + assert_eq!(w_int_get_value(w_list_getitem(original, 0).unwrap()), 5); + } + } + + #[test] + fn test_empty_setslice_uses_range_copy_into_storage() { + let source = w_list_new_range(2, 4, 3); + let destination = w_list_new(Vec::new()); + unsafe { + w_list_setslice(destination, 0, 0, source).unwrap(); + assert_eq!( + (*(destination as *const W_ListObject)).strategy, + ListStrategy::Range + ); + assert_eq!( + (*(destination as *const W_ListObject)).items, + (*(source as *const W_ListObject)).items + ); + assert_eq!(w_int_get_value(w_list_getitem(destination, 2).unwrap()), 10); + } + } + + #[test] + fn test_range_mutations_follow_base_range_strategy() { + let middle = w_list_new_range(10, 2, 5); + let simple = w_list_new_range(0, 1, 2); + unsafe { + assert_eq!(w_int_get_value(w_list_pop(middle, 2).unwrap()), 14); + assert_eq!( + (*(middle as *const W_ListObject)).strategy, + ListStrategy::Integer + ); + assert_eq!(w_int_get_value(w_list_pop_end(simple).unwrap()), 1); + assert_eq!( + (*(simple as *const W_ListObject)).strategy, + ListStrategy::SimpleRange + ); + assert_eq!(w_int_get_value(w_list_pop_end(simple).unwrap()), 0); + assert_eq!( + (*(simple as *const W_ListObject)).strategy, + ListStrategy::Empty + ); + + let append_int = w_list_new_range(0, 1, 3); + w_list_append(append_int, w_int_new(3)); + assert_eq!( + (*(append_int as *const W_ListObject)).strategy, + ListStrategy::Integer + ); + let append_object = w_list_new_range(0, 1, 3); + w_list_append(append_object, crate::noneobject::w_none()); + assert_eq!( + (*(append_object as *const W_ListObject)).strategy, + ListStrategy::Object + ); + } + } + + #[test] + fn test_range_sort_preserves_only_an_already_ordered_range() { + let ascending = w_list_new_range(0, 1, 4); + let descending = w_list_new_range(9, -2, 4); + unsafe { + assert!(w_list_sort_range(ascending, false)); + assert_eq!( + (*(ascending as *const W_ListObject)).strategy, + ListStrategy::SimpleRange + ); + assert!(!w_list_sort_range(ascending, true)); + assert_eq!( + (*(ascending as *const W_ListObject)).strategy, + ListStrategy::Integer + ); + + assert!(w_list_sort_range(descending, true)); + assert_eq!( + (*(descending as *const W_ListObject)).strategy, + ListStrategy::Range + ); + assert!(!w_list_sort_range(descending, false)); + assert_eq!( + (*(descending as *const W_ListObject)).strategy, + ListStrategy::Integer + ); + } } #[test] @@ -3469,7 +4754,7 @@ mod tests { // Integer-strategy list reads only `int_items`: the other side must // carry the empty form, not an allocated single-slot block. This is the // shape `emit_typed_list_inline` already leaves for traced code. - let object_list = w_list_new(vec![crate::w_str_new("x")]); + let object_list = w_list_new(vec![crate::w_none()]); let int_list = w_list_new(vec![w_int_new(1)]); let float_list = w_list_new(vec![crate::floatobject::w_float_new(1.5)]); unsafe { @@ -3478,6 +4763,7 @@ mod tests { assert!(l.int_items.block.is_null()); assert!(l.float_items.block.is_null()); assert!(l.bytes_items.block.is_null()); + assert!(l.ascii_items.block.is_null()); assert!(l.int_items.as_slice().is_empty()); assert!(l.float_items.as_slice().is_empty()); @@ -3486,12 +4772,14 @@ mod tests { assert!(!l.int_items.block.is_null()); assert!(l.float_items.block.is_null()); assert!(l.bytes_items.block.is_null()); + assert!(l.ascii_items.block.is_null()); let l = &*(float_list as *const W_ListObject); assert_eq!(l.strategy, ListStrategy::Float); assert!(l.int_items.block.is_null()); assert!(!l.float_items.block.is_null()); assert!(l.bytes_items.block.is_null()); + assert!(l.ascii_items.block.is_null()); } } @@ -3526,6 +4814,77 @@ mod tests { } } + #[test] + fn ascii_strategy_stores_utf8_payloads_and_dehomogenizes() { + let a = crate::w_str_new("alpha"); + let b = crate::w_str_new("beta"); + let a_storage = unsafe { w_str_storage(a) }; + let list = w_list_new(vec![a, b]); + unsafe { + let l = &*(list as *const W_ListObject); + assert_eq!(l.strategy, ListStrategy::Ascii); + assert!(l.items.is_null()); + assert!(!l.ascii_items.block.is_null()); + let wrapped = w_list_getitem(list, 0).unwrap(); + assert_eq!(crate::w_str_get_value(wrapped), "alpha"); + assert_eq!(w_str_storage(wrapped), a_storage); + + w_list_append(list, crate::w_str_new("gamma")); + assert_eq!( + (*(list as *const W_ListObject)).strategy, + ListStrategy::Ascii + ); + w_list_append(list, crate::w_str_new("é")); + let l = &*(list as *const W_ListObject); + assert_eq!(l.strategy, ListStrategy::Object); + assert!(l.ascii_items.block.is_null()); + assert_eq!(w_list_len(list), 4); + assert_eq!(w_str_storage(w_list_getitem(list, 0).unwrap()), a_storage); + } + } + + #[test] + fn ascii_strategy_mutations_and_sort_stay_unwrapped() { + let list = w_list_new(vec![crate::w_str_new("c"), crate::w_str_new("a")]); + let donor = w_list_new(vec![crate::w_str_new("b")]); + unsafe { + w_list_insert(list, 1, crate::w_str_new("d")); + assert_eq!( + (*(list as *const W_ListObject)).strategy, + ListStrategy::Ascii + ); + w_list_delslice(list, 1, 2); + w_list_setslice(list, 1, 1, donor).unwrap(); + assert!(w_list_sort_strings(list, false)); + assert_eq!( + crate::w_str_get_value(w_list_getitem(list, 0).unwrap()), + "a" + ); + assert_eq!( + crate::w_str_get_value(w_list_getitem(list, 1).unwrap()), + "b" + ); + assert_eq!( + crate::w_str_get_value(w_list_getitem(list, 2).unwrap()), + "c" + ); + w_list_reverse(list); + assert_eq!(crate::w_str_get_value(w_list_pop(list, 0).unwrap()), "c"); + assert_eq!( + (*(list as *const W_ListObject)).strategy, + ListStrategy::Ascii + ); + + let empty = w_list_new(Vec::new()); + w_list_setslice(empty, 0, 0, list).unwrap(); + assert_eq!( + (*(empty as *const W_ListObject)).strategy, + ListStrategy::Ascii + ); + assert_eq!(w_list_len(empty), 2); + } + } + #[test] fn empty_typed_storage_grows_on_first_append() { // The empty form has capacity 0, so the first write must reach `grow` @@ -3554,6 +4913,7 @@ mod tests { assert!(l.int_items.block.is_null()); assert!(l.float_items.block.is_null()); assert!(l.bytes_items.block.is_null()); + assert!(l.ascii_items.block.is_null()); // The next append reinstalls the matching typed storage. w_list_append(list, w_int_new(9)); let l = &*(list as *const W_ListObject); @@ -3974,6 +5334,119 @@ mod tests { } } + #[test] + fn test_size_list_strategy_consumes_hint_on_first_append() { + // listobject.py SizeListStrategy inherits EmptyListStrategy and only + // overrides get_sizehint/_resize_hint. + let list = w_list_new_with_sizehint(13); + unsafe { + assert_eq!( + (*(list as *const W_ListObject)).strategy, + ListStrategy::Size + ); + assert_eq!(w_list_len(list), 0); + assert_eq!(w_list_physical_size(list), Some(0)); + w_list_append(list, w_int_new(7)); + assert_eq!( + (*(list as *const W_ListObject)).strategy, + ListStrategy::Integer + ); + assert_eq!(w_list_physical_size(list), Some(13)); + assert_eq!(w_list_len(list), 1); + assert_eq!(w_int_get_value(w_list_getitem(list, 0).unwrap()), 7); + } + } + + #[test] + fn test_size_list_strategy_clone_shares_strategy_state() { + // SizeListStrategy inherits EmptyListStrategy.clone, which retains + // `self` rather than constructing a new strategy object. + unsafe { + let list = w_list_new_with_sizehint(5); + let clone = w_list_clone_if_shared_strategy(list).unwrap(); + assert!(w_list_resize_hint(list, 9)); + w_list_append(clone, w_int_new(7)); + assert_eq!(w_list_physical_size(clone), Some(9)); + } + } + + #[test] + fn test_object_list_sizehint_preallocates_without_changing_length() { + // BaseObjSpace._unpackiterable_unknown_length uses RPython + // newlist_hint for a raw list[W_Root], not SizeListStrategy. + unsafe { + let list = w_list_new_object_with_sizehint(11); + assert_eq!( + (*(list as *const W_ListObject)).strategy, + ListStrategy::Object + ); + assert_eq!(w_list_len(list), 0); + assert_eq!(w_list_physical_size(list), Some(11)); + w_list_append(list, w_int_new(7)); + assert_eq!(w_list_physical_size(list), Some(11)); + + let empty = w_list_new_object_with_sizehint(0); + assert_eq!(w_list_physical_size(empty), Some(0)); + } + } + + #[test] + fn test_size_list_strategy_preallocates_each_unwrapped_storage() { + unsafe { + let float = w_list_new_with_sizehint(5); + w_list_append(float, w_float_new(1.5)); + assert_eq!( + (*(float as *const W_ListObject)).strategy, + ListStrategy::Float + ); + assert_eq!(w_list_physical_size(float), Some(5)); + + let bytes = w_list_new_with_sizehint(6); + w_list_append(bytes, crate::bytesobject::w_bytes_from_bytes(b"x")); + assert_eq!( + (*(bytes as *const W_ListObject)).strategy, + ListStrategy::Bytes + ); + assert_eq!(w_list_physical_size(bytes), Some(6)); + + let ascii = w_list_new_with_sizehint(7); + w_list_append(ascii, crate::unicodeobject::w_str_new("x")); + assert_eq!( + (*(ascii as *const W_ListObject)).strategy, + ListStrategy::Ascii + ); + assert_eq!(w_list_physical_size(ascii), Some(7)); + + let object = w_list_new_with_sizehint(8); + w_list_append(object, crate::w_none()); + assert_eq!( + (*(object as *const W_ListObject)).strategy, + ListStrategy::Object + ); + assert_eq!(w_list_physical_size(object), Some(8)); + } + } + + #[test] + fn test_resize_hint_uses_rpython_capacity_policy() { + unsafe { + let empty = w_list_new(Vec::new()); + assert!(w_list_resize_hint(empty, 13)); + assert_eq!( + (*(empty as *const W_ListObject)).strategy, + ListStrategy::Size + ); + assert_eq!(w_list_sizehint(empty), Some(13)); + + let ints = w_list_new(vec![w_int_new(1), w_int_new(2)]); + assert_eq!(w_list_physical_size(ints), Some(2)); + assert!(w_list_resize_hint(ints, 10)); + // rlist.py: newsize + 6 + (newsize >> 3) + assert_eq!(w_list_physical_size(ints), Some(17)); + assert_eq!(w_int_get_value(w_list_getitem(ints, 1).unwrap()), 2); + } + } + #[test] fn test_clear_resets_to_empty_strategy() { // listobject.py W_ListObject.clear → EmptyListStrategy. diff --git a/pyre/pyre-object/src/unicode_array.rs b/pyre/pyre-object/src/unicode_array.rs new file mode 100644 index 00000000000..e5a9b409b84 --- /dev/null +++ b/pyre/pyre-object/src/unicode_array.rs @@ -0,0 +1,297 @@ +use std::ops::{Index, IndexMut}; + +use crate::object_array::{ + ItemsBlock, alloc_list_items_block_gc, dealloc_list_items_block, items_block_capacity, + items_block_items_base, +}; +use crate::pyobject::PyObjectRef; +use rustpython_wtf8::Wtf8Buf; + +/// PyPy `AsciiListStrategy`'s erased `[rpython str]` storage. +/// +/// Each entry is the GC pointer to a `Wtf8Buf`, not a boxed +/// `W_UnicodeObject`. `ItemsBlock` is the runtime's `GcArray(GCREF)` shape, so +/// its existing varsize trace forwards both the backing block and every raw +/// string pointer it contains. +#[repr(C)] +pub struct UnicodeArray { + pub block: *mut ItemsBlock, + len: usize, +} + +pub const UNICODE_ARRAY_BLOCK_OFFSET: usize = std::mem::offset_of!(UnicodeArray, block); +pub const UNICODE_ARRAY_LEN_OFFSET: usize = std::mem::offset_of!(UnicodeArray, len); + +impl UnicodeArray { + #[inline] + fn base(&self) -> *mut PyObjectRef { + unsafe { items_block_items_base(self.block) } + } + + pub fn empty() -> Self { + Self { + block: std::ptr::null_mut(), + len: 0, + } + } + + pub fn from_vec(values: Vec<*const Wtf8Buf>) -> Self { + let mut refs = Vec::with_capacity(values.len()); + for value in values { + refs.push(value as PyObjectRef); + } + let len = refs.len(); + Self { + block: unsafe { alloc_list_items_block_gc(&refs) }, + len, + } + } + + /// `AbstractUnwrappedStrategy.get_empty_storage(sizehint)` for ASCII text. + pub fn with_capacity(capacity: usize) -> Self { + if capacity == 0 { + return Self::empty(); + } + Self { + block: unsafe { + crate::object_array::grow_list_items_block_gc(std::ptr::null_mut(), capacity, 0) + }, + len: 0, + } + } + + #[must_use] + pub fn pin_block(&self) -> usize { + let slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(self.block as PyObjectRef); + slot + } + + pub fn reload_block(&mut self, slot: usize) { + self.block = crate::gc_roots::shadow_stack_get(slot) as *mut ItemsBlock; + } + + pub fn install(&mut self, fresh: UnicodeArray) { + let _roots = crate::gc_roots::push_roots(); + let slot = fresh.pin_block(); + *self = fresh; + self.reload_block(slot); + } + + #[inline] + fn capacity(&self) -> usize { + unsafe { items_block_capacity(self.block) } + } + + #[inline] + pub fn spare_capacity(&self) -> usize { + self.capacity().saturating_sub(self.len) + } + + #[inline] + pub fn heap_capacity(&self) -> usize { + self.capacity() + } + + #[inline] + pub fn set_len(&mut self, new_len: usize) { + assert!(new_len <= self.capacity()); + self.len = new_len; + } + + #[inline] + pub fn is_inline(&self) -> bool { + false + } + + /// The room `capacity` must already hold for `additional` more entries. + /// + /// A fresh block is young, and this array is embedded in the owning + /// `W_ListObject` — the only object through which a collection reaches it. + /// An old-gen owner that gains that edge without being on the remembered + /// set is skipped by the minor collection that would forward the block, and + /// the block, along with every `Wtf8Buf` reachable only through it, is + /// reclaimed while the list still names it. `UnicodeArray` cannot reach its + /// owner to barrier it, so it never allocates a block: the list reserves + /// room through `W_ListObject::ascii_grow`, which barriers on both sides of + /// the allocation. Refuse loudly rather than grow behind the owner's back. + #[inline] + fn assert_room(&self, additional: usize) { + assert!( + self.len + additional <= self.capacity(), + "UnicodeArray needs {additional} more slot(s) than its capacity {}; \ + reserve through W_ListObject::ascii_grow first", + self.capacity(), + ); + } + + #[inline] + fn barrier(&self) { + if !self.block.is_null() { + crate::gc_hook::try_gc_write_barrier(self.block as *mut u8); + } + } + + pub fn push(&mut self, value: *const Wtf8Buf) { + let _roots = crate::gc_roots::push_roots(); + let value_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(value as PyObjectRef); + self.assert_room(1); + self.barrier(); + unsafe { *self.base().add(self.len) = crate::gc_roots::shadow_stack_get(value_slot) }; + self.len += 1; + } + + #[inline] + pub fn len(&self) -> usize { + self.len + } + + #[inline] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn as_slice(&self) -> &[*const Wtf8Buf] { + unsafe { std::slice::from_raw_parts(self.base() as *const *const Wtf8Buf, self.len) } + } + + pub fn as_mut_slice(&mut self) -> &mut [*const Wtf8Buf] { + unsafe { std::slice::from_raw_parts_mut(self.base() as *mut *const Wtf8Buf, self.len) } + } + + pub fn to_vec(&self) -> Vec<*const Wtf8Buf> { + self.as_slice().to_vec() + } + + pub fn insert(&mut self, index: usize, value: *const Wtf8Buf) { + assert!(index <= self.len); + let _roots = crate::gc_roots::push_roots(); + let value_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(value as PyObjectRef); + self.assert_room(1); + self.barrier(); + unsafe { + let p = self.base().add(index); + std::ptr::copy(p, p.add(1), self.len - index); + *p = crate::gc_roots::shadow_stack_get(value_slot); + } + self.len += 1; + } + + pub fn set(&mut self, index: usize, value: *const Wtf8Buf) { + assert!(index < self.len); + let _roots = crate::gc_roots::push_roots(); + let slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(value as PyObjectRef); + self.barrier(); + unsafe { *self.base().add(index) = crate::gc_roots::shadow_stack_get(slot) }; + } + + pub fn remove(&mut self, index: usize) -> *const Wtf8Buf { + assert!(index < self.len); + let value = self.as_slice()[index]; + unsafe { + let p = self.base().add(index); + std::ptr::copy(p.add(1), p, self.len - index - 1); + *p.add(self.len - index - 1) = std::ptr::null_mut(); + } + self.len -= 1; + value + } + + pub fn pop(&mut self) -> *const Wtf8Buf { + assert!(self.len > 0); + let value = self.as_slice()[self.len - 1]; + self.len -= 1; + unsafe { *self.base().add(self.len) = std::ptr::null_mut() }; + value + } + + pub fn reverse(&mut self) { + self.as_mut_slice().reverse(); + } + + pub fn splice(&mut self, start: usize, remove_count: usize, values: &[*const Wtf8Buf]) { + let old_len = self.len; + let start = start.min(old_len); + let removed = remove_count.min(old_len - start); + let new_len = old_len - removed + values.len(); + let _roots = crate::gc_roots::push_roots(); + let root_base = crate::gc_roots::shadow_stack_len(); + for &value in values { + let _ = crate::gc_roots::pin_root(value as PyObjectRef); + } + assert!( + new_len <= self.capacity(), + "UnicodeArray splice needs {new_len} slots but capacity is {}; \ + reserve through W_ListObject::ascii_grow first", + self.capacity(), + ); + self.barrier(); + unsafe { + let base = self.base(); + std::ptr::copy( + base.add(start + removed), + base.add(start + values.len()), + old_len - start - removed, + ); + self.len = new_len; + for i in 0..values.len() { + *base.add(start + i) = crate::gc_roots::shadow_stack_get(root_base + i); + } + for i in new_len..old_len { + *base.add(i) = std::ptr::null_mut(); + } + } + } + + pub fn drain(&mut self, range: std::ops::Range) { + assert!(range.start <= range.end && range.end <= self.len); + let count = range.end - range.start; + if count == 0 { + return; + } + unsafe { + let base = self.base(); + std::ptr::copy( + base.add(range.end), + base.add(range.start), + self.len - range.end, + ); + for i in self.len - count..self.len { + *base.add(i) = std::ptr::null_mut(); + } + } + self.len -= count; + } + + pub fn clear(&mut self) { + unsafe { + for i in 0..self.len { + *self.base().add(i) = std::ptr::null_mut(); + } + } + self.len = 0; + } +} + +impl Drop for UnicodeArray { + fn drop(&mut self) { + unsafe { dealloc_list_items_block(self.block) }; + } +} + +impl Index for UnicodeArray { + type Output = *const Wtf8Buf; + + fn index(&self, index: usize) -> &Self::Output { + &self.as_slice()[index] + } +} + +impl IndexMut for UnicodeArray { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.as_mut_slice()[index] + } +} diff --git a/pyre/pyre-object/src/unicodeobject.rs b/pyre/pyre-object/src/unicodeobject.rs index 1b2ae0787dd..5e59415c480 100644 --- a/pyre/pyre-object/src/unicodeobject.rs +++ b/pyre/pyre-object/src/unicodeobject.rs @@ -214,6 +214,46 @@ pub fn w_str_new_managed(s: &str) -> PyObjectRef { w_str_from_wtf8_managed(Wtf8Buf::from_string(s.to_string())) } +/// Wrap an existing PyPy UTF-8 `rpython str` payload for +/// `AsciiListStrategy.wrap` (`listobject.py`). The immutable `_utf8` storage +/// is shared and only the exact `W_UnicodeObject` wrapper is newly allocated, +/// just as `space.newutf8(stringval, len(stringval))` does upstream. +#[majit_macros::dont_look_inside] +pub fn w_str_from_storage(value: *mut UnicodeValueStorage) -> PyObjectRef { + let _roots = crate::gc_roots::push_roots(); + let value_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(value as PyObjectRef); + let class_slot = crate::gc_roots::shadow_stack_len(); + let _ = crate::gc_roots::pin_root(get_instantiate(&STR_TYPE)); + let raw = crate::gc_hook::try_gc_alloc_stable_raw(W_UNICODE_GC_TYPE_ID, W_UNICODE_OBJECT_SIZE); + let value = crate::gc_roots::shadow_stack_get(value_slot) as *mut UnicodeValueStorage; + // AsciiListStrategy accepts only `is_ascii()` values, for which the byte + // length and code-point length are identical. + let len = unsafe { (*value).len() }; + let body = W_UnicodeObject { + ob_header: PyObject { + ob_type: &STR_TYPE as *const PyType, + w_class: crate::gc_roots::shadow_stack_get(class_slot), + }, + value, + byte_len: len, + len, + w_slots: PY_NULL, + index_storage: std::ptr::null_mut(), + hash: 0, + }; + if raw.is_null() { + crate::lltype::malloc_typed(body) as PyObjectRef + } else { + unsafe { std::ptr::write(raw as *mut W_UnicodeObject, body) }; + // `value` may be an existing young GC storage box. The new wrapper is + // born old, so mirror the creation barrier used by + // `w_bytes_from_block` before the list drops its array edge. + crate::gc_hook::try_gc_write_barrier_managed(raw); + raw as PyObjectRef + } +} + /// Allocate a new W_UnicodeObject from a WTF-8 buffer that may carry lone /// surrogate code points (produced by surrogateescape / surrogatepass /// decoding). `byte_len` is the WTF-8 byte count, `len` the code point @@ -849,6 +889,16 @@ pub unsafe fn w_str_get_wtf8(obj: PyObjectRef) -> &'static Wtf8 { } } +/// Return the erased UTF-8 `rpython str` stored by PyPy's +/// `AsciiListStrategy`. +/// +/// # Safety +/// `obj` must point to a valid exact `W_UnicodeObject`. +#[inline] +pub unsafe fn w_str_storage(obj: PyObjectRef) -> *mut UnicodeValueStorage { + unsafe { (*(obj as *const W_UnicodeObject)).value } +} + /// Object-space entry to [`W_UnicodeObject::eq_w`]. /// /// # Safety