diff --git a/majit/majit-gc/src/collector.rs b/majit/majit-gc/src/collector.rs index 1bac85a5ce4..8adbb5ab9b0 100644 --- a/majit/majit-gc/src/collector.rs +++ b/majit/majit-gc/src/collector.rs @@ -9,6 +9,7 @@ use indexmap::IndexSet; use majit_ir::GcRef; use std::collections::VecDeque; +use std::sync::RwLock; use std::sync::atomic::{AtomicUsize, Ordering}; use crate::address_dict::AddressMap; @@ -83,6 +84,52 @@ pub struct GcConfig { pub taggedpointers: bool, } +/// The variables [`GcConfig`] and [`MiniMarkGC::with_config`] resolve against, +/// for an embedder that has to hand its environment over rather than share it. +/// +/// Published here so such a host does not keep its own copy of the list in step +/// with the collector; `PYPY_GC_DEBUG` and the tracing knobs are absent because +/// nothing in this file reads them. +pub const GC_ENV_NAMES: &[&str] = &[ + "PYPY_GC_NURSERY", + "PYPY_GC_INCREMENT_STEP", + "PYPY_GC_MAJOR_COLLECT", + "PYPY_GC_GROWTH", + "PYPY_GC_MIN", + "PYPY_GC_MAX", + "PYPY_GC_MAX_DELTA", +]; + +/// Environment an embedder supplies because the platform gives the process +/// none. Read only where `std::env` misses, so a host that has a real +/// environment resolves against it exactly as before. +/// +/// `wasm32-unknown-unknown` is the case that needs it: `std::env::var` there +/// always fails, so every name in [`GC_ENV_NAMES`] reads as unset and a guest +/// runs the built-in defaults no matter what its host was configured with. The +/// interpreter's launcher options have the same problem and the same answer +/// (`pyre-wasm`'s `LAUNCH_ENV`). +static SUPPLIED_ENV: RwLock> = RwLock::new(Vec::new()); + +/// Install the environment [`GC_ENV_NAMES`] resolves against when the process +/// has none. Call before the first allocation: the values are read once, when +/// the collector is built. +pub fn set_supplied_env(entries: Vec<(String, String)>) { + *SUPPLIED_ENV.write().unwrap() = entries; +} + +/// `std::env::var`, falling back to what the embedder supplied. +fn env_var(varname: &str) -> Option { + if let Ok(value) = std::env::var(varname) { + return Some(value); + } + let supplied = SUPPLIED_ENV.read().unwrap(); + supplied + .iter() + .find(|(name, _)| name == varname) + .map(|(_, value)| value.clone()) +} + /// env.py:17-36 `_read_float_and_factor_from_env`. Parse `varname` as a float /// with an optional `k`/`m`/`g` size suffix (optionally followed by `b`/`B`), /// returning `(value, factor)`. `None` mirrors PyPy's `(0.0, 0)` absent / @@ -91,7 +138,7 @@ pub struct GcConfig { /// non-finite handling happens at the `int`/`r_uint` conversion sites, as /// upstream where `int(inf)`/`r_uint(inf)` raise. fn read_float_and_factor_from_env(varname: &str) -> Option<(f64, f64)> { - let raw = std::env::var(varname).ok()?; + let raw = env_var(varname)?; let mut value = raw.trim(); if value.is_empty() { return None; @@ -5338,6 +5385,29 @@ mod tests { static SHADOW_STACK_TEST_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + /// A supplied environment answers a name the process does not define, and + /// yields to one it does. The name is not in [`GC_ENV_NAMES`], so a + /// concurrently built collector cannot see this table. + #[test] + fn supplied_env_fills_in_only_what_the_process_lacks() { + let absent = "PYRE_TEST_SUPPLIED_ENV_ABSENT"; + let present = "PYRE_TEST_SUPPLIED_ENV_PRESENT"; + // SAFETY: single-threaded within this test; the names are unique to it. + unsafe { std::env::set_var(present, "2m") }; + + assert_eq!(read_uint_from_env(absent), None); + set_supplied_env(vec![ + (absent.to_string(), "1m".to_string()), + (present.to_string(), "4m".to_string()), + ]); + assert_eq!(read_uint_from_env(absent), Some(1024 * 1024)); + assert_eq!(read_uint_from_env(present), Some(2 * 1024 * 1024)); + + set_supplied_env(Vec::new()); + assert_eq!(read_uint_from_env(absent), None); + unsafe { std::env::remove_var(present) }; + } + /// Helper: create a GC with a small nursery for testing. fn test_gc(nursery_size: usize) -> MiniMarkGC { MiniMarkGC::with_config(GcConfig { diff --git a/pyre/bench/synth/arith_int_bool.wasm.jitstats b/pyre/bench/synth/arith_int_bool.wasm.jitstats index 63ad9a4924b..36ef78d98f9 100644 --- a/pyre/bench/synth/arith_int_bool.wasm.jitstats +++ b/pyre/bench/synth/arith_int_bool.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2214 +guard_failures=2211 internal_compile_panics=0 loops_aborted=0 loops_compiled=7 diff --git a/pyre/bench/synth/binary_slice_index.wasm.jitstats b/pyre/bench/synth/binary_slice_index.wasm.jitstats index 63ea4fe852c..9c16916b038 100644 --- a/pyre/bench/synth/binary_slice_index.wasm.jitstats +++ b/pyre/bench/synth/binary_slice_index.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=4 +guard_failures=2 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/bool_dunder_error_no_leak.wasm.jitstats b/pyre/bench/synth/bool_dunder_error_no_leak.wasm.jitstats index 26fead6b346..1cc731febcf 100644 --- a/pyre/bench/synth/bool_dunder_error_no_leak.wasm.jitstats +++ b/pyre/bench/synth/bool_dunder_error_no_leak.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/build_set_hashability.wasm.jitstats b/pyre/bench/synth/build_set_hashability.wasm.jitstats index 26fead6b346..1cc731febcf 100644 --- a/pyre/bench/synth/build_set_hashability.wasm.jitstats +++ b/pyre/bench/synth/build_set_hashability.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/closure_per_call.wasm.jitstats b/pyre/bench/synth/closure_per_call.wasm.jitstats index 04e0b5011b3..a8c5e65dc64 100644 --- a/pyre/bench/synth/closure_per_call.wasm.jitstats +++ b/pyre/bench/synth/closure_per_call.wasm.jitstats @@ -5,7 +5,7 @@ descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=468 +guard_failures=420 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/dict_set.wasm.jitstats b/pyre/bench/synth/dict_set.wasm.jitstats index 908b18b78d5..ab7acb61f13 100644 --- a/pyre/bench/synth/dict_set.wasm.jitstats +++ b/pyre/bench/synth/dict_set.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=609 +guard_failures=606 internal_compile_panics=0 loops_aborted=0 loops_compiled=5 diff --git a/pyre/bench/synth/divmod_long_int_pair.wasm.jitstats b/pyre/bench/synth/divmod_long_int_pair.wasm.jitstats index ef51a080b1c..3787785f8c2 100644 --- a/pyre/bench/synth/divmod_long_int_pair.wasm.jitstats +++ b/pyre/bench/synth/divmod_long_int_pair.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=9 +guard_failures=4 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/exc_caught_in_callee_return_loop.wasm.jitstats b/pyre/bench/synth/exc_caught_in_callee_return_loop.wasm.jitstats index b58f2529486..f1958f4d488 100644 --- a/pyre/bench/synth/exc_caught_in_callee_return_loop.wasm.jitstats +++ b/pyre/bench/synth/exc_caught_in_callee_return_loop.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=613 +guard_failures=611 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 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 62e3f6d3e6a..bf7bfa5ac55 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,9 +1,9 @@ -bridges_compiled=5 +bridges_compiled=4 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=1016 +guard_failures=814 internal_compile_panics=0 loops_aborted=0 loops_compiled=15 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 8aeebdb89ea..385dea9c0be 100644 --- a/pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats +++ b/pyre/bench/synth/exception_inline_callee_tb_frames.wasm.jitstats @@ -1,9 +1,9 @@ -bridges_compiled=5 +bridges_compiled=4 descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=1008 +guard_failures=807 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/exception_inlined_callee_caught.wasm.jitstats b/pyre/bench/synth/exception_inlined_callee_caught.wasm.jitstats index 9dc67f88e2a..8beed56f050 100644 --- a/pyre/bench/synth/exception_inlined_callee_caught.wasm.jitstats +++ b/pyre/bench/synth/exception_inlined_callee_caught.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/exception_oserror_fields.wasm.jitstats b/pyre/bench/synth/exception_oserror_fields.wasm.jitstats index cbfba7e5e41..114b48b9fd0 100644 --- a/pyre/bench/synth/exception_oserror_fields.wasm.jitstats +++ b/pyre/bench/synth/exception_oserror_fields.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=202 +guard_failures=201 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/exception_subclass_attrs.wasm.jitstats b/pyre/bench/synth/exception_subclass_attrs.wasm.jitstats index 6575161c7ec..8beed56f050 100644 --- a/pyre/bench/synth/exception_subclass_attrs.wasm.jitstats +++ b/pyre/bench/synth/exception_subclass_attrs.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=3 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats b/pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats index 95b7ea20405..6a29fca26ca 100644 --- a/pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats +++ b/pyre/bench/synth/exception_traceback_frame_lineno.wasm.jitstats @@ -5,7 +5,7 @@ descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=819 +guard_failures=817 internal_compile_panics=0 loops_aborted=0 loops_compiled=17 diff --git a/pyre/bench/synth/exception_traceback_loop_forms.wasm.jitstats b/pyre/bench/synth/exception_traceback_loop_forms.wasm.jitstats index 742556ba522..197a4ce61e6 100644 --- a/pyre/bench/synth/exception_traceback_loop_forms.wasm.jitstats +++ b/pyre/bench/synth/exception_traceback_loop_forms.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=822 +guard_failures=811 internal_compile_panics=0 loops_aborted=0 loops_compiled=8 diff --git a/pyre/bench/synth/exception_value_op_caught.wasm.jitstats b/pyre/bench/synth/exception_value_op_caught.wasm.jitstats index 9dc67f88e2a..8beed56f050 100644 --- a/pyre/bench/synth/exception_value_op_caught.wasm.jitstats +++ b/pyre/bench/synth/exception_value_op_caught.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/float_div_zero_caught_loop.wasm.jitstats b/pyre/bench/synth/float_div_zero_caught_loop.wasm.jitstats index b58f2529486..f1958f4d488 100644 --- a/pyre/bench/synth/float_div_zero_caught_loop.wasm.jitstats +++ b/pyre/bench/synth/float_div_zero_caught_loop.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=613 +guard_failures=611 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/foriter_body_return.wasm.jitstats b/pyre/bench/synth/foriter_body_return.wasm.jitstats index 4c0ca1a180a..99a92443498 100644 --- a/pyre/bench/synth/foriter_body_return.wasm.jitstats +++ b/pyre/bench/synth/foriter_body_return.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=612 +guard_failures=604 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/foriter_exempt_nested_foriter.wasm.jitstats b/pyre/bench/synth/foriter_exempt_nested_foriter.wasm.jitstats index 4f25297e805..8f9aaeb4a9c 100644 --- a/pyre/bench/synth/foriter_exempt_nested_foriter.wasm.jitstats +++ b/pyre/bench/synth/foriter_exempt_nested_foriter.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2 +guard_failures=1 internal_compile_panics=0 loops_aborted=1 loops_compiled=3 diff --git a/pyre/bench/synth/foriter_exempt_shared_generator.wasm.jitstats b/pyre/bench/synth/foriter_exempt_shared_generator.wasm.jitstats index 4f25297e805..8f9aaeb4a9c 100644 --- a/pyre/bench/synth/foriter_exempt_shared_generator.wasm.jitstats +++ b/pyre/bench/synth/foriter_exempt_shared_generator.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2 +guard_failures=1 internal_compile_panics=0 loops_aborted=1 loops_compiled=3 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 c229b7a7832..9c9e7bc0d9c 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 @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2037 +guard_failures=2027 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/gc_deque_backing_list.wasm.jitstats b/pyre/bench/synth/gc_deque_backing_list.wasm.jitstats index c2c345bc6ae..6ba592f1135 100644 --- a/pyre/bench/synth/gc_deque_backing_list.wasm.jitstats +++ b/pyre/bench/synth/gc_deque_backing_list.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=204 +guard_failures=203 internal_compile_panics=0 loops_aborted=0 loops_compiled=5 diff --git a/pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats b/pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats index 6e3b5767b12..40f0d87bd34 100644 --- a/pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats +++ b/pyre/bench/synth/gc_iterator_source_drop.wasm.jitstats @@ -5,7 +5,7 @@ descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=614 +guard_failures=613 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/getattribute_override_no_bind.wasm.jitstats b/pyre/bench/synth/getattribute_override_no_bind.wasm.jitstats index 7d39b749f20..26fead6b346 100644 --- a/pyre/bench/synth/getattribute_override_no_bind.wasm.jitstats +++ b/pyre/bench/synth/getattribute_override_no_bind.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=14 +guard_failures=2 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/global_cell_shortpreamble_hot.wasm.jitstats b/pyre/bench/synth/global_cell_shortpreamble_hot.wasm.jitstats index 2b9c55c75d0..205058aeee4 100644 --- a/pyre/bench/synth/global_cell_shortpreamble_hot.wasm.jitstats +++ b/pyre/bench/synth/global_cell_shortpreamble_hot.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=409 +guard_failures=408 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/inline_gate_operand_provenance.wasm.jitstats b/pyre/bench/synth/inline_gate_operand_provenance.wasm.jitstats index 18b28dd8f70..b4fa09e329c 100644 --- a/pyre/bench/synth/inline_gate_operand_provenance.wasm.jitstats +++ b/pyre/bench/synth/inline_gate_operand_provenance.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=10 +guard_failures=4 internal_compile_panics=0 loops_aborted=3 loops_compiled=7 diff --git a/pyre/bench/synth/inline_subwalk_mutating_residual.wasm.jitstats b/pyre/bench/synth/inline_subwalk_mutating_residual.wasm.jitstats index a2529fd23d5..26207624a12 100644 --- a/pyre/bench/synth/inline_subwalk_mutating_residual.wasm.jitstats +++ b/pyre/bench/synth/inline_subwalk_mutating_residual.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=815 +guard_failures=813 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/inline_subwalk_property_mutates.wasm.jitstats b/pyre/bench/synth/inline_subwalk_property_mutates.wasm.jitstats index b58f2529486..f1958f4d488 100644 --- a/pyre/bench/synth/inline_subwalk_property_mutates.wasm.jitstats +++ b/pyre/bench/synth/inline_subwalk_property_mutates.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=613 +guard_failures=611 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/inline_subwalk_user_iterator.wasm.jitstats b/pyre/bench/synth/inline_subwalk_user_iterator.wasm.jitstats index 4f05176c78a..93e39b6768e 100644 --- a/pyre/bench/synth/inline_subwalk_user_iterator.wasm.jitstats +++ b/pyre/bench/synth/inline_subwalk_user_iterator.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=402 +guard_failures=401 internal_compile_panics=0 loops_aborted=1 loops_compiled=3 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 2321d60dcb6..d68b413d6f3 100644 --- a/pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats +++ b/pyre/bench/synth/kept_stack_deep_var_shortcircuit.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=824 +guard_failures=819 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/list_ops.wasm.jitstats b/pyre/bench/synth/list_ops.wasm.jitstats index 349d754e74b..b6327714294 100644 --- a/pyre/bench/synth/list_ops.wasm.jitstats +++ b/pyre/bench/synth/list_ops.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=408 +guard_failures=407 internal_compile_panics=0 loops_aborted=0 loops_compiled=7 diff --git a/pyre/bench/synth/loops_comprehension.wasm.jitstats b/pyre/bench/synth/loops_comprehension.wasm.jitstats index 928aaec299b..719c9a944ce 100644 --- a/pyre/bench/synth/loops_comprehension.wasm.jitstats +++ b/pyre/bench/synth/loops_comprehension.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=2613 +guard_failures=2611 internal_compile_panics=0 loops_aborted=0 loops_compiled=7 diff --git a/pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats b/pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats index 454ae31ec45..f4509ffc3bb 100644 --- a/pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats +++ b/pyre/bench/synth/match_sequence_of_class_patterns.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=402 +guard_failures=401 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/math_isqrt_compare_bridge_resume.wasm.jitstats b/pyre/bench/synth/math_isqrt_compare_bridge_resume.wasm.jitstats index d0ac585f531..e2698431419 100644 --- a/pyre/bench/synth/math_isqrt_compare_bridge_resume.wasm.jitstats +++ b/pyre/bench/synth/math_isqrt_compare_bridge_resume.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=409 +guard_failures=402 internal_compile_panics=0 loops_aborted=0 loops_compiled=4 diff --git a/pyre/bench/synth/mutate_then_raise_caught.wasm.jitstats b/pyre/bench/synth/mutate_then_raise_caught.wasm.jitstats index fc2889ef19d..f1958f4d488 100644 --- a/pyre/bench/synth/mutate_then_raise_caught.wasm.jitstats +++ b/pyre/bench/synth/mutate_then_raise_caught.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=612 +guard_failures=611 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/newslice_step_hot.wasm.jitstats b/pyre/bench/synth/newslice_step_hot.wasm.jitstats index 023ac9e6f5e..e1158a4643b 100644 --- a/pyre/bench/synth/newslice_step_hot.wasm.jitstats +++ b/pyre/bench/synth/newslice_step_hot.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=24 +guard_failures=4 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/recursion_memo_branch.wasm.jitstats b/pyre/bench/synth/recursion_memo_branch.wasm.jitstats index 4600bdb39c2..2fc0df5d183 100644 --- a/pyre/bench/synth/recursion_memo_branch.wasm.jitstats +++ b/pyre/bench/synth/recursion_memo_branch.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=4731 +guard_failures=4724 internal_compile_panics=0 loops_aborted=2 loops_compiled=3 diff --git a/pyre/bench/synth/recursive_call_frame_relocation.wasm.jitstats b/pyre/bench/synth/recursive_call_frame_relocation.wasm.jitstats index 4bc1b8e3595..54792ece17e 100644 --- a/pyre/bench/synth/recursive_call_frame_relocation.wasm.jitstats +++ b/pyre/bench/synth/recursive_call_frame_relocation.wasm.jitstats @@ -5,7 +5,7 @@ descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=648 +guard_failures=638 internal_compile_panics=0 loops_aborted=0 loops_compiled=3 diff --git a/pyre/bench/synth/sre_pattern_methods.wasm.jitstats b/pyre/bench/synth/sre_pattern_methods.wasm.jitstats index bedde7b54df..235d297fa8f 100644 --- a/pyre/bench/synth/sre_pattern_methods.wasm.jitstats +++ b/pyre/bench/synth/sre_pattern_methods.wasm.jitstats @@ -5,7 +5,7 @@ descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 field_pos_attached_misplaced=0 field_pos_spec_misplaced=0 -guard_failures=1013 +guard_failures=1012 internal_compile_panics=0 loops_aborted=0 loops_compiled=7 diff --git a/pyre/bench/synth/str_fstring.wasm.jitstats b/pyre/bench/synth/str_fstring.wasm.jitstats index 3fb932f4dc9..046924bea32 100644 --- a/pyre/bench/synth/str_fstring.wasm.jitstats +++ b/pyre/bench/synth/str_fstring.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=666 +guard_failures=658 internal_compile_panics=0 loops_aborted=0 loops_compiled=6 diff --git a/pyre/bench/synth/type_name_setter.wasm.jitstats b/pyre/bench/synth/type_name_setter.wasm.jitstats index 6575161c7ec..8beed56f050 100644 --- a/pyre/bench/synth/type_name_setter.wasm.jitstats +++ b/pyre/bench/synth/type_name_setter.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=3 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/type_name_surrogate_reject.wasm.jitstats b/pyre/bench/synth/type_name_surrogate_reject.wasm.jitstats index b6d47c17adc..283898ec6a3 100644 --- a/pyre/bench/synth/type_name_surrogate_reject.wasm.jitstats +++ b/pyre/bench/synth/type_name_surrogate_reject.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=202 +guard_failures=201 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/unary_negative.wasm.jitstats b/pyre/bench/synth/unary_negative.wasm.jitstats index f19add84d62..26fead6b346 100644 --- a/pyre/bench/synth/unary_negative.wasm.jitstats +++ b/pyre/bench/synth/unary_negative.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=5 +guard_failures=2 internal_compile_panics=0 loops_aborted=0 loops_compiled=2 diff --git a/pyre/bench/synth/unary_positive_resume.wasm.jitstats b/pyre/bench/synth/unary_positive_resume.wasm.jitstats index 6575161c7ec..8beed56f050 100644 --- a/pyre/bench/synth/unary_positive_resume.wasm.jitstats +++ b/pyre/bench/synth/unary_positive_resume.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=3 +guard_failures=1 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/unpack_ex_hot.wasm.jitstats b/pyre/bench/synth/unpack_ex_hot.wasm.jitstats index 40f1f7e21cb..9dc67f88e2a 100644 --- a/pyre/bench/synth/unpack_ex_hot.wasm.jitstats +++ b/pyre/bench/synth/unpack_ex_hot.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=15 +guard_failures=2 internal_compile_panics=0 loops_aborted=0 loops_compiled=1 diff --git a/pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats b/pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats index f98f2ca9404..bf08a9bf682 100644 --- a/pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats +++ b/pyre/bench/synth/wasm_ca_trampoline_decline.wasm.jitstats @@ -3,7 +3,7 @@ descr_set_absent=0 descr_set_ambiguous=0 descr_set_stale_absent=0 fbw_rolled_back_with_effects=0 -guard_failures=420 +guard_failures=404 internal_compile_panics=0 loops_aborted=1 loops_compiled=2 diff --git a/pyre/check.py b/pyre/check.py index b1b9bfdafc0..f16a2197188 100644 --- a/pyre/check.py +++ b/pyre/check.py @@ -465,6 +465,11 @@ def pyre_env(): # (0.125 * total memory) enters only as an upper bound at collector.rs:3570 # and the `min_heap_size` floor is applied after it (:2452), so the floor # wins on every host. + # + # Both pins reach the wasm backend only because `pyre-wasm-runner` hands + # them to the guest explicitly (`pyre_set_gc_env`): a wasm32-unknown-unknown + # module's `std::env` is permanently empty, so setting them here does + # nothing on its own there. env.setdefault("PYPY_GC_MIN", str(256 * 1024 * 1024)) # Keep the bench directory off `sys.path` (`-P`), so the jit-stats counters # describe the fixture rather than the directory it happens to sit in. diff --git a/pyre/extra_tests/parity_tests/memoryio_seek_whence_range.py b/pyre/extra_tests/parity_tests/memoryio_seek_whence_range.py new file mode 100644 index 00000000000..b717ac086a0 --- /dev/null +++ b/pyre/extra_tests/parity_tests/memoryio_seek_whence_range.py @@ -0,0 +1,75 @@ +"""The memory streams take `seek`'s whence as a C int, its position wider.""" + +import io + + +def raises(exc_type, operation): + try: + result = operation() + except exc_type: + return + except BaseException as exc: + raise AssertionError(f"expected {exc_type.__name__}, got {exc!r}") from exc + raise AssertionError(f"expected {exc_type.__name__}, got {result!r}") + + +class Index: + """Only `__index__` — what the whence is read through.""" + + def __init__(self, value): + self.value = value + + def __index__(self): + return self.value + + +class Both: + """Both dunders, disagreeing, so which one is consulted is observable.""" + + def __index__(self): + return 0 # SEEK_SET + + def __int__(self): + return 2 # SEEK_END + + +class IntOnly: + def __int__(self): + return 0 + + +for factory, data in ((io.BytesIO, b"abcdefgh"), (io.StringIO, "abcdefgh")): + size = len(data) + stream = factory(data) + + # In range: rejected for what it is, not for how wide it is. The message is + # the converter's on one side and the range check's on the other, so only + # the type is compared. + raises(ValueError, lambda: stream.seek(0, 3)) + raises(ValueError, lambda: stream.seek(0, -1)) + + # Out of a C int's range. A whence of 2**32 narrowed to a C int instead + # would truncate to 0 and seek to the start; taken as a machine int it + # reaches the range check and comes back as the wrong exception type. + for whence in (2**32, -(2**32), 2**63, -(2**63) - 1): + raises(OverflowError, lambda whence=whence: stream.seek(0, whence)) + + # The whence is taken through the index protocol. Asserted by value against + # a non-empty buffer, so SEEK_SET and SEEK_END are distinguishable: reading + # `Both` through `__int__` would answer `size` here rather than 0. + assert stream.seek(0, Index(0)) == 0 + assert stream.seek(0, Index(2)) == size + assert stream.seek(0, Both()) == 0 + raises(OverflowError, lambda: stream.seek(0, Index(2**32))) + raises(ValueError, lambda: stream.seek(0, Index(3))) + + # `__int__` alone is not the index protocol, so it is not a whence. + raises(TypeError, lambda: stream.seek(0, IntOnly())) + raises(TypeError, lambda: stream.seek(0, "0")) + raises(TypeError, lambda: stream.seek(0, 0.0)) + + # The position is a C ssize_t, so one a C int could not hold is a position. + assert stream.seek(2**32) == 2**32 + raises(OverflowError, lambda: stream.seek(2**63)) + +print("OK") diff --git a/pyre/extra_tests/parity_tests/run.py b/pyre/extra_tests/parity_tests/run.py index 50d89156389..64664abf0db 100644 --- a/pyre/extra_tests/parity_tests/run.py +++ b/pyre/extra_tests/parity_tests/run.py @@ -18,6 +18,15 @@ applies to in its header (`# pyre-check: platforms=linux,darwin`) and is skipped elsewhere. +A script whose defect is only reachable under a particular runtime +configuration declares it with one or more + + # parity-env: NAME=VALUE + +lines, which are added to the environment of every runner for that script +only. Without this a script can keep passing after the shape it exercises +stops being reachable, i.e. cover nothing while still looking green. + Usage: python3 pyre/extra_tests/parity_tests/run.py [--dynasm-only|--cranelift-only] [--gc-poison] @@ -34,6 +43,7 @@ import argparse import os +import re import subprocess import sys from pathlib import Path @@ -82,6 +92,15 @@ def _scripts() -> tuple[list[Path], list[Path]]: return out, skipped +_ENV_DIRECTIVE = re.compile(r"^#\s*parity-env:\s*(\w+)=(\S*)\s*$", re.MULTILINE) + + +def _script_env(script: Path) -> dict[str, str]: + """Environment a script pins for itself with `# parity-env: NAME=VALUE`.""" + text = script.read_text(encoding="utf-8", errors="replace") + return {m.group(1): m.group(2) for m in _ENV_DIRECTIVE.finditer(text)} + + def _run(cmd: list[str], script: Path, env: dict[str, str] | None) -> tuple[bool, str]: try: proc = subprocess.run( @@ -151,9 +170,11 @@ def main() -> int: fail = 0 for script in scripts: name = script.name + pinned = _script_env(script) row: list[str] = [f" {name:<36s}"] for backend, cmd, env in runners: - ok, detail = _run(cmd, script, env) + merged = {**(env or {}), **pinned} + ok, detail = _run(cmd, script, merged or None) mark = "OK" if ok else "FAIL" row.append(f"{backend}={mark}") if not ok: diff --git a/pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py b/pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py index 0c9dd5ad4ae..f6a3a27ab30 100644 --- a/pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py +++ b/pyre/extra_tests/parity_tests/thread_start_walk_abort_no_replay.py @@ -29,8 +29,18 @@ Every acquire on the main thread is bounded so a lost release reports instead of hanging, and `die` uses `os._exit` so a failure cannot block on shutdown. + +⚠️ The `parity-env` line below is what keeps this file covering anything. All +three kept-stack decline hazards are scoped to `!ctx.vstack_valid`, so once the +callee operand-stack mirror became the default an inline sub-walk describes its +own stack and the aborting guard is never reached. Measured on a binary WITHOUT +the fix: 4/4 pass at the default setting, 4/4 fail with the mirror off (the same +deterministic round 97 either way). Drop the pin and this test goes green +against the very defect it exists to catch. """ +# parity-env: PYRE_FBW_CALLEE_VSTACK=0 + import contextvars import os import threading diff --git a/pyre/pyre-interpreter/src/baseobjspace.rs b/pyre/pyre-interpreter/src/baseobjspace.rs index bf455c379ab..e9997af60c6 100644 --- a/pyre/pyre-interpreter/src/baseobjspace.rs +++ b/pyre/pyre-interpreter/src/baseobjspace.rs @@ -13017,6 +13017,19 @@ pub fn index_int_w_preserve_negative(obj: PyObjectRef) -> Result { } } +/// The index-protocol counterpart of [`c_int_w`], for a 3.14 Argument Clinic +/// `int` parameter: the value arrives through `__index__` alone, and one +/// outside a C int's range is an OverflowError rather than a truncation. +/// +/// [`c_int_w`] cannot serve here because `gateway_int_w` is `int_w` +/// (baseobjspace.py:2043), which converts through `__int__` first — so an +/// object carrying both dunders would be read from the wrong one, and one +/// carrying only `__int__` would be accepted where 3.14 raises TypeError. +pub fn index_c_int_w(obj: PyObjectRef) -> Result { + let value = int_w(space_index(obj)?)?; + i32::try_from(value).map_err(|_| PyError::overflow_error("expected a 32-bit integer")) +} + /// `objspace.honor__builtins__` default is False — the frame builtin is /// `space.builtin`, ignoring a custom `__builtins__` in globals. The /// `pick_builtin*` family below is the `honor__builtins__=True` path, diff --git a/pyre/pyre-interpreter/src/call.rs b/pyre/pyre-interpreter/src/call.rs index 7490d526f6c..92a520adad7 100644 --- a/pyre/pyre-interpreter/src/call.rs +++ b/pyre/pyre-interpreter/src/call.rs @@ -3649,25 +3649,18 @@ fn call_metaclass_with_kwargs( kwargs: PyObjectRef, ) -> PyObjectRef { if unsafe { !pyre_object::is_type(w_metaclass) } { - // compiling.py:213-219 — `space.call_args(w_meta, Arguments(name, + // compiling.py:215-221 — `space.call_args(w_meta, Arguments(name, // bases, ns, **kwds))`; a non-type metaclass receives the - // class-definition keywords too. + // class-definition keywords too, and `call_args` + // (descroperation.py:189) takes no frame. let kwds: Vec<(Wtf8Buf, PyObjectRef)> = if unsafe { pyre_object::is_dict(kwargs) } { unsafe { pyre_object::w_dict_str_entries_wtf8(kwargs) } } else { Vec::new() }; - let frame = { - let stored = take_last_exec_ctx(); - if stored.is_null() { - std::ptr::null_mut() - } else { - unsafe { (*stored).gettopframe_raw() } - } - }; - if !kwds.is_empty() && !frame.is_null() { - return match call_with_kwargs( - unsafe { &mut *frame }, + if !kwds.is_empty() { + return match call_with_kwargs_in_ctx( + take_last_exec_ctx(), w_metaclass, &[name, bases, w_namespace_dict], &kwds, @@ -4075,26 +4068,18 @@ fn build_class_inner( // namespace. match crate::baseobjspace::getattr_str(w_metaclass, "__prepare__") { Ok(prepare) => { - // compiling.py:190-196 — call __prepare__ with the + // compiling.py:194-199 — call __prepare__ with the // class-definition keywords ('metaclass' already popped by - // the caller). + // the caller), through the frameless `space.call_args`. let prepare_kwds: Vec<(Wtf8Buf, PyObjectRef)> = match current_kwds() { Some(kw) if unsafe { pyre_object::is_dict(kw) } => unsafe { pyre_object::w_dict_str_entries_wtf8(kw) }, _ => Vec::new(), }; - let prepare_frame = { - let stored = take_last_exec_ctx(); - if stored.is_null() { - std::ptr::null_mut() - } else { - unsafe { (*stored).gettopframe_raw() } - } - }; - let ns_obj = if !prepare_kwds.is_empty() && !prepare_frame.is_null() { - call_with_kwargs( - unsafe { &mut *prepare_frame }, + let ns_obj = if !prepare_kwds.is_empty() { + call_with_kwargs_in_ctx( + take_last_exec_ctx(), prepare, &[pyre_object::w_str_new(name), bases], &prepare_kwds, @@ -4865,37 +4850,15 @@ pub(crate) fn call_init_subclass_on_bases( let w_objtype = crate::builtins::super_check(w_type, w_type)?; let w_super = pyre_object::descriptor::w_super_new(w_type, w_objtype, w_type); let w_func = crate::baseobjspace::getattr_str(w_super, "__init_subclass__")?; - // `__args__.replace_arguments([])` — keywords only, no positionals. + // typeobject.py:1025-1026 — `args = __args__.replace_arguments([])` then + // `space.call_args(w_func, args)`: keywords only, no positionals, and no + // frame, because `call_args` (descroperation.py:189) never takes one. let kwds: Vec<(Wtf8Buf, PyObjectRef)> = init_subclass_kwargs .iter() .filter(|(k, _)| unsafe { pyre_object::is_str(*k) }) .map(|(k, v)| (unsafe { pyre_object::w_str_get_wtf8(*k) }.to_owned(), *v)) .collect(); - let frame = { - let stored = take_last_exec_ctx(); - if stored.is_null() { - std::ptr::null_mut() - } else { - unsafe { (*stored).gettopframe_raw() } - } - }; - if !frame.is_null() { - call_with_kwargs(unsafe { &mut *frame }, w_func, &[], &kwds)?; - } else if kwds.is_empty() { - // No live frame to thread through call_with_kwargs (direct - // embedding entry); the bound method carries the receiver. - clear_call_error(); - let res = crate::call_function(w_func, &[]); - if res.is_null() { - if let Some(err) = take_call_error() { - return Err(err); - } - } - } else { - return Err(crate::PyError::type_error( - "__init_subclass__() takes no keyword arguments", - )); - } + call_with_kwargs_in_ctx(take_last_exec_ctx(), w_func, &[], &kwds)?; Ok(()) } diff --git a/pyre/pyre-interpreter/src/module/_io/bytesio.rs b/pyre/pyre-interpreter/src/module/_io/bytesio.rs index 48b5eeb9869..940625a0549 100644 --- a/pyre/pyre-interpreter/src/module/_io/bytesio.rs +++ b/pyre/pyre-interpreter/src/module/_io/bytesio.rs @@ -389,9 +389,14 @@ impl W_BytesIO { fn seek( &mut self, pos: PyIndexInt, - #[default(0)] whence: PyIndexInt, + #[default(0)] whence: PyIndexCInt, ) -> Result { // interp_bytesio.py:162-180 validation followed by RStringIO.seek. + // `@unwrap_spec(pos=r_longlong, whence=int)` (:162): the position is a + // long long but the whence is a C int, so one that does not fit is an + // OverflowError from the converter rather than a value the arms below + // ever see. Both come through `__index__` alone — 3.14 rejects a whence + // carrying only `__int__`, and reads one carrying both from `__index__`. self.check_closed()?; match whence { 0 if pos < 0 => { @@ -416,7 +421,7 @@ impl W_BytesIO { ))); } } - self.seek_pos(pos, whence); + self.seek_pos(pos, whence as i64); Ok(self.tell_pos()) } diff --git a/pyre/pyre-interpreter/src/module/_io/stringio.rs b/pyre/pyre-interpreter/src/module/_io/stringio.rs index fbfbfcaa0c4..0f3064407af 100644 --- a/pyre/pyre-interpreter/src/module/_io/stringio.rs +++ b/pyre/pyre-interpreter/src/module/_io/stringio.rs @@ -387,8 +387,13 @@ impl W_StringIO { self.check_closed()?; let _roots = pyre_object::gc_roots::push_roots(); let slot = self.pin_self(); + // `@unwrap_spec(pos=int, mode=int)` (:403). The whence is a C int, so + // one that does not fit is an OverflowError from the converter rather + // than a value the range check below ever sees. The position is not: + // 3.14 takes it as a `Py_ssize_t`, and `seek(2**32)` is a position it + // accepts. Both stay on the index protocol. let pos = crate::baseobjspace::index_int_w_preserve_negative(w_pos)?; - let whence = crate::baseobjspace::index_int_w_preserve_negative(w_whence)?; + let whence = crate::baseobjspace::index_c_int_w(w_whence)?; let this = Self::from_slot(slot); this.check_closed()?; if !(0..=2).contains(&whence) { diff --git a/pyre/pyre-jit/src/lib.rs b/pyre/pyre-jit/src/lib.rs index 888a679272d..4d49d53519b 100644 --- a/pyre/pyre-jit/src/lib.rs +++ b/pyre/pyre-jit/src/lib.rs @@ -67,6 +67,12 @@ pub fn fbw_diag_counter(i: usize) -> u64 { pyre_jit_trace::trace::fbw_diag::get(i) } +/// The `PYPY_GC_*` variables the collector sizes itself from, and the setter +/// for an embedder whose platform hands the process no environment to read them +/// out of. Re-exported so such a host reaches them through the crate it already +/// builds the interpreter with. +pub use majit_gc::collector::{GC_ENV_NAMES, set_supplied_env as set_gc_supplied_env}; + /// Diagnostic only: `(oldgen_total_bytes, nursery_used_bytes)` of the wasm /// backend's GC on this thread. Lets the wasm runner attribute guest /// linear-memory growth to GC-retained objects vs. host-heap allocations. diff --git a/pyre/pyre-macros/src/lib.rs b/pyre/pyre-macros/src/lib.rs index c2ee30fcbc5..07afdd187b0 100644 --- a/pyre/pyre-macros/src/lib.rs +++ b/pyre/pyre-macros/src/lib.rs @@ -538,6 +538,14 @@ fn typed_alias( crate::baseobjspace::index_int_w_preserve_negative(args[#idx])? }, ), + "PyIndexCInt" => ( + // The same parameters narrowed to a C int, as an `int` parameter + // whose callee compares against a small fixed set is. `PyCInt` + // does not serve: its converter reaches the value through + // `__int__` first, which the index protocol does not. + quote! { i32 }, + quote! { crate::baseobjspace::index_c_int_w(args[#idx])? }, + ), // Integer aliases — route through the `space.gateway_nonnegint_w` // / `space.c_*_w` converters in baseobjspace.rs so the range / sign // checks and their exception messages live in one place. diff --git a/pyre/pyre-wasm-runner/src/main.rs b/pyre/pyre-wasm-runner/src/main.rs index 6602f7f3dad..b06eb5e5c2c 100644 --- a/pyre/pyre-wasm-runner/src/main.rs +++ b/pyre/pyre-wasm-runner/src/main.rs @@ -450,6 +450,50 @@ fn run(module_path: &PathBuf, source: &str, script: &Path) -> Result { } } + // The environment the collector sizes itself from, forwarded the same way + // and for the same reason. It is not a diagnostic knob: `PYPY_GC_MIN` and + // `PYPY_GC_NURSERY` fix where the major-collection threshold falls, the + // major step arms the eval-breaker word, and every compiled loop's back edge + // polls that word through a real guard — so a guest that cannot read them + // counts a different number of guard failures than the native backends run + // beside it, from the same settings. Absent on a module predating the + // export, which then keeps its built-in defaults. + let gc_env_names = instance + .get_typed_func::<(), u64>(&mut store, "pyre_gc_env_names") + .ok(); + let set_gc_env = instance + .get_typed_func::<(u32, u32), ()>(&mut store, "pyre_set_gc_env") + .ok(); + if let (Some(names), Some(set_gc_env)) = (gc_env_names, set_gc_env) { + let packed = names.call(&mut store, ())?; + let (nptr, nlen) = ((packed >> 32) as u32, packed as u32); + let mut buf = vec![0u8; nlen as usize]; + memory.read(&store, nptr as usize, &mut buf)?; + dealloc.call(&mut store, (nptr, nlen))?; + + // `env::var`, not `var_os`: the guest parses these as numbers, so a + // value that does not decode could not have been one and is left unset + // exactly as it would be natively. + let blob = String::from_utf8_lossy(&buf) + .split('\0') + .filter(|name| !name.is_empty()) + .filter_map(|name| { + std::env::var(name) + .ok() + .map(|value| format!("{name}={value}")) + }) + .collect::>() + .join("\0"); + + let blen = blob.len() as u32; + if blen != 0 { + let p = alloc.call(&mut store, blen)?; + memory.write(&mut store, p as usize, blob.as_bytes())?; + set_gc_env.call(&mut store, (p, blen))?; + dealloc.call(&mut store, (p, blen))?; + } + } + // Name the script so the guest compiles it under its real path: that is // what a traceback prints, what its source-line lookup reads back through // `pyre_host.host_read`, and the directory that heads `sys.path`. Absent diff --git a/pyre/pyre-wasm/src/lib.rs b/pyre/pyre-wasm/src/lib.rs index 810421f137e..fa1c9bf1a64 100644 --- a/pyre/pyre-wasm/src/lib.rs +++ b/pyre/pyre-wasm/src/lib.rs @@ -793,9 +793,9 @@ pub fn run_python(source: &str) -> String { /// and `pyre_exit_code()` → the status to exit with. /// /// `pyre_set_script_path(ptr, len)` may precede step 2 to name the file the -/// source came from, and `pyre_set_launch_env(ptr, len)` to supply the -/// environment the launcher options resolve against — the guest has none of -/// its own. +/// source came from, `pyre_set_launch_env(ptr, len)` to supply the environment +/// the launcher options resolve against, and `pyre_set_gc_env(ptr, len)` the +/// one the collector sizes itself from — the guest has none of its own. #[cfg(feature = "wasm-host")] mod host_abi { use super::run_python_impl; @@ -926,6 +926,41 @@ mod host_abi { ) } + /// Supply the `PYPY_GC_*` variables the collector sizes itself from, in the + /// same NUL-separated `NAME=VALUE` form as [`pyre_set_launch_env`]. The + /// guest has no environment, so without this the nursery, the major-collection + /// threshold and the growth rates all take their built-in defaults however + /// the host was configured — and the threshold decides when the collector + /// arms the eval-breaker word, which every compiled loop's back edge polls + /// through a real guard. `pyre_gc_env_names` lists the names that are read. + /// + /// The collector reads them once, when it is built, which the first + /// allocation does — so this must precede `pyre_run_python`, not merely the + /// script's own first line. + #[unsafe(no_mangle)] + pub extern "C" fn pyre_set_gc_env(ptr: *const u8, len: usize) { + let Some(blob) = guest_str(ptr, len) else { + return; + }; + let entries = blob + .split('\0') + .filter_map(|record| { + let (name, value) = record.split_once('=')?; + (!name.is_empty()).then(|| (name.to_string(), value.to_string())) + }) + .collect(); + pyre_jit::set_gc_supplied_env(entries); + } + + /// The names [`pyre_set_gc_env`] is worth being given, as NUL-separated + /// records in a buffer the host must free with `pyre_dealloc`. Returned for + /// the same reason as [`pyre_launch_env_names`]: so a host does not keep its + /// own copy of the list in step with the collector. + #[unsafe(no_mangle)] + pub extern "C" fn pyre_gc_env_names() -> u64 { + pack_into_guest(pyre_jit::GC_ENV_NAMES.join("\0").into_bytes()) + } + /// Set `-P` / PYTHONSAFEPATH for the next `pyre_run_python`, suppressing the /// `sys.path[0]` entry `pyre_set_script_path` would otherwise seed. Kept for /// a host predating [`pyre_set_launch_env`], which carries the same flag