Skip to content

Commit 053c285

Browse files
authored
gh-130704: Strength reduce LOAD_FAST{_LOAD_FAST} (#130708)
Optimize `LOAD_FAST` opcodes into faster versions that load borrowed references onto the operand stack when we can prove that the lifetime of the local outlives the lifetime of the temporary that is loaded onto the stack.
1 parent e9556e1 commit 053c285

35 files changed

+1282
-345
lines changed

Doc/library/dis.rst

+17-3
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ the following command can be used to display the disassembly of
7676
2 RESUME 0
7777
<BLANKLINE>
7878
3 LOAD_GLOBAL 1 (len + NULL)
79-
LOAD_FAST 0 (alist)
79+
LOAD_FAST_BORROW 0 (alist)
8080
CALL 1
8181
RETURN_VALUE
8282

@@ -215,7 +215,7 @@ Example:
215215
...
216216
RESUME
217217
LOAD_GLOBAL
218-
LOAD_FAST
218+
LOAD_FAST_BORROW
219219
CALL
220220
RETURN_VALUE
221221

@@ -1402,13 +1402,28 @@ iterations of the loop.
14021402
This opcode is now only used in situations where the local variable is
14031403
guaranteed to be initialized. It cannot raise :exc:`UnboundLocalError`.
14041404

1405+
.. opcode:: LOAD_FAST_BORROW (var_num)
1406+
1407+
Pushes a borrowed reference to the local ``co_varnames[var_num]`` onto the
1408+
stack.
1409+
1410+
.. versionadded:: 3.14
1411+
14051412
.. opcode:: LOAD_FAST_LOAD_FAST (var_nums)
14061413

14071414
Pushes references to ``co_varnames[var_nums >> 4]`` and
14081415
``co_varnames[var_nums & 15]`` onto the stack.
14091416

14101417
.. versionadded:: 3.13
14111418

1419+
1420+
.. opcode:: LOAD_FAST_BORROW_LOAD_FAST_BORROW (var_nums)
1421+
1422+
Pushes borrowed references to ``co_varnames[var_nums >> 4]`` and
1423+
``co_varnames[var_nums & 15]`` onto the stack.
1424+
1425+
.. versionadded:: 3.14
1426+
14121427
.. opcode:: LOAD_FAST_CHECK (var_num)
14131428

14141429
Pushes a reference to the local ``co_varnames[var_num]`` onto the stack,
@@ -2023,4 +2038,3 @@ instructions:
20232038

20242039
.. deprecated:: 3.13
20252040
All jumps are now relative. This list is empty.
2026-

Include/internal/pycore_frame.h

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ struct _frame {
2828
PyEval_GetLocals requires a borrowed reference so the actual reference
2929
is stored here */
3030
PyObject *f_locals_cache;
31+
/* A tuple containing strong references to fast locals that were overwritten
32+
* via f_locals. Borrowed references to these locals may exist in frames
33+
* closer to the top of the stack. The references in this tuple act as
34+
* "support" for the borrowed references, ensuring that they remain valid.
35+
*/
36+
PyObject *f_overwritten_fast_locals;
3137
/* The frame data, if this frame object owns the frame */
3238
PyObject *_f_frame_data[1];
3339
};

Include/internal/pycore_opcode_metadata.h

+16-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Include/internal/pycore_stackref.h

+36-14
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ PyStackRef_MakeHeapSafe(_PyStackRef ref)
172172
return ref;
173173
}
174174

175+
static inline _PyStackRef
176+
PyStackRef_Borrow(_PyStackRef ref)
177+
{
178+
return PyStackRef_DUP(ref)
179+
}
180+
175181
#define PyStackRef_CLEAR(REF) \
176182
do { \
177183
_PyStackRef *_tmp_op_ptr = &(REF); \
@@ -253,6 +259,25 @@ _PyStackRef_FromPyObjectSteal(PyObject *obj)
253259
}
254260
# define PyStackRef_FromPyObjectSteal(obj) _PyStackRef_FromPyObjectSteal(_PyObject_CAST(obj))
255261

