1717#include " cinderx/Jit/deopt.h"
1818#include " cinderx/Jit/frame_header.h"
1919#include " cinderx/Jit/gen_data_footer.h"
20- #include " cinderx/Jit/threaded_compile.h"
21- #if defined(CINDER_X86_64)
20+ #include " cinderx/Jit/stack_walk.h"
2221#include " cinderx/Jit/symbolizer.h"
23- #endif
22+ #include " cinderx/Jit/threaded_compile.h "
2423#include " cinderx/UpstreamBorrow/borrowed.h"
2524#include " cinderx/module_state.h"
2625
26+ #include < csignal>
27+ #include < cstring>
2728#include < optional>
29+ #include < string>
2830
2931namespace cinderx ::jit {
3032
@@ -91,7 +93,6 @@ bool isInlined(_PyInterpreterFrame* frame) {
9193 return isInlinedFrame (frame);
9294}
9395
94- #if defined(CINDER_X86_64)
9596// Return the base of the stack frame given its frame.
9697uintptr_t getFrameBaseFromOnStackFrame (_PyInterpreterFrame* frame) {
9798 // The frame is embedded in the frame header at the beginning of the
@@ -100,59 +101,108 @@ uintptr_t getFrameBaseFromOnStackFrame(_PyInterpreterFrame* frame) {
100101 sizeof (PyObject*) * _PyFrame_GetCode (frame)->co_framesize ;
101102}
102103
103- uintptr_t getIP (_PyInterpreterFrame* frame, int frame_size) {
104104#ifdef ENABLE_LIGHTWEIGHT_FRAMES
105- JIT_CHECK (isJitFrame (frame), " frame not executed by the JIT" );
105+
106+ #if defined(CINDER_X86_64)
107+
108+ const void ** getIPStackAddr (_PyInterpreterFrame* frame, int frame_size) {
106109 uintptr_t frame_base;
107110 if (isGeneratorFrame (frame)) {
111+ // A running generator spills to the heap but still executes on the real
112+ // stack, so its calls push return addresses relative to the frame pointer
113+ // the resume function was entered with.
108114 PyGenObject* gen = _PyGen_GetGeneratorFromFrame (frame);
109- auto footer = jitGenDataFooter (gen);
110- if (footer->yieldPoint == nullptr ) {
111- // The generator is running.
112- // On x86, we read the return address from a fixed offset on the real
113- // stack relative to the resume function's RBP.
114- frame_base = footer->originalFramePointer ;
115- } else {
116- // The generator is suspended.
117- return footer->yieldPoint ->resumeTarget ();
118- }
115+ frame_base = jitGenDataFooter (gen)->originalFramePointer ;
119116 } else {
120117 frame_base = getFrameBaseFromOnStackFrame (frame);
121118 }
122- // Read the saved IP from the stack.
123- // On x86, `call` pushes the return address on the stack at a fixed
124- // location relative to the caller's frame pointer.
125- uintptr_t ip;
126- auto saved_ip =
127- reinterpret_cast <uintptr_t *>(frame_base - frame_size - kPointerSize );
128- memcpy (&ip, saved_ip, kPointerSize );
129- return ip;
130- #else
131- throw std::runtime_error{" getIP: Lightweight frames are not supported" };
132- #endif
119+ // `call` pushes the return address immediately below the unit's stack frame.
120+ return reinterpret_cast <const void **>(frame_base - frame_size - kPointerSize );
121+ }
122+
123+ #elif defined(CINDER_AARCH64)
124+
125+ // `bl` leaves the return address in LR and the callee spills it into its own
126+ // frame record, so there is no fixed location relative to the unit's frame to
127+ // read it back from. Walk the native stack the unit lives on instead, looking
128+ // for the frame whose caller is the unit.
129+ const void ** getIPStackAddr (_PyInterpreterFrame* frame, int ) {
130+ // The value the frame-pointer register holds while the unit runs. Resumed
131+ // generators point it at their heap-allocated data so that spills survive
132+ // suspension; every other unit leaves it at the base of the stack frame the
133+ // interpreter frame is embedded in.
134+ const StackFrame* unit_frame;
135+ if (isGeneratorFrame (frame)) {
136+ PyGenObject* gen = _PyGen_GetGeneratorFromFrame (frame);
137+ unit_frame = reinterpret_cast <const StackFrame*>(jitGenDataFooter (gen));
138+ } else {
139+ unit_frame = reinterpret_cast <const StackFrame*>(
140+ getFrameBaseFromOnStackFrame (frame));
141+ }
142+
143+ const void ** result = nullptr ;
144+ auto visit = [&](const void * frame_pointer, const void * pc) {
145+ auto record =
146+ const_cast <StackFrame*>(static_cast <const StackFrame*>(frame_pointer));
147+ if (record->frame_pointer == unit_frame) {
148+ // This frame belongs to the unit's callee, which saved the address the
149+ // unit resumes at.
150+ result = &record->return_address ;
151+ return false ;
152+ }
153+ // Keep going as long as we aren't at the running function who would
154+ // have no return address.
155+ return record != unit_frame;
156+ };
157+
158+ PyThreadState* owner = jitFrameGetHeader (frame)->tstate ;
159+ JIT_CHECK (owner != nullptr , " JIT frame is not associated with a thread" );
160+
161+ FreeThreadedJITEntrypointGuard guard;
162+ StackWalk walker;
163+ walker.walk (owner, visit);
164+ return result;
133165}
166+
167+ #else
168+ CINDER_UNSUPPORTED
134169#endif
135170
136- void setIP (
137- [[maybe_unused]] _PyInterpreterFrame* frame,
138- [[maybe_unused]] int frame_size,
139- [[maybe_unused]] uintptr_t new_ip) {
140- #if defined(__x86_64__) && defined(ENABLE_LIGHTWEIGHT_FRAMES)
171+ uintptr_t getIP (_PyInterpreterFrame* frame, int frame_size) {
141172 JIT_CHECK (isJitFrame (frame), " frame not executed by the JIT" );
142- uintptr_t frame_base;
143173 if (isGeneratorFrame (frame)) {
144- PyGenObject* gen = _PyGen_GetGeneratorFromFrame (frame);
145- auto footer = jitGenDataFooter (gen);
146- frame_base = footer->originalFramePointer ;
147- } else {
148- frame_base = getFrameBaseFromOnStackFrame (frame);
174+ auto footer = jitGenDataFooter (_PyGen_GetGeneratorFromFrame (frame));
175+ if (footer->yieldPoint != nullptr ) {
176+ // The generator is suspended, so it has no native frame of its own; it
177+ // will resume at its yield point.
178+ return footer->yieldPoint ->resumeTarget ();
179+ }
149180 }
150- auto saved_ip_addr =
151- reinterpret_cast <uintptr_t *>(frame_base - frame_size - kPointerSize );
152- *saved_ip_addr = new_ip;
153- #endif
181+ const void ** saved = getIPStackAddr (frame, frame_size);
182+ if (saved != nullptr ) {
183+ uintptr_t ip;
184+ memcpy (&ip, saved, kPointerSize );
185+ return ip;
186+ }
187+ // Callers report a missing IP; they have the code object needed to make the
188+ // message useful.
189+ return 0 ;
154190}
155191
192+ // Overwrite the address the unit resumes at when its current call returns.
193+ // Returns false if the unit's IP isn't saved anywhere that can be patched.
194+ bool setIP (_PyInterpreterFrame* frame, int frame_size, uintptr_t new_ip) {
195+ JIT_CHECK (isJitFrame (frame), " frame not executed by the JIT" );
196+ const void ** saved = getIPStackAddr (frame, frame_size);
197+ if (saved == nullptr ) {
198+ return false ;
199+ }
200+ *saved = reinterpret_cast <const void *>(new_ip);
201+ return true ;
202+ }
203+
204+ #endif // ENABLE_LIGHTWEIGHT_FRAMES
205+
156206// Collect all the frames in the unit, with the frame for the
157207// non-inlined function as the first element in the return vector.
158208std::vector<_PyInterpreterFrame*> getUnitFrames (_PyInterpreterFrame* frame) {
@@ -211,13 +261,10 @@ UnitState getUnitState(_PyInterpreterFrame* frame) {
211261 }
212262 };
213263
214- #if defined(CINDER_AARCH64)
215- // Look up bytecode offsets using the deopt index stored in the frame header.
216- // The JIT updates this index before each instruction that can deopt, so it
217- // always reflects the current position in the bytecode.
218- std::size_t deopt_idx = jitFrameGetHeader (non_inlined_sf)->deopt_idx ;
264+ // Look up bytecode offsets from the unit's current instruction pointer.
265+ uintptr_t ip = getIP (non_inlined_sf, code_rt->frameSize ());
219266 std::optional<UnitCallStack> locs =
220- code_rt->getUnitCallStackFromDeoptIdx (deopt_idx );
267+ code_rt->debugInfo ()-> getUnitCallStack (ip );
221268 if (locs.has_value ()) {
222269 // We may have a different number of unit_frames than locs, this happens
223270 // when we're updating the outer frame while we're in an inlined function,
@@ -230,35 +277,6 @@ UnitState getUnitState(_PyInterpreterFrame* frame) {
230277 codeName (locs->at (i).code ));
231278 unit_state.emplace_back (unit_frames[i], locs->at (i));
232279 }
233- } else {
234- // We might not have debug info for a number of reasons.
235- // The consequences of getting this wrong (incorrect line numbers) don't
236- // warrant aborting in production, but it is worth investigating.
237- JIT_LOG (
238- " No debug info for deopt_idx {} in {}" ,
239- deopt_idx,
240- PyUnicode_AsUTF8 (code_rt->code ()->co_qualname ));
241- logUnitFrames ();
242- JIT_DABORT (" No debug info for deopt_idx {}" , deopt_idx);
243- for (_PyInterpreterFrame* unit_frame : unit_frames) {
244- unit_state.emplace_back (
245- unit_frame, CodeObjLoc{_PyFrame_GetCode (unit_frame), BCOffset{-1 }});
246- }
247- }
248- #elif defined(CINDER_X86_64)
249- // On x86-64, look up bytecode offsets using the IP-based symbolizer.
250- uintptr_t ip = getIP (non_inlined_sf, code_rt->frameSize ());
251- std::optional<UnitCallStack> locs =
252- code_rt->debugInfo ()->getUnitCallStack (ip);
253- if (locs.has_value ()) {
254- for (std::size_t i = 0 ; i < unit_frames.size (); i++) {
255- JIT_DCHECK (
256- _PyFrame_GetCode (unit_frames[i]) == locs->at (i).code ,
257- " code mismatch {} vs {}" ,
258- codeName (_PyFrame_GetCode (unit_frames[i])),
259- codeName (locs->at (i).code ));
260- unit_state.emplace_back (unit_frames[i], locs->at (i));
261- }
262280 } else {
263281 JIT_LOG (
264282 " No debug info for addr {:x} {}" ,
@@ -271,9 +289,6 @@ UnitState getUnitState(_PyInterpreterFrame* frame) {
271289 unit_frame, CodeObjLoc{_PyFrame_GetCode (unit_frame), BCOffset{-1 }});
272290 }
273291 }
274- #else
275- CINDER_UNSUPPORTED
276- #endif
277292
278293 return unit_state;
279294#else
@@ -342,12 +357,7 @@ std::optional<ActiveDeoptMetadata> getActiveDeoptMetadata(
342357 auto footer = jitGenDataFooter (gen);
343358 frame_base = reinterpret_cast <uintptr_t >(footer);
344359 } else {
345- #if defined(CINDER_X86_64)
346360 frame_base = getFrameBaseFromOnStackFrame (owning_frame);
347- #else
348- frame_base = reinterpret_cast <uintptr_t >(owning_frame) +
349- sizeof (PyObject*) * _PyFrame_GetCode (owning_frame)->co_framesize ;
350- #endif
351361 }
352362 return ActiveDeoptMetadata{
353363 .meta = &code_rt->getDeoptMetadata (deopt_idx),
@@ -654,9 +664,6 @@ void jitFrameInitLightweight(
654664 setFrameCode (frame, reifier);
655665 setFrameFunction (frame, (PyObject*)Py_NewRef (func));
656666 jitFrameGetHeader (frame)->frame_status = 0 ;
657- #if defined(CINDER_AARCH64)
658- jitFrameGetHeader (frame)->deopt_idx = 0 ;
659- #endif
660667#else
661668 frame->stacktop = 0 ;
662669 setFrameInstruction (frame, _PyCode_CODE (code) - 1 );
@@ -667,6 +674,14 @@ void jitFrameInitLightweight(
667674 " frame helper must be immortal" );
668675 setFrameFunction (frame, cinderx::getModuleState ()->frame_reifier );
669676 jitFrameSetFunction (frame, (PyFunctionObject*)Py_NewRef (func));
677+ #endif
678+ #if defined(CINDER_AARCH64)
679+ // Refreshed on every resume by the generator resume entry, since a generator
680+ // can be resumed on a thread other than the one that started it.
681+ jitFrameGetHeader (frame)->tstate = tstate;
682+ #endif
683+ #if defined(Py_GIL_DISABLED)
684+ jitFrameGetHeader (frame)->deopt_idx = 0 ;
670685#endif
671686 frame->previous = previous;
672687}
@@ -830,7 +845,7 @@ void retainActiveDeferredData(
830845}
831846
832847void deoptAllJitFramesOnStack () {
833- #if defined(__x86_64__) && defined( ENABLE_LIGHTWEIGHT_FRAMES)
848+ #ifdef ENABLE_LIGHTWEIGHT_FRAMES
834849 PyInterpreterState* interp = PyInterpreterState_Get ();
835850
836851 // In free-threaded builds, other threads are actively running and may be
@@ -879,11 +894,16 @@ void deoptAllJitFramesOnStack() {
879894 }
880895
881896 jitFramePopulateFrame (frame);
882- setIP (frame, code_rt->frameSize (), deopt_exit.value ());
883-
884- // Mark frame so updatePrevInstr skips it (deopt exit IP
885- // has no debug info entry).
886- jitFrameGetHeader (frame)->frame_status |= JIT_FRAME_DEOPT_PATCHED ;
897+ if (setIP (frame, code_rt->frameSize (), deopt_exit.value ())) {
898+ // Mark frame so updatePrevInstr skips it (deopt exit IP
899+ // has no debug info entry).
900+ jitFrameGetHeader (frame)->frame_status |=
901+ JIT_FRAME_DEOPT_PATCHED ;
902+ } else {
903+ JIT_DLOG (
904+ " Couldn't patch the IP of the JIT frame at {:#x}" ,
905+ current_ip);
906+ }
887907 } else {
888908 JIT_DLOG (
889909 " No callsite deopt exit for JIT frame at IP {:#x}" ,
0 commit comments