diff --git a/majit/majit-metainterp/src/jitdriver.rs b/majit/majit-metainterp/src/jitdriver.rs index 8ad2e3309c2..954147de673 100644 --- a/majit/majit-metainterp/src/jitdriver.rs +++ b/majit/majit-metainterp/src/jitdriver.rs @@ -466,6 +466,31 @@ pub fn no_bridge_enabled() -> bool { static FLAG: std::sync::OnceLock = std::sync::OnceLock::new(); *FLAG.get_or_init(|| std::env::var_os("MAJIT_NO_BRIDGE").is_some()) } +/// `MAJIT_MAX_BRIDGES=N` (diagnostic): allow the first N bridge compilations +/// and behave as `MAJIT_NO_BRIDGE` from then on. Bisecting N names the bridge +/// whose compilation first produces a wrong value, at seconds per run rather +/// than a rebuild per arm. Consumes fuel only when the rest of `should_bridge` +/// already held, so the count is bridges actually taken — place it last in the +/// `&&` chain. `MAJIT_BRIDGE_FUEL_LOG` reports each one taken. +fn bridge_fuel_take() -> bool { + static LIMIT: std::sync::OnceLock> = std::sync::OnceLock::new(); + let Some(limit) = *LIMIT.get_or_init(|| { + std::env::var("MAJIT_MAX_BRIDGES") + .ok() + .and_then(|v| v.parse().ok()) + }) else { + return true; + }; + static USED: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0); + let n = USED.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + if n >= limit { + return false; + } + if std::env::var_os("MAJIT_BRIDGE_FUEL_LOG").is_some() { + eprintln!("@@@FUEL bridge #{n}"); + } + true +} fn guardlog_enabled() -> bool { static FLAG: std::sync::OnceLock = std::sync::OnceLock::new(); *FLAG.get_or_init(|| std::env::var_os("MAJIT_GUARDLOG").is_some()) @@ -4413,7 +4438,8 @@ impl JitDriver { // pending-field prologue (resume.py:993-1007). let should_bridge = must_compile && !majit_metainterp::MetaInterp::::stack_almost_full() - && !no_bridge_enabled(); + && !no_bridge_enabled() + && bridge_fuel_take(); // compile.py:710 recovery_layout header_pc parity: // guard resume_pc comes from the guard's recovery metadata. @@ -5715,7 +5741,8 @@ impl JitDriver { // every bridge. let should_bridge = must_compile && !majit_metainterp::MetaInterp::::stack_almost_full() - && !no_bridge_enabled(); + && !no_bridge_enabled() + && bridge_fuel_take(); // Same `@@@GUARD` line the sibling loops emit. Without it this loop — // the one pyre reaches — had no per-guard-failure trace at all, so @@ -6860,7 +6887,8 @@ impl JitDriver { // resume defects from the blackhole path. let should_bridge = must_compile && !majit_metainterp::MetaInterp::::stack_almost_full() - && !no_bridge_enabled(); + && !no_bridge_enabled() + && bridge_fuel_take(); // compile.py:710 recovery_layout header_pc parity: // guard resume_pc comes from the guard's recovery metadata. diff --git a/majit/majit-translate/src/model.rs b/majit/majit-translate/src/model.rs index 81b97a9c711..3f10c5c2458 100644 --- a/majit/majit-translate/src/model.rs +++ b/majit/majit-translate/src/model.rs @@ -4053,13 +4053,30 @@ pub fn prune_dead_phis(graph: &mut FunctionGraph) { // Step 6: trim Link.args at indices whose target inputarg is dead. // `simplify.py:512-516`. Walk in reverse so removals don't shift // surviving indices. + // + // The scope is the *target*'s reachability, not the source's. Step 7 + // trims the inputargs of every reachable block, and a block the + // reachability walk excludes — an orphan `eliminate_empty_blocks` + // bypassed, or one of the merge blocks jtransform leaves whose + // inputargs are phi targets rather than parameters — can still name a + // reachable block as its link target. Skipping that link because its + // own block is unreachable breaks `len(link.args) == + // len(link.target.inputargs)` with no diagnostic: + // `remove_duplicate_inputargs` reads each column by index across every + // incoming link, so the untrimmed one contributes the value one slot + // over, and the union-find merges two variables that are not the same + // value — a rename applied to the whole graph. Reachability is closed + // under exits, so this only ever adds links, never drops one. + // + // Upstream cannot reach this: `graph.iterblocks()` *is* its block list, + // so an unreachable block is not in `blocks` and has no link to skip. for block_idx in 0..graph.blocks.len() { - if !reachable.contains(&graph.blocks[block_idx].id) { - continue; - } let exits_len = graph.blocks[block_idx].exits.len(); for exit_idx in 0..exits_len { let target = graph.blocks[block_idx].exits[exit_idx].target; + if !reachable.contains(&target) { + continue; + } let target_iargs: Vec = { let &i = block_index .get(&target) @@ -6312,6 +6329,60 @@ mod tests { assert!(!has_phi_op, "orphan phi `OpKind::Input` must be dropped"); } + #[test] + fn prune_dead_phis_trims_an_unreachable_predecessors_link_with_the_target() { + // entry ─┐ + // ├→ merge(inputargs [dead, live]) → returnblock(live) + // orphan ─┘ + // + // `orphan` has no predecessor and is not a calling-convention entry — + // its inputarg is a phi target with no backing `OpKind::Input`, the + // shape `jtransform` leaves behind — so the reachability walk excludes + // it. Steps 6 and 7 both walk `reachable`, so `merge`'s dead inputarg + // goes while `orphan`'s link keeps both args, and every later consumer + // that zips a link against its target's inputargs is then off by one. + let mut graph = FunctionGraph::new("test"); + let entry = graph.startblock; + let e_dead = graph.push_op_var(entry, OpKind::ConstInt(1), true).unwrap(); + let e_live = graph.push_op_var(entry, OpKind::ConstInt(2), true).unwrap(); + + let merge = graph.create_block(); + install_phi(&mut graph, merge, "dead"); + let live_phi = install_phi(&mut graph, merge, "live"); + graph.set_goto(entry, merge, vec![e_dead, e_live.clone()]); + graph.set_return(merge, Some(live_phi)); + + let orphan = graph.create_block(); + let stranded = graph.alloc_value_var(); + graph.push_inputarg_var(orphan, stranded); + let o_dead = graph + .push_op_var(orphan, OpKind::ConstInt(3), true) + .unwrap(); + let o_live = graph + .push_op_var(orphan, OpKind::ConstInt(4), true) + .unwrap(); + graph.set_goto(orphan, merge, vec![o_dead, o_live.clone()]); + + prune_dead_phis(&mut graph); + + assert_eq!( + graph.block(merge).inputargs.len(), + 1, + "the unread phi column is dropped" + ); + assert_eq!( + graph.block(entry).exits[0].args, + vec![LinkArg::Value(e_live)], + "the reachable predecessor keeps the live column" + ); + assert_eq!( + graph.block(orphan).exits[0].args, + vec![LinkArg::Value(o_live)], + "an unreachable predecessor's link must be trimmed with its target, \ + not left naming the column that was dropped" + ); + } + #[test] fn prune_dead_phis_retains_live_single_source_phi_pending_ssa_to_ssi() { // entry -> merge(phi 'x' read by a BinOp whose result is the diff --git a/pyre/cpython_tests/baseline.json b/pyre/cpython_tests/baseline.json index 0bd680b4097..5eb109e5857 100644 --- a/pyre/cpython_tests/baseline.json +++ b/pyre/cpython_tests/baseline.json @@ -47,7 +47,7 @@ "dynasm": "IMPORTERROR" }, "test.test_array": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_asdl_parser": { "dynasm": "IMPORTERROR" @@ -64,7 +64,7 @@ "reason": "stalls in about one arm in four, at a test that wanders" }, "test.test_atexit": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_audit": { "dynasm": "IMPORTERROR" @@ -74,10 +74,10 @@ "dynasm": "PASS" }, "test.test_base64": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_baseexception": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_bdb": { "dynasm": "IMPORTERROR" @@ -128,7 +128,7 @@ "dynasm": "PASS" }, "test.test_calendar": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_call": { "cranelift": "PASS", @@ -156,7 +156,7 @@ "dynasm": "IMPORTERROR" }, "test.test_cmd": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_cmd_line": { "dynasm": "IMPORTERROR" @@ -239,13 +239,13 @@ "dynasm": "PASS" }, "test.test_complex": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_concurrent_futures": { "dynasm": "IMPORTERROR" }, "test.test_configparser": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_contains": { "cranelift": "PASS", @@ -270,7 +270,7 @@ "dynasm": "PASS" }, "test.test_coroutines": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_cppext": { "dynasm": "SKIP", @@ -283,7 +283,7 @@ "dynasm": "IMPORTERROR" }, "test.test_csv": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_ctypes": { "dynasm": "CRASH" @@ -298,7 +298,7 @@ "dynasm": "PASS" }, "test.test_dbm": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_dbm_dumb": { "cranelift": "PASS", @@ -314,7 +314,7 @@ "dynasm": "CRASH" }, "test.test_decimal": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_decorators": { "cranelift": "PASS", @@ -325,28 +325,28 @@ "dynasm": "PASS" }, "test.test_deque": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_descr": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_descrtut": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_devpoll": { "dynasm": "IMPORTERROR" }, "test.test_dict": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_dictcomps": { "dynasm": "FAIL" }, "test.test_dictviews": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_difflib": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_dis": { "dynasm": "SKIP", @@ -356,7 +356,7 @@ "dynasm": "IMPORTERROR" }, "test.test_docxmlrpc": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_dtrace": { "cranelift": "PASS", @@ -381,7 +381,7 @@ "reason": "needs embedded CPython" }, "test.test_ensurepip": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_enum": { "dynasm": "PASS" @@ -408,7 +408,7 @@ "dynasm": "PASS" }, "test.test_exception_hierarchy": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_exception_variations": { "cranelift": "PASS", @@ -437,13 +437,13 @@ "dynasm": "PASS" }, "test.test_filecmp": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_fileinput": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_fileio": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_fileutils": { "dynasm": "IMPORTERROR" @@ -452,7 +452,7 @@ "dynasm": "CRASH" }, "test.test_float": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_flufl": { "dynasm": "FAIL" @@ -465,10 +465,10 @@ "dynasm": "FAIL" }, "test.test_format": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_fractions": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_frame": { "dynasm": "SKIP", @@ -488,7 +488,7 @@ "dynasm": "IMPORTERROR" }, "test.test_funcattrs": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_functools": { "dynasm": "PASS" @@ -514,7 +514,7 @@ "dynasm": "IMPORTERROR" }, "test.test_genericalias": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_genericclass": { "cranelift": "PASS", @@ -527,7 +527,7 @@ "dynasm": "IMPORTERROR" }, "test.test_getopt": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_getpass": { "dynasm": "IMPORTERROR" @@ -539,7 +539,7 @@ "dynasm": "IMPORTERROR" }, "test.test_glob": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_global": { "cranelift": "PASS", @@ -549,16 +549,16 @@ "dynasm": "IMPORTERROR" }, "test.test_graphlib": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_grp": { "dynasm": "FAIL" }, "test.test_gzip": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_hash": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_hashlib": { "dynasm": "IMPORTERROR" @@ -574,10 +574,10 @@ "dynasm": "PASS" }, "test.test_htmlparser": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_http_cookiejar": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_http_cookies": { "dynasm": "IMPORTERROR" @@ -630,17 +630,17 @@ "dynasm": "PASS" }, "test.test_isinstance": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_iter": { - "dynasm": "TIMEOUT" + "dynasm": "PASS" }, "test.test_iterlen": { "cranelift": "PASS", "dynasm": "PASS" }, "test.test_itertools": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_json": { "dynasm": "IMPORTERROR" @@ -664,10 +664,10 @@ "dynasm": "IMPORTERROR" }, "test.test_linecache": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_list": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_listcomps": { "dynasm": "IMPORTERROR" @@ -677,13 +677,13 @@ "dynasm": "PASS" }, "test.test_locale": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_logging": { "dynasm": "IMPORTERROR" }, "test.test_long": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_longexp": { "cranelift": "PASS", @@ -706,10 +706,10 @@ "dynasm": "PASS" }, "test.test_memoryio": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_memoryview": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_metaclass": { "dynasm": "IMPORTERROR" @@ -761,7 +761,7 @@ "dynasm": "IMPORTERROR" }, "test.test_nturl2path": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_numeric_tower": { "cranelift": "PASS", @@ -816,7 +816,7 @@ "dynasm": "PASS" }, "test.test_pep646_syntax": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_perf_profiler": { "dynasm": "IMPORTERROR" @@ -825,7 +825,7 @@ "dynasm": "IMPORTERROR" }, "test.test_pickle": { - "dynasm": "TIMEOUT" + "dynasm": "PASS" }, "test.test_picklebuffer": { "dynasm": "PASS" @@ -896,7 +896,7 @@ "dynasm": "FAIL" }, "test.test_py_compile": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_pyclbr": { "dynasm": "IMPORTERROR" @@ -911,7 +911,7 @@ "dynasm": "FAIL" }, "test.test_queue": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_quopri": { "cranelift": "PASS", @@ -921,10 +921,10 @@ "dynasm": "FAIL" }, "test.test_random": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_range": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_re": { "cranelift": "PASS", @@ -943,25 +943,25 @@ "dynasm": "IMPORTERROR" }, "test.test_reprlib": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_resource": { "dynasm": "IMPORTERROR" }, "test.test_richcmp": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_rlcompleter": { "dynasm": "IMPORTERROR" }, "test.test_robotparser": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_runpy": { "dynasm": "PASS" }, "test.test_sax": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_sched": { "cranelift": "PASS", @@ -971,7 +971,7 @@ "dynasm": "CRASH" }, "test.test_script_helper": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_secrets": { "dynasm": "IMPORTERROR" @@ -980,16 +980,16 @@ "dynasm": "FAIL" }, "test.test_selectors": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_set": { "dynasm": "CRASH" }, "test.test_setcomps": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_shelve": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_shlex": { "cranelift": "PASS", @@ -1005,7 +1005,7 @@ "dynasm": "IMPORTERROR" }, "test.test_slice": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_smtplib": { "dynasm": "IMPORTERROR" @@ -1017,7 +1017,7 @@ "dynasm": "IMPORTERROR" }, "test.test_socketserver": { - "dynasm": "TIMEOUT" + "dynasm": "PASS" }, "test.test_sort": { "dynasm": "CRASH" @@ -1045,23 +1045,23 @@ "dynasm": "IMPORTERROR" }, "test.test_str": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_strftime": { "cranelift": "PASS", "dynasm": "PASS" }, "test.test_string": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_string_literals": { "dynasm": "FAIL" }, "test.test_stringprep": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_strptime": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_strtod": { "cranelift": "PASS", @@ -1071,7 +1071,7 @@ "dynasm": "IMPORTERROR" }, "test.test_structseq": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_subclassinit": { "cranelift": "PASS", @@ -1085,13 +1085,13 @@ "dynasm": "PASS" }, "test.test_super": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_support": { "dynasm": "IMPORTERROR" }, "test.test_symtable": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_syntax": { "dynasm": "IMPORTERROR" @@ -1111,7 +1111,7 @@ "dynasm": "IMPORTERROR" }, "test.test_syslog": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_tabnanny": { "cranelift": "PASS", @@ -1134,7 +1134,7 @@ "dynasm": "PASS" }, "test.test_thread": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_thread_local_bytecode": { "dynasm": "IMPORTERROR" @@ -1147,7 +1147,7 @@ "dynasm": "IMPORTERROR" }, "test.test_threading_local": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_threadsignals": { "dynasm": "IMPORTERROR" @@ -1169,7 +1169,7 @@ "dynasm": "IMPORTERROR" }, "test.test_tomllib": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_tools": { "dynasm": "SKIP", @@ -1195,10 +1195,10 @@ "dynasm": "IMPORTERROR" }, "test.test_tty": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_tuple": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_turtle": { "dynasm": "IMPORTERROR" @@ -1239,14 +1239,14 @@ "dynasm": "PASS" }, "test.test_unicode_file": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_unicode_file_functions": { "cranelift": "PASS", "dynasm": "PASS" }, "test.test_unicode_identifiers": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_unicodedata": { "dynasm": "IMPORTERROR" @@ -1255,7 +1255,7 @@ "dynasm": "IMPORTERROR" }, "test.test_univnewlines": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_unpack": { "dynasm": "IMPORTERROR" @@ -1267,13 +1267,13 @@ "dynasm": "IMPORTERROR" }, "test.test_urllib": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_urllib2": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_urllib2_localnet": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_urllib2net": { "dynasm": "IMPORTERROR" @@ -1283,20 +1283,20 @@ "dynasm": "PASS" }, "test.test_urllibnet": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_urlparse": { "cranelift": "PASS", "dynasm": "PASS" }, "test.test_userdict": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_userlist": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_userstring": { - "dynasm": "FAIL" + "dynasm": "PASS" }, "test.test_utf8_mode": { "dynasm": "IMPORTERROR" @@ -1306,7 +1306,7 @@ "dynasm": "PASS" }, "test.test_uuid": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_venv": { "dynasm": "IMPORTERROR" @@ -1321,16 +1321,16 @@ "dynasm": "IMPORTERROR" }, "test.test_wave": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_weakref": { "dynasm": "IMPORTERROR" }, "test.test_weakset": { - "dynasm": "CRASH" + "dynasm": "PASS" }, "test.test_webbrowser": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_winapi": { "dynasm": "IMPORTERROR" @@ -1353,7 +1353,7 @@ "dynasm": "IMPORTERROR" }, "test.test_wsgiref": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_xml_dom_minicompat": { "cranelift": "PASS", @@ -1386,7 +1386,7 @@ "dynasm": "CRASH" }, "test.test_zipapp": { - "dynasm": "IMPORTERROR" + "dynasm": "PASS" }, "test.test_zipfile": { "dynasm": "IMPORTERROR" diff --git a/pyre/pyre-interpreter/src/pyframe.rs b/pyre/pyre-interpreter/src/pyframe.rs index de98a529fb2..5bb186e1893 100644 --- a/pyre/pyre-interpreter/src/pyframe.rs +++ b/pyre/pyre-interpreter/src/pyframe.rs @@ -41,10 +41,21 @@ macro_rules! locals_w { } /// Mutably borrow a frame's `locals_cells_stack_w` array. See [`locals_w!`]. +/// +/// The `&mut *` on the receiver is load-bearing: the field it projects is a +/// raw pointer, which reads fine through a shared `&PyFrame`, so without it a +/// safe caller holding a shared frame could mint `&mut` to the array. The +/// accessor this replaced took `&mut self` and this restores that requirement. +/// +/// It does not bound the result's lifetime — `&mut *ptr` has an unconstrained +/// one — so two overlapping calls still produce aliasing the borrow checker +/// cannot see. Tying the lifetime needs a function signature to tie it to, +/// which is the accessor form that puts the `getfield` in its own graph and +/// defeats the pairing described on [`locals_w!`]. #[macro_export] macro_rules! locals_w_mut { ($frame:expr) => { - (unsafe { &mut *$frame.locals_cells_stack_w }) + (unsafe { &mut *(&mut *$frame).locals_cells_stack_w }) }; }