262+
static inline bool
263+
PyStackRef_IsHeapSafe(_PyStackRef stackref)
264+
{
265+
if (PyStackRef_IsDeferred(stackref)) {
266+
PyObject *obj = PyStackRef_AsPyObjectBorrow(stackref);
267+
return obj == NULL || _Py_IsImmortal(obj) || _PyObject_HasDeferredRefcount(obj);
268+
}
269+
return true;
270+
}
271+
272+
static inline _PyStackRef
273+
PyStackRef_MakeHeapSafe(_PyStackRef stackref)
274+
{
275+
if (PyStackRef_IsHeapSafe(stackref)) {
276+
return stackref;
277+
}
278+
PyObject *obj = PyStackRef_AsPyObjectBorrow(stackref);
279+
return (_PyStackRef){ .bits = (uintptr_t)(Py_NewRef(obj)) | Py_TAG_PTR };
280+
}
256281

257282
static inline _PyStackRef
258283
PyStackRef_FromPyObjectStealMortal(PyObject *obj)
@@ -311,25 +336,16 @@ PyStackRef_DUP(_PyStackRef stackref)
311336
{
312337
assert(!PyStackRef_IsNull(stackref));
313338
if (PyStackRef_IsDeferred(stackref)) {
314-
assert(_Py_IsImmortal(PyStackRef_AsPyObjectBorrow(stackref)) ||
315-
_PyObject_HasDeferredRefcount(PyStackRef_AsPyObjectBorrow(stackref))
316-
);
317339
return stackref;
318340
}
319341
Py_INCREF(PyStackRef_AsPyObjectBorrow(stackref));
320342
return stackref;
321343
}
322344

323-
static inline bool
324-
PyStackRef_IsHeapSafe(_PyStackRef ref)
325-
{
326-
return true;
327-
}
328-
329345
static inline _PyStackRef
330-
PyStackRef_MakeHeapSafe(_PyStackRef ref)
346+
PyStackRef_Borrow(_PyStackRef stackref)
331347
{
332-
return ref;
348+
return (_PyStackRef){ .bits = stackref.bits | Py_TAG_DEFERRED };
333349
}
334350

335351
// Convert a possibly deferred reference to a strong reference.
@@ -399,7 +415,6 @@ static inline void PyStackRef_CheckValid(_PyStackRef ref) {
399415
assert(!_Py_IsStaticImmortal(obj));
400416
break;
401417
case Py_TAG_REFCNT:
402-
assert(obj == NULL || _Py_IsImmortal(obj));
403418
break;
404419
default:
405420
assert(0);
@@ -413,21 +428,28 @@ static inline void PyStackRef_CheckValid(_PyStackRef ref) {
413428
#endif
414429

415430
#ifdef _WIN32
416-
#define PyStackRef_RefcountOnObject(REF) (((REF).bits & Py_TAG_BITS) == 0)
431+
#define PyStackRef_RefcountOnObject(REF) (((REF).bits & Py_TAG_REFCNT) == 0)
417432
#define PyStackRef_AsPyObjectBorrow BITS_TO_PTR_MASKED
433+
#define PyStackRef_Borrow(REF) (_PyStackRef){ .bits = ((REF).bits) | Py_TAG_REFCNT};
418434
#else
419435
/* Does this ref not have an embedded refcount and thus not refer to a declared immmortal object? */
420436
static inline int
421437
PyStackRef_RefcountOnObject(_PyStackRef ref)
422438
{
423-
return (ref.bits & Py_TAG_BITS) == 0;
439+
return (ref.bits & Py_TAG_REFCNT) == 0;
424440
}
425441

426442
static inline PyObject *
427443
PyStackRef_AsPyObjectBorrow(_PyStackRef ref)
428444
{
429445
return BITS_TO_PTR_MASKED(ref);
430446
}
447+
448+
static inline _PyStackRef
449+
PyStackRef_Borrow(_PyStackRef ref)
450+
{
451+
return (_PyStackRef){ .bits = ref.bits | Py_TAG_REFCNT };
452+
}
431453
#endif
432454

433455
static inline PyObject *

0 commit comments

Comments
 (0)