Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-115758: Optimizer constant propagation for 64-bit ints and doubles #117396

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

Closed
Closed
1 change: 1 addition & 0 deletions Include/internal/pycore_floatobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ extern PyObject* _Py_string_to_number_with_underscores(

extern double _Py_parse_inf_or_nan(const char *p, char **endptr);

PyAPI_FUNC(PyObject *) _PyFloat_From64Bits(int64_t);

#ifdef __cplusplus
}
Expand Down
62 changes: 33 additions & 29 deletions Include/internal/pycore_uop_ids.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions Include/internal/pycore_uop_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

75 changes: 58 additions & 17 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -794,48 +794,44 @@ def testfunc(n):

def test_float_add_constant_propagation(self):
def testfunc(n):
a = 1.0
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a limitation of loop-based traces. We should start projecting traces from function entries as well, but I'll leave that for Mark or someone else to do in the future.

for _ in range(n):
a = 1.0
a = a + 0.25
a = a + 0.25
a = a + 0.25
a = a + 0.25
return a

res, ex = self._run_with_optimizer(testfunc, 32)
self.assertAlmostEqual(res, 33.0)
self.assertAlmostEqual(res, 2.0)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"]
self.assertLessEqual(len(guard_both_float_count), 1)
# TODO gh-115506: this assertion may change after propagating constants.
# We'll also need to verify that propagation actually occurs.
self.assertIn("_BINARY_OP_ADD_FLOAT", uops)
self.assertNotIn("_BINARY_OP_ADD_FLOAT", uops)
self.assertIn("_LOAD_FLOAT", uops)

def test_float_subtract_constant_propagation(self):
def testfunc(n):
a = 1.0
for _ in range(n):
a = 1.0
a = a - 0.25
a = a - 0.25
a = a - 0.25
a = a - 0.25
return a

res, ex = self._run_with_optimizer(testfunc, 32)
self.assertAlmostEqual(res, -31.0)
self.assertAlmostEqual(res, 0.0)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"]
self.assertLessEqual(len(guard_both_float_count), 1)
# TODO gh-115506: this assertion may change after propagating constants.
# We'll also need to verify that propagation actually occurs.
self.assertIn("_BINARY_OP_SUBTRACT_FLOAT", uops)
self.assertNotIn("_BINARY_OP_SUBTRACT_FLOAT", uops)
self.assertIn("_LOAD_FLOAT", uops)

def test_float_multiply_constant_propagation(self):
def testfunc(n):
a = 1.0
for _ in range(n):
a = 1.0
a = a * 1.0
a = a * 1.0
a = a * 1.0
Expand All @@ -848,9 +844,22 @@ def testfunc(n):
uops = get_opnames(ex)
guard_both_float_count = [opname for opname in iter_opnames(ex) if opname == "_GUARD_BOTH_FLOAT"]
self.assertLessEqual(len(guard_both_float_count), 1)
# TODO gh-115506: this assertion may change after propagating constants.
# We'll also need to verify that propagation actually occurs.
self.assertIn("_BINARY_OP_MULTIPLY_FLOAT", uops)
self.assertNotIn("_BINARY_OP_MULTIPLY_FLOAT", uops)
self.assertIn("_LOAD_FLOAT", uops)

def test_int_add_constant_propagation_peepholer_advanced(self):
def testfunc(n):
for _ in range(n):
a = 1
a = (a + a) + (a + a + (a + a))
return a

res, ex = self._run_with_optimizer(testfunc, 32)
self.assertAlmostEqual(res, 6)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_BINARY_OP_ADD_INT", uops)
self.assertIn("_LOAD_INT", uops)

def test_add_unicode_propagation(self):
def testfunc(n):
Expand Down Expand Up @@ -941,7 +950,8 @@ def testfunc(n):
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_GUARD_BOTH_INT", uops)
self.assertIn("_BINARY_OP_ADD_INT", uops)
# Constant folded
self.assertIn("_LOAD_INT", uops)
# Try again, but between the runs, set the global to a float.
# This should result in no executor the second time.
ns = {}
Expand Down Expand Up @@ -1245,5 +1255,36 @@ def testfunc(n):
self.assertEqual(res, 32 * 32)
self.assertIsNone(ex)

def test_int_constant_propagation(self):
def testfunc(n):
for _ in range(n):
a = 1
x = a + a - a * a
return x

res, ex = self._run_with_optimizer(testfunc, 32)
self.assertTrue(res)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertNotIn("_BINARY_OP_ADD_INT", uops)
self.assertNotIn("_BINARY_OP_MULTIPLY_INT", uops)
self.assertNotIn("_BINARY_OP_SUBTRACT_INT", uops)

def test_no_bigint_constant_propagation(self):
# We don't want to hold strong references in the trace.
def testfunc(n):
for _ in range(n):
a = 100000000000000000000000000000000000000
x = a + a - a * a
return x

res, ex = self._run_with_optimizer(testfunc, 32)
self.assertTrue(res)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertIn("_BINARY_OP_ADD_INT", uops)
self.assertIn("_BINARY_OP_MULTIPLY_INT", uops)
self.assertIn("_BINARY_OP_SUBTRACT_INT", uops)

if __name__ == "__main__":
unittest.main()
9 changes: 9 additions & 0 deletions Objects/floatobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2614,3 +2614,12 @@ PyFloat_Unpack8(const char *data, int le)
return x;
}
}

PyObject*
_PyFloat_From64Bits(int64_t val)
{
assert(sizeof(double) == sizeof(int64_t));
double dst;
memcpy(&dst, &val, sizeof(int64_t));
return PyFloat_FromDouble(dst);
}
24 changes: 24 additions & 0 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -4127,6 +4127,30 @@ dummy_func(
null = NULL;
}

tier2 pure op(_LOAD_INT, (cached/4 -- value)) {
value = PyLong_FromLong((int64_t)cached);
ERROR_IF(value == NULL, error);
}

tier2 pure op(_POP_TWO_LOAD_INT, (cached/4, pop1, pop2 -- value)) {
Py_DECREF(pop1);
Py_DECREF(pop2);
value = PyLong_FromLong((int64_t)cached);
ERROR_IF(value == NULL, error);
}

tier2 pure op(_LOAD_FLOAT, (cached/4: int64_t -- value)) {
value = _PyFloat_From64Bits((int64_t)cached);
ERROR_IF(value == NULL, error);
}

tier2 pure op(_POP_TWO_LOAD_FLOAT, (cached/4: int64_t, pop1, pop2 -- value)) {
Py_DECREF(pop1);
Py_DECREF(pop2);
value = _PyFloat_From64Bits((int64_t)cached);
ERROR_IF(value == NULL, error);
}

tier2 op(_CHECK_FUNCTION, (func_version/2 -- )) {
assert(PyFunction_Check(frame->f_funcobj));
DEOPT_IF(((PyFunctionObject *)frame->f_funcobj)->func_version != func_version);
Expand Down
52 changes: 52 additions & 0 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading