-
Notifications
You must be signed in to change notification settings - Fork 19
jit(aarch64): close the loop gap vs PyPy in the backend; fix NaN float compares on both dynasm arches #859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
890083f
6f8d984
f49f485
57614fa
a9595a9
75a9429
21ce7bf
0a3200c
beb210b
f5844c7
30fdb17
b34deab
a8be8a3
9cd8131
79cea09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,9 +49,34 @@ pub fn register_force_vref_hook(f: ForceVRefFn) { | |
| let _ = FORCE_VREF_HOOK.set(f); | ||
| } | ||
|
|
||
| /// The frame a chain slot NAMES, read WITHOUT forcing — | ||
| /// `virtualref.py force_virtual`'s trailing `return vref.forced`. | ||
| /// | ||
| /// Exact for the whole recording walk: `virtual_ref_during_tracing` writes | ||
| /// `forced = real_object` at allocation and only `continue_tracing` ever | ||
| /// rewrites it. Null when the vref is still virtual with nothing | ||
| /// materialized — a live compiled frame — so callers must read that as | ||
| /// "names no reachable frame", never as a match. | ||
| /// | ||
| /// For identity tests only, never for handing a frame to application code. | ||
| /// Forcing would be wrong here, not merely expensive: a live vref carries | ||
| /// `TOKEN_TRACING_RESCALL` across a residual, `force_virtual` clears it, and | ||
| /// that cleared token is the one marker `tracing_after_residual_call` reads as | ||
| /// "the callee forced this vref". A reader that forced would report its own | ||
| /// read as a callee escape. | ||
| #[inline] | ||
| pub fn vref_referent(ptr: *mut PyFrame) -> *mut PyFrame { | ||
| if unsafe { majit_metainterp::virtualref::ptr_is_virtual_ref(ptr as *const u8) } { | ||
| unsafe { majit_metainterp::virtualref::vref_forced(ptr as *const u8) as *mut PyFrame } | ||
| } else { | ||
| ptr | ||
| } | ||
| } | ||
|
|
||
| /// Force a vref stored in the frame chain (`topframeref` / `f_backref`). | ||
| /// `virtualref.py:135`: `if inst.typeptr != jit_virtual_ref_vtable: return inst` | ||
| /// (the pointer already *is* the frame) else materialize via `force_virtual`. | ||
| /// `virtualref.py force_virtual_if_necessary`: `if inst.typeptr != | ||
| /// jit_virtual_ref_vtable: return inst` (the pointer already *is* the frame) | ||
| /// else materialize via `force_virtual`. | ||
| #[inline] | ||
| pub(crate) fn force_vref(ptr: *mut PyFrame) -> *mut PyFrame { | ||
| if unsafe { majit_metainterp::virtualref::ptr_is_virtual_ref(ptr as *const u8) } { | ||
|
|
@@ -907,7 +932,7 @@ impl ExecutionContext { | |
| self.w_tracefunc = pyre_object::PY_NULL; | ||
| } else { | ||
| self.force_all_frames(false); | ||
| // executioncontext.py:296-298 — increase the JIT's | ||
| // executioncontext.py settrace — increase the JIT's | ||
| // trace_limit when a tracefunc is installed; tracing | ||
| // generates a ton of extra ops per bytecode. | ||
| crate::call::set_jit_param("trace_limit", 10000); | ||
|
|
@@ -918,7 +943,7 @@ impl ExecutionContext { | |
| self.w_tracefunc | ||
| } | ||
|
|
||
| /// pypy/interpreter/executioncontext.py:303-310 setprofile. | ||
| /// `executioncontext.py setprofile`. | ||
| pub fn setprofile(&mut self, w_func: PyObjectRef) -> Result<(), crate::PyError> { | ||
| if w_func.is_null() || w_func == pyre_object::w_none() { | ||
| self.profilefunc = None; | ||
|
|
@@ -929,19 +954,19 @@ impl ExecutionContext { | |
| } | ||
| } | ||
|
|
||
| /// pypy/interpreter/executioncontext.py:312-313 getprofile. | ||
| /// `executioncontext.py getprofile`. | ||
| pub fn getprofile(&self) -> PyObjectRef { | ||
| self.w_profilefuncarg | ||
| } | ||
|
|
||
| /// pypy/interpreter/executioncontext.py:315-321 setllprofile. | ||
| /// `executioncontext.py setllprofile`. | ||
| pub fn setllprofile( | ||
| &mut self, | ||
| func: Option<ProfileFunc>, | ||
| w_arg: PyObjectRef, | ||
| ) -> Result<(), crate::PyError> { | ||
| if func.is_some() { | ||
| // executioncontext.py:317-318 `if w_arg is None: raise | ||
| // executioncontext.py setllprofile: `if w_arg is None: raise | ||
| // ValueError("Cannot call setllprofile with real None")`. | ||
| // The check is against RPython-level None (== null in pyre); | ||
| // Python-level `w_none()` (`space.w_None`) is a valid user | ||
|
|
@@ -958,9 +983,24 @@ impl ExecutionContext { | |
| Ok(()) | ||
| } | ||
|
|
||
| /// `executioncontext.py force_all_frames` — "force" every frame in the | ||
| /// sense of the JIT, so one that is running in assembler fails its next | ||
| /// `GUARD_NOT_FORCED` and falls back to interpreted execution, where the | ||
| /// freshly installed trace / profile callback is honoured. | ||
| /// | ||
| /// Upstream gets that effect from the walk itself: `f_backref` holds a | ||
| /// `jit.virtual_ref`, so `getnextframe_nohidden`'s `frame.f_backref()` is a | ||
| /// `jit_force_virtual`, and `virtualref.force_virtual` runs | ||
| /// `ResumeGuardForcedDescr.force_now` on a token that still names a live | ||
| /// JIT frame. Pyre's walk calls [`force_vref`] at the same points, but | ||
| /// nothing stores a `JitVirtualRef` in the chain yet, so it is the identity | ||
| /// and the walk forces nothing. Until the tracer emits `VIRTUAL_REF` at | ||
| /// the inline push, this consumer — whose whole purpose is the force — | ||
| /// states it directly. | ||
| pub fn force_all_frames(&mut self, is_being_profiled: bool) { | ||
| let mut frame = self.gettopframe_nohidden(); | ||
| while !frame.is_null() { | ||
| force_frame(frame); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When AGENTS.md reference: AGENTS.md:L194-L196 Useful? React with 👍 / 👎. |
||
| if is_being_profiled { | ||
| unsafe { | ||
| (*frame).getorcreatedebug(-1).is_being_profiled = true; | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1479,12 +1479,10 @@ pub fn jit_trace_fnaddrs() -> Vec<(&'static str, i64)> { | |||||||||||||||
| // store leaves to native ops, leaving `list_write_barrier(l)` as a | ||||||||||||||||
| // residual call. Register it so the codewriter resolves the residual to a | ||||||||||||||||
| // runtime-patchable address instead of a `symbolic_fnaddr_for_path` hash | ||||||||||||||||
| // the inline sub-walk must decline. The address is also what the walker | ||||||||||||||||
| // matches on to drop the residual entirely when the backend GC rewrite | ||||||||||||||||
| // already covers the store (`FbwWalkMode::append_inplace_wb_covered`); | ||||||||||||||||
| // with an off-GC ItemsBlock the residual stays, because there the | ||||||||||||||||
| // collector reaches the block's slots only through the remembered | ||||||||||||||||
| // `W_ListObject`. | ||||||||||||||||
| // the inline sub-walk must decline. The residual barrier remembers the | ||||||||||||||||
| // enclosing `W_ListObject`, whose trace reaches every item slot, and is | ||||||||||||||||
| // the only thing keeping an appended `old -> young` element reachable | ||||||||||||||||
| // across a minor collection. | ||||||||||||||||
|
Comment on lines
+1482
to
+1485
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win Clarify the barrier’s role in reachability. The write barrier does not itself keep the appended element reachable; the list slot does. Its role is to record the old Proposed wording- // the inline sub-walk must decline. The residual barrier remembers the
- // enclosing `W_ListObject`, whose trace reaches every item slot, and is
- // the only thing keeping an appended `old -> young` element reachable
- // across a minor collection.
+ // the inline sub-walk must decline. The residual barrier remembers the
+ // enclosing `W_ListObject`, whose trace reaches every item slot, so the
+ // collector scans an appended `old -> young` edge during minor collection.📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| push_alias_pair( | ||||||||||||||||
| &mut entries, | ||||||||||||||||
| "pyre_object::listobject::list_write_barrier", | ||||||||||||||||
|
|
||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Preserve unordered-NaN semantics before fusing
FloatGt/FloatGe.AArch64
FCMPon NaN leavesN=0, Z=0, C=1, V=1; rawb.gtandb.geboth succeed becauseN == V. PublishingCC_G/CC_GEthrough the sentinel therefore makes a following guard incorrectly pass for NaN. Add unordered-aware flag handling, or exclude these operations from CC fusion until that handling exists.majit/majit-backend-dynasm/src/regalloc.rs#L4135-L4138: retain a NaN-correct materialization path or use an unordered-aware fused branch.majit/majit-backend-dynasm/src/regalloc.rs#L4156-L4158: apply the same handling to the J2 path.📍 Affects 1 file
majit/majit-backend-dynasm/src/regalloc.rs#L4135-L4138(this comment)majit/majit-backend-dynasm/src/regalloc.rs#L4156-L4158🤖 Prompt for AI Agents