From 1f294669ee635a449ab8cb0801e93928596dfa49 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Jul 2023 11:13:48 -0700 Subject: [PATCH 01/12] Attack FOR_ITER_LIST, part 1 (Tier 1) --- Include/internal/pycore_opcode_metadata.h | 18 ++++--- Python/bytecodes.c | 58 +++++++++++++++------ Python/executor_cases.c.h | 37 ++++++++++++++ Python/generated_cases.c.h | 62 +++++++++++++++-------- 4 files changed, 131 insertions(+), 44 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index d2c1f9ad6e5fb3..15eafa735f7d65 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -40,9 +40,12 @@ #define _GUARD_GLOBALS_VERSION 318 #define _GUARD_BUILTINS_VERSION 319 #define IS_NONE 320 -#define _ITER_CHECK_RANGE 321 -#define _ITER_EXHAUSTED_RANGE 322 -#define _ITER_NEXT_RANGE 323 +#define _ITER_CHECK_LIST 321 +#define _ITER_EXHAUSTED_LIST 322 +#define _ITER_NEXT_LIST 323 +#define _ITER_CHECK_RANGE 324 +#define _ITER_EXHAUSTED_RANGE 325 +#define _ITER_NEXT_RANGE 326 #ifndef NEED_OPCODE_METADATA extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); @@ -1322,8 +1325,11 @@ const char * const _PyOpcode_uop_name[512] = { [318] = "_GUARD_GLOBALS_VERSION", [319] = "_GUARD_BUILTINS_VERSION", [320] = "IS_NONE", - [321] = "_ITER_CHECK_RANGE", - [322] = "_ITER_EXHAUSTED_RANGE", - [323] = "_ITER_NEXT_RANGE", + [321] = "_ITER_CHECK_LIST", + [322] = "_ITER_EXHAUSTED_LIST", + [323] = "_ITER_NEXT_LIST", + [324] = "_ITER_CHECK_RANGE", + [325] = "_ITER_EXHAUSTED_RANGE", + [326] = "_ITER_NEXT_RANGE", }; #endif // NEED_OPCODE_METADATA diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 18862f87b65fa0..4dabbc111add7d 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -55,13 +55,14 @@ static PyObject *value, *value1, *value2, *left, *right, *res, *sum, *prod, *sub; static PyObject *container, *start, *stop, *v, *lhs, *rhs, *res2; static PyObject *list, *tuple, *dict, *owner, *set, *str, *tup, *map, *keys; -static PyObject *exit_func, *lasti, *val, *retval, *obj, *iter; +static PyObject *exit_func, *lasti, *val, *retval, *obj, *iter, *exhausted; static PyObject *aiter, *awaitable, *iterable, *w, *exc_value, *bc, *locals; static PyObject *orig, *excs, *update, *b, *fromlist, *level, *from; static PyObject **pieces, **values; static size_t jump; // Dummy variables for cache effects static uint16_t invert, counter, index, hint; +#define unused 0 static uint32_t type_version; static PyObject * @@ -2405,29 +2406,54 @@ dummy_func( INSTRUMENTED_JUMP(here, target, PY_MONITORING_EVENT_BRANCH); } - inst(FOR_ITER_LIST, (unused/1, iter -- iter, next)) { + op(_ITER_CHECK_LIST, (iter -- iter)) { DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); + } + + op(_ITER_JUMP_LIST, (iter -- iter)) { _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); STAT_INC(FOR_ITER, hit); PyListObject *seq = it->it_seq; - if (seq) { - if (it->it_index < PyList_GET_SIZE(seq)) { - next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); - goto end_for_iter_list; // End of this instruction + if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + if (seq != NULL) { + it->it_seq = NULL; + Py_DECREF(seq); } - it->it_seq = NULL; - Py_DECREF(seq); + Py_DECREF(iter); + STACK_SHRINK(1); + SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); + /* Jump forward oparg, then skip following END_FOR instruction */ + JUMPBY(oparg + 1); + DISPATCH(); } - Py_DECREF(iter); - STACK_SHRINK(1); - SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); - DISPATCH(); - end_for_iter_list: - // Common case: no jump, leave it to the code generator } + // Only used by Tier 2 + op(_ITER_EXHAUSTED_LIST, (iter -- iter, exhausted)) { + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + exhausted = Py_True; + } + else { + exhausted = Py_False; + } + } + + op(_ITER_NEXT_LIST, (iter -- iter, next)) { + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyList_GET_SIZE(seq)); + next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); + } + + macro(FOR_ITER_LIST) = + unused/1 + _ITER_CHECK_LIST + _ITER_JUMP_LIST + _ITER_NEXT_LIST; + inst(FOR_ITER_TUPLE, (unused/1, iter -- iter, next)) { _PyTupleIterObject *it = (_PyTupleIterObject *)iter; DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 2c2dbf429cec11..9ebd1c2a4215d7 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1720,6 +1720,43 @@ break; } + case _ITER_CHECK_LIST: { + PyObject *iter = stack_pointer[-1]; + DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); + break; + } + + case _ITER_EXHAUSTED_LIST: { + PyObject *iter = stack_pointer[-1]; + PyObject *exhausted; + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + exhausted = Py_True; + } + else { + exhausted = Py_False; + } + STACK_GROW(1); + stack_pointer[-1] = exhausted; + break; + } + + case _ITER_NEXT_LIST: { + PyObject *iter = stack_pointer[-1]; + PyObject *next; + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyList_GET_SIZE(seq)); + next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); + STACK_GROW(1); + stack_pointer[-1] = next; + break; + } + case _ITER_CHECK_RANGE: { PyObject *iter = stack_pointer[-1]; _PyRangeIterObject *r = (_PyRangeIterObject *)iter; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 383432f51a89ac..26548783d1ae2e 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3034,31 +3034,49 @@ } TARGET(FOR_ITER_LIST) { - PyObject *iter = stack_pointer[-1]; - PyObject *next; - DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); - _PyListIterObject *it = (_PyListIterObject *)iter; - STAT_INC(FOR_ITER, hit); - PyListObject *seq = it->it_seq; - if (seq) { - if (it->it_index < PyList_GET_SIZE(seq)) { - next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); - goto end_for_iter_list; // End of this instruction + PyObject *_tmp_1; + PyObject *_tmp_2 = stack_pointer[-1]; + { + PyObject *iter = _tmp_2; + DEOPT_IF(Py_TYPE(iter) != &PyListIter_Type, FOR_ITER); + _tmp_2 = iter; + } + { + PyObject *iter = _tmp_2; + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + STAT_INC(FOR_ITER, hit); + PyListObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyList_GET_SIZE(seq)) { + if (seq != NULL) { + it->it_seq = NULL; + Py_DECREF(seq); + } + Py_DECREF(iter); + STACK_SHRINK(1); + SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); + /* Jump forward oparg, then skip following END_FOR instruction */ + JUMPBY(oparg + 1); + DISPATCH(); } - it->it_seq = NULL; - Py_DECREF(seq); + _tmp_2 = iter; + } + { + PyObject *iter = _tmp_2; + PyObject *next; + _PyListIterObject *it = (_PyListIterObject *)iter; + assert(Py_TYPE(iter) == &PyListIter_Type); + PyListObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyList_GET_SIZE(seq)); + next = Py_NewRef(PyList_GET_ITEM(seq, it->it_index++)); + _tmp_2 = iter; + _tmp_1 = next; } - Py_DECREF(iter); - STACK_SHRINK(1); - SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); - DISPATCH(); - end_for_iter_list: - // Common case: no jump, leave it to the code generator - STACK_GROW(1); - stack_pointer[-1] = next; next_instr += 1; + STACK_GROW(1); + stack_pointer[-1] = _tmp_1; + stack_pointer[-2] = _tmp_2; DISPATCH(); } From 020c4c77a4f03450a5eb7daf52db03077cb52901 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Jul 2023 13:33:45 -0700 Subject: [PATCH 02/12] FOR_ITER_LIST Tier 2; fix a reservation bug --- Python/optimizer.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index abd2351f6b78bd..dc35eec4a0c91c 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -474,26 +474,47 @@ translate_bytecode_to_trace( case JUMP_FORWARD: { + // Reserve 2 entries (SAVE_IP + EXIT_TRACE) + if (trace_length + 2 > max_length) { + DPRINTF(1, "Ran out of space for JUMP_FORWARD\n"); + goto done; + } // This will emit two SAVE_IP instructions; leave it to the optimizer instr += oparg; break; } + case FOR_ITER_LIST: case FOR_ITER_RANGE: { + int check_op, exhausted_op, next_op; + switch (opcode) { + case FOR_ITER_LIST: + check_op = _ITER_CHECK_LIST; + exhausted_op = _ITER_EXHAUSTED_LIST; + next_op = _ITER_NEXT_LIST; + break; + case FOR_ITER_RANGE: + check_op = _ITER_CHECK_RANGE; + exhausted_op = _ITER_EXHAUSTED_RANGE; + next_op = _ITER_NEXT_RANGE; + break; + default: + assert(0); + } // Assume jump unlikely (can a for-loop exit be likely?) // Reserve 9 entries (4 here, 3 stub, plus SAVE_IP + EXIT_TRACE) if (trace_length + 9 > max_length) { - DPRINTF(1, "Ran out of space for FOR_ITER_RANGE\n"); - goto done; + DPRINTF(1, "Ran out of space for %s\n", uop_name(opcode)); + goto done; } _Py_CODEUNIT *target_instr = // +1 at the end skips over END_FOR instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]] + oparg + 1; max_length -= 3; // Really the start of the stubs - ADD_TO_TRACE(_ITER_CHECK_RANGE, 0); - ADD_TO_TRACE(_ITER_EXHAUSTED_RANGE, 0); + ADD_TO_TRACE(check_op, 0); + ADD_TO_TRACE(exhausted_op, 0); ADD_TO_TRACE(_POP_JUMP_IF_TRUE, max_length); - ADD_TO_TRACE(_ITER_NEXT_RANGE, 0); + ADD_TO_TRACE(next_op, 0); ADD_TO_STUB(max_length + 0, POP_TOP, 0); ADD_TO_STUB(max_length + 1, SAVE_IP, INSTR_IP(target_instr, code)); From a94e583d7443b86c1ce8b78a64831c53310ad7c8 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Jul 2023 14:17:04 -0700 Subject: [PATCH 03/12] Make reserving space less error-prone --- Python/optimizer.c | 64 ++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index dc35eec4a0c91c..d05b4d9bba7a2f 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -378,6 +378,7 @@ translate_bytecode_to_trace( _Py_CODEUNIT *initial_instr = instr; int trace_length = 0; int max_length = buffer_size; + int reserved = 0; #ifdef Py_DEBUG char *uop_debug = Py_GETENV("PYTHONUOPSDEBUG"); @@ -385,6 +386,9 @@ translate_bytecode_to_trace( if (uop_debug != NULL && *uop_debug >= '0') { lltrace = *uop_debug - '0'; // TODO: Parse an int and all that } +#endif + +#ifdef Py_DEBUG #define DPRINTF(level, ...) \ if (lltrace >= (level)) { fprintf(stderr, __VA_ARGS__); } #else @@ -397,6 +401,8 @@ translate_bytecode_to_trace( uop_name(OPCODE), \ (uint64_t)(OPERAND)); \ assert(trace_length < max_length); \ + assert(reserved > 0); \ + reserved--; \ trace[trace_length].opcode = (OPCODE); \ trace[trace_length].operand = (OPERAND); \ trace_length++; @@ -409,9 +415,23 @@ translate_bytecode_to_trace( (INDEX), \ uop_name(OPCODE), \ (uint64_t)(OPERAND)); \ + assert(reserved > 0); \ + reserved--; \ trace[(INDEX)].opcode = (OPCODE); \ trace[(INDEX)].operand = (OPERAND); +// Reserve space for n uops +#define RESERVE_RAW(n, opname) \ + if (trace_length + (n) > max_length) { \ + DPRINTF(2, "No room for %s (need %d, got %d)\n", \ + (opname), (n), max_length - trace_length); \ + goto done; \ + } \ + reserved = (n); // Keep ADD_TO_TRACE / ADD_TO_STUB honest + +// Reserve space for main+stub uops, plus 2 for SAVE_IP and EXIT_TRACE +#define RESERVE(main, stub) RESERVE_RAW((main) + (stub) + 2, uop_name(opcode)) + DPRINTF(4, "Optimizing %s (%s:%d) at byte offset %ld\n", PyUnicode_AsUTF8(code->co_qualname), @@ -420,16 +440,20 @@ translate_bytecode_to_trace( 2 * INSTR_IP(initial_instr, code)); for (;;) { + RESERVE_RAW(2, "epilogue"); // Always need space for SAVE_IP and EXIT_TRACE ADD_TO_TRACE(SAVE_IP, INSTR_IP(instr, code)); + int opcode = instr->op.code; int oparg = instr->op.arg; int extras = 0; + while (opcode == EXTENDED_ARG) { instr++; extras += 1; opcode = instr->op.code; oparg = (oparg << 8) | instr->op.arg; } + if (opcode == ENTER_EXECUTOR) { _PyExecutorObject *executor = (_PyExecutorObject *)code->co_executors->executors[oparg&255]; @@ -437,17 +461,14 @@ translate_bytecode_to_trace( DPRINTF(2, " * ENTER_EXECUTOR -> %s\n", _PyOpcode_OpName[opcode]); oparg = (oparg & 0xffffff00) | executor->vm_data.oparg; } + switch (opcode) { case POP_JUMP_IF_FALSE: case POP_JUMP_IF_TRUE: { // Assume jump unlikely (TODO: handle jump likely case) - // Reserve 5 entries (1 here, 2 stub, plus SAVE_IP + EXIT_TRACE) - if (trace_length + 5 > max_length) { - DPRINTF(1, "Ran out of space for POP_JUMP_IF_FALSE\n"); - goto done; - } + RESERVE(1, 2); _Py_CODEUNIT *target_instr = instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]] + oparg; max_length -= 2; // Really the start of the stubs @@ -461,9 +482,8 @@ translate_bytecode_to_trace( case JUMP_BACKWARD: { - if (instr + 2 - oparg == initial_instr - && trace_length + 3 <= max_length) - { + if (instr + 2 - oparg == initial_instr) { + RESERVE(1, 0); ADD_TO_TRACE(JUMP_TO_TOP, 0); } else { @@ -474,11 +494,7 @@ translate_bytecode_to_trace( case JUMP_FORWARD: { - // Reserve 2 entries (SAVE_IP + EXIT_TRACE) - if (trace_length + 2 > max_length) { - DPRINTF(1, "Ran out of space for JUMP_FORWARD\n"); - goto done; - } + RESERVE(0, 0); // This will emit two SAVE_IP instructions; leave it to the optimizer instr += oparg; break; @@ -487,6 +503,7 @@ translate_bytecode_to_trace( case FOR_ITER_LIST: case FOR_ITER_RANGE: { + RESERVE(4, 3); int check_op, exhausted_op, next_op; switch (opcode) { case FOR_ITER_LIST: @@ -503,11 +520,6 @@ translate_bytecode_to_trace( assert(0); } // Assume jump unlikely (can a for-loop exit be likely?) - // Reserve 9 entries (4 here, 3 stub, plus SAVE_IP + EXIT_TRACE) - if (trace_length + 9 > max_length) { - DPRINTF(1, "Ran out of space for %s\n", uop_name(opcode)); - goto done; - } _Py_CODEUNIT *target_instr = // +1 at the end skips over END_FOR instr + 1 + _PyOpcode_Caches[_PyOpcode_Deopt[opcode]] + oparg + 1; max_length -= 3; // Really the start of the stubs @@ -528,10 +540,7 @@ translate_bytecode_to_trace( if (expansion->nuops > 0) { // Reserve space for nuops (+ SAVE_IP + EXIT_TRACE) int nuops = expansion->nuops; - if (trace_length + nuops + 2 > max_length) { - DPRINTF(1, "Ran out of space for %s\n", uop_name(opcode)); - goto done; - } + RESERVE(nuops, 0); for (int i = 0; i < nuops; i++) { uint64_t operand; int offset = expansion->uops[i].offset; @@ -577,12 +586,14 @@ translate_bytecode_to_trace( } DPRINTF(2, "Unsupported opcode %s\n", uop_name(opcode)); goto done; // Break out of loop - } - } + } // End default + + } // End switch (opcode) + instr++; // Add cache size for opcode instr += _PyOpcode_Caches[_PyOpcode_Deopt[opcode]]; - } + } // End for (;;) done: // Skip short traces like SAVE_IP, LOAD_FAST, SAVE_IP, EXIT_TRACE @@ -631,6 +642,9 @@ translate_bytecode_to_trace( } return 0; +#undef RESERVE +#undef RESERVE_RAW +#undef INSTR_IP #undef ADD_TO_TRACE #undef DPRINTF } From 3f3a9e8fd9f84a63d0497fa2d7089c28d239bede Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Jul 2023 14:27:29 -0700 Subject: [PATCH 04/12] Add test for FOR_ITER_LIST in Tier 2 --- Lib/test/test_capi/test_misc.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index abdf7ed8976350..c0d41215241c89 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2590,7 +2590,6 @@ def testfunc(n): for i in range(n): total += i return total - # import dis; dis.dis(testfunc) opt = _testinternalcapi.get_uop_optimizer() with temporary_optimizer(opt): @@ -2606,6 +2605,28 @@ def testfunc(n): # Verification that the jump goes past END_FOR # is done by manual inspection of the output + def test_for_iter_list(self): + def testfunc(a): + total = 0 + for i in a: + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = list(range(10)) + total = testfunc(a) + self.assertEqual(total, 45) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _ in ex} + self.assertIn("_ITER_EXHAUSTED_LIST", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + if __name__ == "__main__": unittest.main() From ff08319a4bef35a04c104bae38563609061ee8cb Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Jul 2023 14:38:16 -0700 Subject: [PATCH 05/12] FOR_ITER_TUPLE Tier 1 --- Include/internal/pycore_opcode_metadata.h | 18 ++++--- Python/bytecodes.c | 57 +++++++++++++++------ Python/executor_cases.c.h | 37 ++++++++++++++ Python/generated_cases.c.h | 62 +++++++++++++++-------- 4 files changed, 130 insertions(+), 44 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 15eafa735f7d65..c3635488b58d66 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -43,9 +43,12 @@ #define _ITER_CHECK_LIST 321 #define _ITER_EXHAUSTED_LIST 322 #define _ITER_NEXT_LIST 323 -#define _ITER_CHECK_RANGE 324 -#define _ITER_EXHAUSTED_RANGE 325 -#define _ITER_NEXT_RANGE 326 +#define _ITER_CHECK_TUPLE 324 +#define _ITER_EXHAUSTED_TUPLE 325 +#define _ITER_NEXT_TUPLE 326 +#define _ITER_CHECK_RANGE 327 +#define _ITER_EXHAUSTED_RANGE 328 +#define _ITER_NEXT_RANGE 329 #ifndef NEED_OPCODE_METADATA extern int _PyOpcode_num_popped(int opcode, int oparg, bool jump); @@ -1328,8 +1331,11 @@ const char * const _PyOpcode_uop_name[512] = { [321] = "_ITER_CHECK_LIST", [322] = "_ITER_EXHAUSTED_LIST", [323] = "_ITER_NEXT_LIST", - [324] = "_ITER_CHECK_RANGE", - [325] = "_ITER_EXHAUSTED_RANGE", - [326] = "_ITER_NEXT_RANGE", + [324] = "_ITER_CHECK_TUPLE", + [325] = "_ITER_EXHAUSTED_TUPLE", + [326] = "_ITER_NEXT_TUPLE", + [327] = "_ITER_CHECK_RANGE", + [328] = "_ITER_EXHAUSTED_RANGE", + [329] = "_ITER_NEXT_RANGE", }; #endif // NEED_OPCODE_METADATA diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 4dabbc111add7d..36f44c744c11c0 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2454,29 +2454,54 @@ dummy_func( macro(FOR_ITER_LIST) = unused/1 + _ITER_CHECK_LIST + _ITER_JUMP_LIST + _ITER_NEXT_LIST; - inst(FOR_ITER_TUPLE, (unused/1, iter -- iter, next)) { + op(_ITER_CHECK_TUPLE, (iter -- iter)) { + DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER); + } + + op(_ITER_JUMP_TUPLE, (iter -- iter)) { _PyTupleIterObject *it = (_PyTupleIterObject *)iter; - DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER); + assert(Py_TYPE(iter) == &PyTupleIter_Type); STAT_INC(FOR_ITER, hit); PyTupleObject *seq = it->it_seq; - if (seq) { - if (it->it_index < PyTuple_GET_SIZE(seq)) { - next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); - goto end_for_iter_tuple; // End of this instruction + if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) { + if (seq != NULL) { + it->it_seq = NULL; + Py_DECREF(seq); } - it->it_seq = NULL; - Py_DECREF(seq); + Py_DECREF(iter); + STACK_SHRINK(1); + SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); + /* Jump forward oparg, then skip following END_FOR instruction */ + JUMPBY(oparg + 1); + DISPATCH(); } - Py_DECREF(iter); - STACK_SHRINK(1); - SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); - DISPATCH(); - end_for_iter_tuple: - // Common case: no jump, leave it to the code generator } + // Only used by Tier 2 + op(_ITER_EXHAUSTED_TUPLE, (iter -- iter, exhausted)) { + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) { + exhausted = Py_True; + } + else { + exhausted = Py_False; + } + } + + op(_ITER_NEXT_TUPLE, (iter -- iter, next)) { + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyTuple_GET_SIZE(seq)); + next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); + } + + macro(FOR_ITER_TUPLE) = + unused/1 + _ITER_CHECK_TUPLE + _ITER_JUMP_TUPLE + _ITER_NEXT_TUPLE; + op(_ITER_CHECK_RANGE, (iter -- iter)) { _PyRangeIterObject *r = (_PyRangeIterObject *)iter; DEOPT_IF(Py_TYPE(r) != &PyRangeIter_Type, FOR_ITER); diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 9ebd1c2a4215d7..21dc7eeaf15d94 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1757,6 +1757,43 @@ break; } + case _ITER_CHECK_TUPLE: { + PyObject *iter = stack_pointer[-1]; + DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER); + break; + } + + case _ITER_EXHAUSTED_TUPLE: { + PyObject *iter = stack_pointer[-1]; + PyObject *exhausted; + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) { + exhausted = Py_True; + } + else { + exhausted = Py_False; + } + STACK_GROW(1); + stack_pointer[-1] = exhausted; + break; + } + + case _ITER_NEXT_TUPLE: { + PyObject *iter = stack_pointer[-1]; + PyObject *next; + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyTuple_GET_SIZE(seq)); + next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); + STACK_GROW(1); + stack_pointer[-1] = next; + break; + } + case _ITER_CHECK_RANGE: { PyObject *iter = stack_pointer[-1]; _PyRangeIterObject *r = (_PyRangeIterObject *)iter; diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 26548783d1ae2e..127f731b9251b0 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -3081,31 +3081,49 @@ } TARGET(FOR_ITER_TUPLE) { - PyObject *iter = stack_pointer[-1]; - PyObject *next; - _PyTupleIterObject *it = (_PyTupleIterObject *)iter; - DEOPT_IF(Py_TYPE(it) != &PyTupleIter_Type, FOR_ITER); - STAT_INC(FOR_ITER, hit); - PyTupleObject *seq = it->it_seq; - if (seq) { - if (it->it_index < PyTuple_GET_SIZE(seq)) { - next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); - goto end_for_iter_tuple; // End of this instruction + PyObject *_tmp_1; + PyObject *_tmp_2 = stack_pointer[-1]; + { + PyObject *iter = _tmp_2; + DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER); + _tmp_2 = iter; + } + { + PyObject *iter = _tmp_2; + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + STAT_INC(FOR_ITER, hit); + PyTupleObject *seq = it->it_seq; + if (seq == NULL || it->it_index >= PyTuple_GET_SIZE(seq)) { + if (seq != NULL) { + it->it_seq = NULL; + Py_DECREF(seq); + } + Py_DECREF(iter); + STACK_SHRINK(1); + SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); + /* Jump forward oparg, then skip following END_FOR instruction */ + JUMPBY(oparg + 1); + DISPATCH(); } - it->it_seq = NULL; - Py_DECREF(seq); + _tmp_2 = iter; + } + { + PyObject *iter = _tmp_2; + PyObject *next; + _PyTupleIterObject *it = (_PyTupleIterObject *)iter; + assert(Py_TYPE(iter) == &PyTupleIter_Type); + PyTupleObject *seq = it->it_seq; + assert(seq); + assert(it->it_index < PyTuple_GET_SIZE(seq)); + next = Py_NewRef(PyTuple_GET_ITEM(seq, it->it_index++)); + _tmp_2 = iter; + _tmp_1 = next; } - Py_DECREF(iter); - STACK_SHRINK(1); - SKIP_OVER(INLINE_CACHE_ENTRIES_FOR_ITER); - /* Jump forward oparg, then skip following END_FOR instruction */ - JUMPBY(oparg + 1); - DISPATCH(); - end_for_iter_tuple: - // Common case: no jump, leave it to the code generator - STACK_GROW(1); - stack_pointer[-1] = next; next_instr += 1; + STACK_GROW(1); + stack_pointer[-1] = _tmp_1; + stack_pointer[-2] = _tmp_2; DISPATCH(); } From c4273518356ad26fbeef0e466b291833d12dbd4d Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Jul 2023 14:49:32 -0700 Subject: [PATCH 06/12] FOR_ITER_TUPLE Tier 2 --- Python/optimizer.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Python/optimizer.c b/Python/optimizer.c index d05b4d9bba7a2f..0ec1214b1259aa 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -501,6 +501,7 @@ translate_bytecode_to_trace( } case FOR_ITER_LIST: + case FOR_ITER_TUPLE: case FOR_ITER_RANGE: { RESERVE(4, 3); @@ -511,6 +512,11 @@ translate_bytecode_to_trace( exhausted_op = _ITER_EXHAUSTED_LIST; next_op = _ITER_NEXT_LIST; break; + case FOR_ITER_TUPLE: + check_op = _ITER_CHECK_TUPLE; + exhausted_op = _ITER_EXHAUSTED_TUPLE; + next_op = _ITER_NEXT_TUPLE; + break; case FOR_ITER_RANGE: check_op = _ITER_CHECK_RANGE; exhausted_op = _ITER_EXHAUSTED_RANGE; From 173cc7c448981312613dc8bd941cafd2f449f856 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Jul 2023 14:51:49 -0700 Subject: [PATCH 07/12] Test for FOR_ITER_TUPLE Tier 2 --- Lib/test/test_capi/test_misc.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index c0d41215241c89..658be7dc40d85d 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2627,6 +2627,28 @@ def testfunc(a): # Verification that the jump goes past END_FOR # is done by manual inspection of the output + def test_for_iter_tuple(self): + def testfunc(a): + total = 0 + for i in a: + total += i + return total + + opt = _testinternalcapi.get_uop_optimizer() + with temporary_optimizer(opt): + a = tuple(range(10)) + total = testfunc(a) + self.assertEqual(total, 45) + + ex = get_first_executor(testfunc) + self.assertIsNotNone(ex) + # for i, (opname, oparg) in enumerate(ex): + # print(f"{i:4d}: {opname:<20s} {oparg:3d}") + uops = {opname for opname, _ in ex} + self.assertIn("_ITER_EXHAUSTED_TUPLE", uops) + # Verification that the jump goes past END_FOR + # is done by manual inspection of the output + if __name__ == "__main__": unittest.main() From 6681cd2f5b842ccdb6ec36fa28081399867ee458 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Wed, 12 Jul 2023 15:14:05 -0700 Subject: [PATCH 08/12] Tweak dummy definitions/includes --- Python/bytecodes.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 36f44c744c11c0..9d7e9182fddd0e 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -17,6 +17,7 @@ #include "pycore_object.h" // _PyObject_GC_TRACK() #include "pycore_moduleobject.h" // PyModuleObject #include "pycore_opcode.h" // EXTRA_CASES +#include "pycore_opcode_metadata.h" // uop names #include "pycore_opcode_utils.h" // MAKE_FUNCTION_* #include "pycore_pyerrors.h" // _PyErr_GetRaisedException() #include "pycore_pystate.h" // _PyInterpreterState_GET() @@ -62,7 +63,7 @@ static PyObject **pieces, **values; static size_t jump; // Dummy variables for cache effects static uint16_t invert, counter, index, hint; -#define unused 0 +#define unused 0 // Used in a macro def, can't be static static uint32_t type_version; static PyObject * From 13708da4ed780041501b2940af759cb9497bc9d6 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 13 Jul 2023 12:19:21 -0700 Subject: [PATCH 09/12] Rename _ITER_EXHAUSTED_XXX to _IS_ITER_EXHAUSTED_XXX --- Include/internal/pycore_opcode_metadata.h | 12 ++++++------ Python/bytecodes.c | 6 +++--- Python/executor_cases.c.h | 6 +++--- Python/optimizer.c | 6 +++--- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index f0f55946c61b41..e94732b64384b5 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -40,13 +40,13 @@ #define _CHECK_MANAGED_OBJECT_HAS_VALUES 318 #define IS_NONE 319 #define _ITER_CHECK_LIST 320 -#define _ITER_EXHAUSTED_LIST 321 +#define _IS_ITER_EXHAUSTED_LIST 321 #define _ITER_NEXT_LIST 322 #define _ITER_CHECK_TUPLE 323 -#define _ITER_EXHAUSTED_TUPLE 324 +#define _IS_ITER_EXHAUSTED_TUPLE 324 #define _ITER_NEXT_TUPLE 325 #define _ITER_CHECK_RANGE 326 -#define _ITER_EXHAUSTED_RANGE 327 +#define _IS_ITER_EXHAUSTED_RANGE 327 #define _ITER_NEXT_RANGE 328 #define _POP_JUMP_IF_FALSE 329 #define _POP_JUMP_IF_TRUE 330 @@ -1330,13 +1330,13 @@ const char * const _PyOpcode_uop_name[512] = { [_CHECK_MANAGED_OBJECT_HAS_VALUES] = "_CHECK_MANAGED_OBJECT_HAS_VALUES", [IS_NONE] = "IS_NONE", [_ITER_CHECK_LIST] = "_ITER_CHECK_LIST", - [_ITER_EXHAUSTED_LIST] = "_ITER_EXHAUSTED_LIST", + [_IS_ITER_EXHAUSTED_LIST] = "_IS_ITER_EXHAUSTED_LIST", [_ITER_NEXT_LIST] = "_ITER_NEXT_LIST", [_ITER_CHECK_TUPLE] = "_ITER_CHECK_TUPLE", - [_ITER_EXHAUSTED_TUPLE] = "_ITER_EXHAUSTED_TUPLE", + [_IS_ITER_EXHAUSTED_TUPLE] = "_IS_ITER_EXHAUSTED_TUPLE", [_ITER_NEXT_TUPLE] = "_ITER_NEXT_TUPLE", [_ITER_CHECK_RANGE] = "_ITER_CHECK_RANGE", - [_ITER_EXHAUSTED_RANGE] = "_ITER_EXHAUSTED_RANGE", + [_IS_ITER_EXHAUSTED_RANGE] = "_IS_ITER_EXHAUSTED_RANGE", [_ITER_NEXT_RANGE] = "_ITER_NEXT_RANGE", [_POP_JUMP_IF_FALSE] = "_POP_JUMP_IF_FALSE", [_POP_JUMP_IF_TRUE] = "_POP_JUMP_IF_TRUE", diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 525477f7b97e42..8390af2b1cf7ab 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2444,7 +2444,7 @@ dummy_func( } // Only used by Tier 2 - op(_ITER_EXHAUSTED_LIST, (iter -- iter, exhausted)) { + op(_IS_ITER_EXHAUSTED_LIST, (iter -- iter, exhausted)) { _PyListIterObject *it = (_PyListIterObject *)iter; assert(Py_TYPE(iter) == &PyListIter_Type); PyListObject *seq = it->it_seq; @@ -2492,7 +2492,7 @@ dummy_func( } // Only used by Tier 2 - op(_ITER_EXHAUSTED_TUPLE, (iter -- iter, exhausted)) { + op(_IS_ITER_EXHAUSTED_TUPLE, (iter -- iter, exhausted)) { _PyTupleIterObject *it = (_PyTupleIterObject *)iter; assert(Py_TYPE(iter) == &PyTupleIter_Type); PyTupleObject *seq = it->it_seq; @@ -2536,7 +2536,7 @@ dummy_func( } // Only used by Tier 2 - op(_ITER_EXHAUSTED_RANGE, (iter -- iter, exhausted)) { + op(_IS_ITER_EXHAUSTED_RANGE, (iter -- iter, exhausted)) { _PyRangeIterObject *r = (_PyRangeIterObject *)iter; assert(Py_TYPE(r) == &PyRangeIter_Type); exhausted = r->len <= 0 ? Py_True : Py_False; diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 7ca5bca957981e..626baece814607 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -1744,7 +1744,7 @@ break; } - case _ITER_EXHAUSTED_LIST: { + case _IS_ITER_EXHAUSTED_LIST: { PyObject *iter = stack_pointer[-1]; PyObject *exhausted; _PyListIterObject *it = (_PyListIterObject *)iter; @@ -1781,7 +1781,7 @@ break; } - case _ITER_EXHAUSTED_TUPLE: { + case _IS_ITER_EXHAUSTED_TUPLE: { PyObject *iter = stack_pointer[-1]; PyObject *exhausted; _PyTupleIterObject *it = (_PyTupleIterObject *)iter; @@ -1819,7 +1819,7 @@ break; } - case _ITER_EXHAUSTED_RANGE: { + case _IS_ITER_EXHAUSTED_RANGE: { PyObject *iter = stack_pointer[-1]; PyObject *exhausted; _PyRangeIterObject *r = (_PyRangeIterObject *)iter; diff --git a/Python/optimizer.c b/Python/optimizer.c index 0ec1214b1259aa..53449d2ac6e94c 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -509,17 +509,17 @@ translate_bytecode_to_trace( switch (opcode) { case FOR_ITER_LIST: check_op = _ITER_CHECK_LIST; - exhausted_op = _ITER_EXHAUSTED_LIST; + exhausted_op = _IS_ITER_EXHAUSTED_LIST; next_op = _ITER_NEXT_LIST; break; case FOR_ITER_TUPLE: check_op = _ITER_CHECK_TUPLE; - exhausted_op = _ITER_EXHAUSTED_TUPLE; + exhausted_op = _IS_ITER_EXHAUSTED_TUPLE; next_op = _ITER_NEXT_TUPLE; break; case FOR_ITER_RANGE: check_op = _ITER_CHECK_RANGE; - exhausted_op = _ITER_EXHAUSTED_RANGE; + exhausted_op = _IS_ITER_EXHAUSTED_RANGE; next_op = _ITER_NEXT_RANGE; break; default: From 93355ea52f0acb8565b9763749650f0fa2974523 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 13 Jul 2023 16:35:59 -0700 Subject: [PATCH 10/12] Fix tests --- Lib/test/test_capi/test_misc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py index 658be7dc40d85d..43c04463236a2a 100644 --- a/Lib/test/test_capi/test_misc.py +++ b/Lib/test/test_capi/test_misc.py @@ -2601,7 +2601,7 @@ def testfunc(n): # for i, (opname, oparg) in enumerate(ex): # print(f"{i:4d}: {opname:<20s} {oparg:3d}") uops = {opname for opname, _ in ex} - self.assertIn("_ITER_EXHAUSTED_RANGE", uops) + self.assertIn("_IS_ITER_EXHAUSTED_RANGE", uops) # Verification that the jump goes past END_FOR # is done by manual inspection of the output @@ -2623,7 +2623,7 @@ def testfunc(a): # for i, (opname, oparg) in enumerate(ex): # print(f"{i:4d}: {opname:<20s} {oparg:3d}") uops = {opname for opname, _ in ex} - self.assertIn("_ITER_EXHAUSTED_LIST", uops) + self.assertIn("_IS_ITER_EXHAUSTED_LIST", uops) # Verification that the jump goes past END_FOR # is done by manual inspection of the output @@ -2645,7 +2645,7 @@ def testfunc(a): # for i, (opname, oparg) in enumerate(ex): # print(f"{i:4d}: {opname:<20s} {oparg:3d}") uops = {opname for opname, _ in ex} - self.assertIn("_ITER_EXHAUSTED_TUPLE", uops) + self.assertIn("_IS_ITER_EXHAUSTED_TUPLE", uops) # Verification that the jump goes past END_FOR # is done by manual inspection of the output From 52dc7f578aaa782809b3292bdf96e295f75675dc Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 13 Jul 2023 16:41:12 -0700 Subject: [PATCH 11/12] Improve formatting of FOR_ITER_XXX macros --- Python/bytecodes.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 8390af2b1cf7ab..15b48ae9d82672 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -2466,7 +2466,10 @@ dummy_func( } macro(FOR_ITER_LIST) = - unused/1 + _ITER_CHECK_LIST + _ITER_JUMP_LIST + _ITER_NEXT_LIST; + unused/1 + // Skip over the counter + _ITER_CHECK_LIST + + _ITER_JUMP_LIST + + _ITER_NEXT_LIST; op(_ITER_CHECK_TUPLE, (iter -- iter)) { DEOPT_IF(Py_TYPE(iter) != &PyTupleIter_Type, FOR_ITER); @@ -2514,7 +2517,10 @@ dummy_func( } macro(FOR_ITER_TUPLE) = - unused/1 + _ITER_CHECK_TUPLE + _ITER_JUMP_TUPLE + _ITER_NEXT_TUPLE; + unused/1 + // Skip over the counter + _ITER_CHECK_TUPLE + + _ITER_JUMP_TUPLE + + _ITER_NEXT_TUPLE; op(_ITER_CHECK_RANGE, (iter -- iter)) { _PyRangeIterObject *r = (_PyRangeIterObject *)iter; @@ -2554,7 +2560,10 @@ dummy_func( } macro(FOR_ITER_RANGE) = - unused/1 + _ITER_CHECK_RANGE + _ITER_JUMP_RANGE + _ITER_NEXT_RANGE; + unused/1 + // Skip over the counter + _ITER_CHECK_RANGE + + _ITER_JUMP_RANGE + + _ITER_NEXT_RANGE; inst(FOR_ITER_GEN, (unused/1, iter -- iter, unused)) { DEOPT_IF(tstate->interp->eval_frame, FOR_ITER); From 63f101df6acab03c70ed682168b2dd0c036e198f Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Thu, 13 Jul 2023 16:44:44 -0700 Subject: [PATCH 12/12] Use Py_UNREACHABLE() instead of assert(0) --- Python/optimizer.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/optimizer.c b/Python/optimizer.c index 53449d2ac6e94c..289b202f806ae1 100644 --- a/Python/optimizer.c +++ b/Python/optimizer.c @@ -523,7 +523,7 @@ translate_bytecode_to_trace( next_op = _ITER_NEXT_RANGE; break; default: - assert(0); + Py_UNREACHABLE(); } // Assume jump unlikely (can a for-loop exit be likely?) _Py_CODEUNIT *target_instr = // +1 at the end skips over END_FOR pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy