Skip to content

Commit 5bfab63

Browse files
jbower-fbmeta-codesync[bot]
authored andcommitted
Enable lightweight frames
Summary: The only things needed for this are: * Fix the incref to support FT. * Make sure the TLBC is set to 0 on frame initialization. A TLBC of 0 suggests we are running from "unmodified" bytecode, which we effectively are. A non-zero TLBC is only needed if we deopt, and we already do this. Generally setting the TLBC to a value other than zero at any time other than initialization (which is unnecessary) or deopting is invalid as we may not be on the thread where this code executes. Reviewed By: DinoV Differential Revision: D92106509 fbshipit-source-id: 21c2be8c47bb174c6361b934df009c2b2365c90d
1 parent e85366e commit 5bfab63

4 files changed

Lines changed: 145 additions & 24 deletions

File tree

cinderx/Jit/codegen/frame_asm.cpp

Lines changed: 120 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,85 @@ void FrameAsm::linkNormalGeneratorFrame(
134134
preserver.restore();
135135
}
136136

137-
void FrameAsm::emitIncTotalRefCount(const arch::Gp& scratch_reg) {
137+
#ifdef Py_GIL_DISABLED
138+
139+
void emit_inc_total_ref_count_x86_nogil(
140+
[[maybe_unused]] asmjit::x86::Builder* as,
141+
[[maybe_unused]] const arch::Gp& tstate_reg) {
142+
#ifdef Py_REF_DEBUG
143+
#if defined(CINDER_X86_64)
144+
as->inc(
145+
x86::ptr(
146+
tstate_reg,
147+
offsetof(_PyThreadStateImpl, reftotal),
148+
sizeof(Py_ssize_t)));
149+
#else
150+
CINDER_UNSUPPORTED
151+
#endif
152+
#endif
153+
}
154+
155+
void inc_ref_x86_nogil(
156+
[[maybe_unused]] asmjit::x86::Builder* as,
157+
[[maybe_unused]] const arch::Gp& reg,
158+
[[maybe_unused]] const arch::Gp& scratch_reg,
159+
[[maybe_unused]] const arch::Gp& tstate_reg) {
160+
#if defined(CINDER_X86_64)
161+
// For free-threaded Python, check immortality via ob_ref_local.
162+
// Load ob_ref_local (32-bit). Note this load should be atomic with relaxed
163+
// memory semantics, which is default on x86.
164+
as->mov(
165+
scratch_reg.r32(), x86::dword_ptr(reg, offsetof(PyObject, ob_ref_local)));
166+
// Add 1 - if result is zero, object was immortal (UINT32_MAX + 1 overflows
167+
// to 0)
168+
as->inc(scratch_reg.r32());
169+
Label immortal = as->newLabel();
170+
as->jz(immortal);
171+
172+
// Check if object is owned by current thread by comparing ob_tid with
173+
// the current thread ID. This is equivalent to _Py_IsOwnedByCurrentThread.
174+
// TODO: I don't have the stomach to find another scratch register for the
175+
// ob_tid to current TID comparison, so I'm just reusing the one scratch
176+
// register we have for now plus the stack. Should still be pretty fast on
177+
// x86.
178+
Label not_owned = as->newLabel();
179+
as->push(scratch_reg);
180+
as->mov(scratch_reg, x86::ptr(reg, offsetof(PyObject, ob_tid)));
181+
x86::Mem tid_mem;
182+
tid_mem.setOffset(0);
183+
tid_mem.setSize(sizeof(uintptr_t));
184+
tid_mem.setSegment(x86::fs);
185+
as->cmp(scratch_reg, tid_mem);
186+
as->pop(scratch_reg);
187+
as->jne(not_owned);
188+
189+
// Owned by current thread - store directly to ob_ref_local (fast path).
190+
// Note this store should be atomic with relaxed memory semantics, which is
191+
// default on x86.
192+
as->mov(
193+
x86::dword_ptr(reg, offsetof(PyObject, ob_ref_local)), scratch_reg.r32());
194+
Label done_incref = as->newLabel();
195+
as->jmp(done_incref);
196+
197+
// Not owned - use atomic add to ob_ref_shared (slow path)
198+
as->bind(not_owned);
199+
as->lock().add(
200+
x86::qword_ptr(reg, offsetof(PyObject, ob_ref_shared)),
201+
1 << _Py_REF_SHARED_SHIFT);
202+
as->bind(done_incref);
203+
emit_inc_total_ref_count_x86_nogil(as, tstate_reg);
204+
as->bind(immortal);
205+
#else
206+
CINDER_UNSUPPORTED
207+
#endif
208+
}
209+
210+
#else
211+
// GILful inc-ref implementation
212+
213+
void emit_inc_total_ref_count_x86_gil(
214+
[[maybe_unused]] asmjit::x86::Builder* as,
215+
[[maybe_unused]] const arch::Gp& scratch_reg) {
138216
#ifdef Py_REF_DEBUG
139217
#if defined(CINDER_X86_64)
140218
PyInterpreterState* interp;
@@ -145,36 +223,54 @@ void FrameAsm::emitIncTotalRefCount(const arch::Gp& scratch_reg) {
145223
}
146224

147225
Py_ssize_t* ref_total = &interp->object_state.reftotal;
148-
as_->mov(scratch_reg, ref_total);
149-
as_->inc(x86::ptr(scratch_reg.r64(), 0, sizeof(void*)));
226+
as->mov(scratch_reg, ref_total);
227+
as->inc(x86::ptr(scratch_reg, 0, sizeof(void*)));
150228
#else
151229
CINDER_UNSUPPORTED
152230
#endif
153231
#endif
154232
}
155233

156-
#ifdef ENABLE_LIGHTWEIGHT_FRAMES
157-
// TODO(T251571746): need to correctly implement FT incref.
158-
void FrameAsm::incRef(const arch::Gp& reg, const arch::Gp& scratch_reg) {
234+
void inc_ref_x86_gil(
235+
asmjit::x86::Builder* as,
236+
const arch::Gp& reg,
237+
const arch::Gp& scratch_reg) {
159238
#if defined(CINDER_X86_64)
160-
as_->mov(scratch_reg, x86::ptr(reg, offsetof(PyObject, ob_refcnt)));
161-
as_->inc(scratch_reg);
162-
Label immortal = as_->newLabel();
239+
Label immortal = as->newLabel();
240+
as->mov(scratch_reg.r32(), x86::ptr(reg, offsetof(PyObject, ob_refcnt)));
241+
as->inc(scratch_reg.r32());
163242
#if PY_VERSION_HEX >= 0x030E0000
164-
as_->js(immortal);
243+
as->js(immortal);
165244
#else
166-
as_->je(immortal);
245+
as->je(immortal);
167246
#endif
168247
// mortal
169-
as_->mov(x86::ptr(reg, offsetof(PyObject, ob_refcnt)), scratch_reg);
170-
emitIncTotalRefCount(scratch_reg.r64());
171-
as_->bind(immortal);
248+
as->mov(x86::ptr(reg, offsetof(PyObject, ob_refcnt)), scratch_reg.r32());
249+
emit_inc_total_ref_count_x86_gil(as, scratch_reg);
250+
as->bind(immortal);
172251
#else
173252
CINDER_UNSUPPORTED
174253
#endif
175254
}
255+
#endif // Py_GIL_DISABLED
256+
257+
void FrameAsm::incRef(
258+
const arch::Gp& reg,
259+
const arch::Gp& scratch_reg,
260+
[[maybe_unused]] const arch::Gp& tstate_reg) {
261+
#if defined(CINDER_X86_64)
262+
263+
#if defined(Py_GIL_DISABLED)
264+
inc_ref_x86_nogil(as_, reg, scratch_reg, tstate_reg);
265+
#else
266+
inc_ref_x86_gil(as_, reg, scratch_reg);
176267
#endif
177268

269+
#else
270+
CINDER_UNSUPPORTED
271+
#endif
272+
}
273+
178274
bool FrameAsm::storeConst(
179275
const arch::Gp& reg,
180276
int32_t offset,
@@ -241,7 +337,7 @@ void FrameAsm::linkLightWeightFunctionFrame(
241337
#else
242338
PyObject* frame_reifier = env_.code_rt->reifier();
243339
#endif
244-
const auto ref_cnt = x86::eax;
340+
const auto ref_cnt = x86::rax;
245341

246342
#define FRAME_OFFSET(NAME) \
247343
-frame_header_size + offsetof(_PyInterpreterFrame, NAME) + sizeof(FrameHeader)
@@ -254,7 +350,7 @@ void FrameAsm::linkLightWeightFunctionFrame(
254350
// Initialize the fields minus previous.
255351
// Store func before the header
256352
as_->mov(x86::ptr(x86::rbp, -frame_header_size), func_reg);
257-
incRef(func_reg, ref_cnt);
353+
incRef(func_reg, ref_cnt, tstate_reg);
258354
env_.addAnnotation("Store func before frame header", store_func_cursor);
259355
#endif
260356

@@ -271,7 +367,7 @@ void FrameAsm::linkLightWeightFunctionFrame(
271367
// if this fit into a 32-bit value we didn't spill it into scratch
272368
as_->mov(scratch, reinterpret_cast<uint64_t>(executable));
273369
}
274-
incRef(scratch, ref_cnt);
370+
incRef(scratch, ref_cnt, tstate_reg);
275371
}
276372
env_.addAnnotation(
277373
"Set _PyInterpreterFrame::f_executable/f_code", store_f_code_cursor);
@@ -280,15 +376,15 @@ void FrameAsm::linkLightWeightFunctionFrame(
280376
asmjit::BaseNode* store_f_funcobj_cursor = as_->cursor();
281377
#if PY_VERSION_HEX >= 0x030E0000
282378
as_->mov(x86::ptr(x86::rbp, FRAME_OFFSET(f_funcobj)), func_reg);
283-
incRef(func_reg, ref_cnt);
379+
incRef(func_reg, ref_cnt, tstate_reg);
284380
#else
285381
storeConst(x86::rbp, FRAME_OFFSET(f_funcobj), frame_reifier, scratch);
286382
JIT_DCHECK(_Py_IsImmortal(frame_reifier), "frame helper must be immortal");
287383
#endif
288384
env_.addAnnotation(
289385
"Set _PyInterpreterFrame::f_funcobj", store_f_funcobj_cursor);
290386

291-
// Store prev_instr
387+
// Store prev_instr + tlbc_index
292388
asmjit::BaseNode* store_prev_instr_cursor = as_->cursor();
293389
#if PY_VERSION_HEX >= 0x030E0000
294390
_Py_CODEUNIT* code = _PyCode_CODE(GetFunction()->code.get());
@@ -298,6 +394,11 @@ void FrameAsm::linkLightWeightFunctionFrame(
298394
storeConst(x86::rbp, FRAME_OFFSET(FRAME_INSTR), code, scratch);
299395
env_.addAnnotation(
300396
"Set _PyInterpreterFrame::prev_instr", store_prev_instr_cursor);
397+
#ifdef Py_GIL_DISABLED
398+
asmjit::BaseNode* tlbc_index_cursor = as_->cursor();
399+
as_->mov(x86::dword_ptr(x86::rbp, FRAME_OFFSET(tlbc_index)), 0);
400+
env_.addAnnotation("Set TLBC index to 0", tlbc_index_cursor);
401+
#endif
301402

302403
// Store owner
303404
asmjit::BaseNode* store_owner_cursor = as_->cursor();
@@ -361,8 +462,6 @@ void FrameAsm::linkLightWeightFunctionFrame(
361462
preserver.remap();
362463
}
363464
#else
364-
// TODO(T251571746): For FTPython either need to set TLBC somewhere in here or
365-
// in the reifier.
366465
throw std::runtime_error{
367466
"linkLightWeightFunctionFrame: Lightweight frames are not supported"};
368467
#endif

cinderx/Jit/codegen/frame_asm.h

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ class FrameAsm {
5050
bool isGen() const {
5151
return func_->code->co_flags & kCoFlagsAnyGenerator;
5252
}
53-
54-
void emitIncTotalRefCount(const arch::Gp& scratch_reg);
55-
void incRef(const arch::Gp& reg, const arch::Gp& scratch_reg);
53+
void incRef(
54+
const arch::Gp& reg,
55+
const arch::Gp& scratch_reg,
56+
const arch::Gp& tstate_reg);
5657
bool storeConst(
5758
const arch::Gp& reg,
5859
int32_t offset,

cinderx/Jit/frame.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,14 @@ void jitFramePopulateFrame([[maybe_unused]] _PyInterpreterFrame* frame) {
324324
return;
325325
}
326326

327+
#if Py_GIL_DISABLED
328+
// The TLBC index should be 0 i.e. the default code for the frame. This makes
329+
// sense because it's what the JIT is effectively running. Furthermore, if
330+
// something did set the TLBC index, we have no guarantee this ran on the
331+
// correct thread.
332+
JIT_CHECK(frame->tlbc_index == 0, "frame has non-zero tlbc_index");
333+
#endif
334+
327335
PyFunctionObject* func;
328336
if (!hasRtfsFunction(frame)) {
329337
func = jitFrameGetFunction(frame);
@@ -479,6 +487,9 @@ void jitFrameInitLightweight(
479487
JIT_DCHECK(reifier, "reifier needed for lightweight frames");
480488
frame->stackpointer = frame->localsplus;
481489
setFrameInstruction(frame, _PyCode_CODE(code));
490+
#ifdef Py_GIL_DISABLED
491+
frame->tlbc_index = 0;
492+
#endif
482493
setFrameCode(frame, reifier);
483494
setFrameFunction(frame, (PyObject*)Py_NewRef(func));
484495
jitFrameGetHeader(frame)->rtfs = 0;

cinderx/Jit/lir/generator.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2961,6 +2961,16 @@ LIRGenerator::TranslatedBlock LIRGenerator::TranslateOneBasicBlock(
29612961
Instruction::kMove,
29622962
codeunit_reg);
29632963

2964+
#ifdef Py_GIL_DISABLED
2965+
bbb.appendInstr(
2966+
OutInd{
2967+
callee_frame,
2968+
offsetof(_PyInterpreterFrame, tlbc_index),
2969+
OperandBase::k32bit},
2970+
Instruction::kMove,
2971+
Imm{0, OperandBase::k32bit});
2972+
#endif
2973+
29642974
bbb.appendInstr(
29652975
OutInd{
29662976
callee_frame,

0 commit comments

Comments
 (0)