diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 16288a447e20fe..ea72f9fdc00915 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -3114,6 +3114,40 @@ def testfunc(n): self.assertNotIn("_POP_TOP_INT", uops) self.assertIn("_POP_TOP_NOP", uops) + def test_strength_reduce_constant_load_fast(self): + # If we detect a _LOAD_FAST is actually loading a constant, + # reduce that to a _LOAD_CONST_INLINE_BORROW which saves + # the read from locals. + def testfunc(n): + for _ in range(n): + x = 0 + y = x + + _, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + + self.assertIn("_LOAD_CONST_INLINE_BORROW", uops) + self.assertNotIn("_LOAD_FAST_2", uops) + + def test_strength_reduce_constant_load_fast_borrow(self): + # If we detect a _LOAD_FAST_BORROW is actually loading a constant, + # reduce that to a _LOAD_CONST_INLINE_BORROW which saves + # the read from locals. + def testfunc(n): + class A: pass + a = A() + for _ in range(n): + x = 0 + a.x = x + + _, ex = self._run_with_optimizer(testfunc, TIER2_THRESHOLD) + self.assertIsNotNone(ex) + uops = get_opnames(ex) + + self.assertIn("_LOAD_CONST_INLINE_BORROW", uops) + self.assertNotIn("_LOAD_FAST_BORROW_4", uops) + def test_143026(self): # https://github.com/python/cpython/issues/143026 diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index b40b597643dc94..de1774e7a57e24 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -87,10 +87,23 @@ dummy_func(void) { op(_LOAD_FAST, (-- value)) { value = GETLOCAL(oparg); + PyObject *const_val = sym_get_const(ctx, value); + if (const_val != NULL) { + // It's safe to always borrow here, for + // the same reason as _LOAD_CONST.. + value = PyJitRef_Borrow(value); + REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)const_val); + } } op(_LOAD_FAST_BORROW, (-- value)) { value = PyJitRef_Borrow(GETLOCAL(oparg)); + PyObject *const_val = sym_get_const(ctx, value); + if (const_val != NULL) { + // It's safe to always borrow here, because + // _LOAD_FAST_BORROW guarantees it. + REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)const_val); + } } op(_LOAD_FAST_AND_CLEAR, (-- value)) { @@ -110,7 +123,7 @@ dummy_func(void) { } op(_STORE_FAST, (value --)) { - GETLOCAL(oparg) = value; + GETLOCAL(oparg) = PyJitRef_StripReferenceInfo(value); } op(_STORE_SUBSCR_LIST_INT, (value, list_st, sub_st -- ls, ss)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index a17a5688847e07..15d0b56ebddf93 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -43,6 +43,11 @@ case _LOAD_FAST: { JitOptRef value; value = GETLOCAL(oparg); + PyObject *const_val = sym_get_const(ctx, value); + if (const_val != NULL) { + value = PyJitRef_Borrow(value); + REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)const_val); + } CHECK_STACK_BOUNDS(1); stack_pointer[0] = value; stack_pointer += 1; @@ -53,6 +58,10 @@ case _LOAD_FAST_BORROW: { JitOptRef value; value = PyJitRef_Borrow(GETLOCAL(oparg)); + PyObject *const_val = sym_get_const(ctx, value); + if (const_val != NULL) { + REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)const_val); + } CHECK_STACK_BOUNDS(1); stack_pointer[0] = value; stack_pointer += 1; @@ -102,7 +111,7 @@ case _STORE_FAST: { JitOptRef value; value = stack_pointer[-1]; - GETLOCAL(oparg) = value; + GETLOCAL(oparg) = PyJitRef_StripReferenceInfo(value); CHECK_STACK_BOUNDS(-1); stack_pointer += -1; ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);