From 56396d5e1a944b50df81012d8b8d9f6276f385ed Mon Sep 17 00:00:00 2001 From: Peter Lazorchak Date: Thu, 15 Feb 2024 20:50:26 -0800 Subject: [PATCH 1/3] Type / constant propagation for float binary uops --- Lib/test/test_capi/test_opt.py | 93 ++++++++++++------- .../tier2_redundancy_eliminator_bytecodes.c | 68 ++++++++++++++ Python/tier2_redundancy_eliminator_cases.c.h | 77 +++++++++++++-- 3 files changed, 200 insertions(+), 38 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index 1a8ed3441fa855..aad5c6634dc957 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -561,6 +561,16 @@ def testfunc(n): class TestUopsOptimization(unittest.TestCase): + def _run_with_optimizer(self, testfunc, arg): + res = None + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + res = testfunc(arg) + + ex = get_first_executor(testfunc) + return res, ex + + def test_int_type_propagation(self): def testfunc(loops): num = 0 @@ -570,12 +580,7 @@ def testfunc(loops): num += 1 return a - opt = _testinternalcapi.get_uop_optimizer() - res = None - with temporary_optimizer(opt): - res = testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) self.assertIsNotNone(ex) self.assertEqual(res, 63) binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] @@ -642,12 +647,7 @@ def testfunc(loops): num += 1 return a - opt = _testinternalcapi.get_uop_optimizer() - res = None - with temporary_optimizer(opt): - res = testfunc(64) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 64) self.assertIsNotNone(ex) binop_count = [opname for opname, _, _ in ex if opname == "_BINARY_OP_ADD_INT"] self.assertGreaterEqual(len(binop_count), 3) @@ -659,11 +659,7 @@ def dummy(x): for i in range(n): dummy(i) - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} self.assertIn("_PUSH_FRAME", uops) @@ -677,11 +673,7 @@ def testfunc(n): x = i + i return x - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - res = testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) self.assertEqual(res, 62) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} @@ -699,11 +691,7 @@ def testfunc(n): res = x + z + a + b return res - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - res = testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) self.assertEqual(res, 4) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} @@ -716,11 +704,8 @@ def testfunc(n): for _ in range(n): return [i for i in range(n)] - opt = _testinternalcapi.get_uop_optimizer() - with temporary_optimizer(opt): - testfunc(32) - - ex = get_first_executor(testfunc) + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertEqual(res, list(range(32))) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} self.assertNotIn("_BINARY_OP_ADD_INT", uops) @@ -785,6 +770,50 @@ def testfunc(n): """)) self.assertEqual(result[0].rc, 0, result) + def test_float_add_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a + 0.1 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, 4.2) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + # 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) + + def test_float_subtract_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a - 0.1 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, -2.2) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + # 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) + + def test_float_multiply_constant_propagation(self): + def testfunc(n): + a = 1.0 + for _ in range(n): + a = a * 2.0 + return a + + res, ex = self._run_with_optimizer(testfunc, 32) + self.assertAlmostEqual(res, 2 ** 32) + self.assertIsNotNone(ex) + uops = {opname for opname, _, _ in ex} + # 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) if __name__ == "__main__": diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index 39ea0eef627632..b2b47568b79a0c 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -140,6 +140,74 @@ dummy_func(void) { } } + op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) + + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + res = sym_new_known_type(ctx, &PyFloat_Type); + // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through + if (res == NULL) { + goto out_of_space; + } + } + } + + op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) - + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + res = sym_new_known_type(ctx, &PyFloat_Type); + // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through + if (res == NULL) { + goto out_of_space; + } + } + } + + op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) * + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + res = sym_new_known_type(ctx, &PyFloat_Type); + // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through + if (res == NULL) { + goto out_of_space; + } + } + } + op(_LOAD_CONST, (-- value)) { // There should be no LOAD_CONST. It should be all // replaced by peephole_opt. diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index a9617f51ef4615..be93110de27bee 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -278,27 +278,92 @@ } case _BINARY_OP_MULTIPLY_FLOAT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) * + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + res = sym_new_known_type(ctx, &PyFloat_Type); + // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through + if (res == NULL) { + goto out_of_space; + } + } stack_pointer[-2] = res; stack_pointer += -1; break; } case _BINARY_OP_ADD_FLOAT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) + + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + res = sym_new_known_type(ctx, &PyFloat_Type); + // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through + if (res == NULL) { + goto out_of_space; + } + } stack_pointer[-2] = res; stack_pointer += -1; break; } case _BINARY_OP_SUBTRACT_FLOAT: { + _Py_UOpsSymType *right; + _Py_UOpsSymType *left; _Py_UOpsSymType *res; - res = sym_new_unknown(ctx); - if (res == NULL) goto out_of_space; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if (is_const(left) && is_const(right)) { + assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); + PyObject *temp = PyFloat_FromDouble( + PyFloat_AS_DOUBLE(get_const(left)) - + PyFloat_AS_DOUBLE(get_const(right))); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + // TODO gh-115506: + // replace opcode with constant propagated one and update tests! + } + else { + res = sym_new_known_type(ctx, &PyFloat_Type); + // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through + if (res == NULL) { + goto out_of_space; + } + } stack_pointer[-2] = res; stack_pointer += -1; break; From 4d1967f8259e3a85748c57e92590df572a2171bf Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 16 Feb 2024 23:07:09 +0800 Subject: [PATCH 2/3] use new macro, fix tests --- Lib/test/test_capi/test_opt.py | 6 ++++++ Python/tier2_redundancy_eliminator_bytecodes.c | 18 +++--------------- Python/tier2_redundancy_eliminator_cases.c.h | 18 +++--------------- 3 files changed, 12 insertions(+), 30 deletions(-) diff --git a/Lib/test/test_capi/test_opt.py b/Lib/test/test_capi/test_opt.py index aad5c6634dc957..66860c67966859 100644 --- a/Lib/test/test_capi/test_opt.py +++ b/Lib/test/test_capi/test_opt.py @@ -781,6 +781,8 @@ def testfunc(n): self.assertAlmostEqual(res, 4.2) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in 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) @@ -796,6 +798,8 @@ def testfunc(n): self.assertAlmostEqual(res, -2.2) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in 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) @@ -811,6 +815,8 @@ def testfunc(n): self.assertAlmostEqual(res, 2 ** 32) self.assertIsNotNone(ex) uops = {opname for opname, _, _ in ex} + guard_both_float_count = [opname for opname, _, _ in 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) diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index f361c5c670be1a..e81b2b5e87bf8f 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -147,11 +147,7 @@ dummy_func(void) { // replace opcode with constant propagated one and update tests! } else { - res = sym_new_known_type(ctx, &PyFloat_Type); - // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); } } @@ -170,11 +166,7 @@ dummy_func(void) { // replace opcode with constant propagated one and update tests! } else { - res = sym_new_known_type(ctx, &PyFloat_Type); - // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); } } @@ -192,11 +184,7 @@ dummy_func(void) { // replace opcode with constant propagated one and update tests! } else { - res = sym_new_known_type(ctx, &PyFloat_Type); - // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); } } diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index 8bc1aee3c2c5cd..42c0a6b2174278 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -288,11 +288,7 @@ // replace opcode with constant propagated one and update tests! } else { - res = sym_new_known_type(ctx, &PyFloat_Type); - // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -319,11 +315,7 @@ // replace opcode with constant propagated one and update tests! } else { - res = sym_new_known_type(ctx, &PyFloat_Type); - // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; @@ -350,11 +342,7 @@ // replace opcode with constant propagated one and update tests! } else { - res = sym_new_known_type(ctx, &PyFloat_Type); - // TODO: OUT_OF_SPACE_IF_NULL once gh-115507 goes through - if (res == NULL) { - goto out_of_space; - } + OUT_OF_SPACE_IF_NULL(res = sym_new_known_type(ctx, &PyFloat_Type)); } stack_pointer[-2] = res; stack_pointer += -1; From 2756130578e40180fc3ca106538e122ba23eebdf Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 16 Feb 2024 23:14:19 +0800 Subject: [PATCH 3/3] add assert --- Python/tier2_redundancy_eliminator_bytecodes.c | 1 + Python/tier2_redundancy_eliminator_cases.c.h | 1 + 2 files changed, 2 insertions(+) diff --git a/Python/tier2_redundancy_eliminator_bytecodes.c b/Python/tier2_redundancy_eliminator_bytecodes.c index e81b2b5e87bf8f..3f6e8ce1bbfbad 100644 --- a/Python/tier2_redundancy_eliminator_bytecodes.c +++ b/Python/tier2_redundancy_eliminator_bytecodes.c @@ -173,6 +173,7 @@ dummy_func(void) { op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { if (is_const(left) && is_const(right)) { assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); PyObject *temp = PyFloat_FromDouble( PyFloat_AS_DOUBLE(get_const(left)) * PyFloat_AS_DOUBLE(get_const(right))); diff --git a/Python/tier2_redundancy_eliminator_cases.c.h b/Python/tier2_redundancy_eliminator_cases.c.h index 42c0a6b2174278..be2fbb9106fffc 100644 --- a/Python/tier2_redundancy_eliminator_cases.c.h +++ b/Python/tier2_redundancy_eliminator_cases.c.h @@ -277,6 +277,7 @@ left = stack_pointer[-2]; if (is_const(left) && is_const(right)) { assert(PyFloat_CheckExact(get_const(left))); + assert(PyFloat_CheckExact(get_const(right))); PyObject *temp = PyFloat_FromDouble( PyFloat_AS_DOUBLE(get_const(left)) * PyFloat_AS_DOUBLE(get_const(right)));