From 1ffbb6b1f1dafc983783eb218fd43038340e90c9 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sun, 20 Apr 2025 00:17:43 +0800 Subject: [PATCH 01/43] Automatically constant evaluate pure operations --- Include/internal/pycore_opcode_metadata.h | 34 ++-- Include/internal/pycore_optimizer.h | 1 + Include/internal/pycore_uop_metadata.h | 82 ++++---- Python/bytecodes.c | 40 ++-- Python/optimizer_analysis.c | 6 + Python/optimizer_bytecodes.c | 81 ++------ Python/optimizer_cases.c.h | 199 +++++++++++-------- Python/optimizer_symbols.c | 9 + Tools/cases_generator/generators_common.py | 44 ++-- Tools/cases_generator/optimizer_generator.py | 168 +++++++++++++++- 10 files changed, 422 insertions(+), 242 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 521f7a92cf08c4..32635cefb5f79e 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1123,7 +1123,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = { [CONTAINS_OP_DICT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CONTAINS_OP_SET] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CONVERT_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_PURE_FLAG }, + [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [COPY_FREE_VARS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [DELETE_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [DELETE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, @@ -1135,7 +1135,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = { [DICT_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [END_ASYNC_FOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [END_FOR] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_NO_SAVE_IP_FLAG }, - [END_SEND] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, + [END_SEND] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [EXIT_INIT_CHECK] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [EXTENDED_ARG] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, @@ -1204,9 +1204,9 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = { [LOAD_CONST_IMMORTAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG }, [LOAD_CONST_MORTAL] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG }, [LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG }, + [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, [LOAD_FAST_AND_CLEAR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_FAST_BORROW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG }, + [LOAD_FAST_BORROW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, [LOAD_FAST_BORROW_LOAD_FAST_BORROW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, [LOAD_FAST_CHECK] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, @@ -1229,17 +1229,17 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = { [MATCH_KEYS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_MAPPING] = { true, INSTR_FMT_IX, 0 }, [MATCH_SEQUENCE] = { true, INSTR_FMT_IX, 0 }, - [NOP] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, - [NOT_TAKEN] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, + [NOP] = { true, INSTR_FMT_IX, 0 }, + [NOT_TAKEN] = { true, INSTR_FMT_IX, 0 }, [POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, - [POP_ITER] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, + [POP_ITER] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [POP_TOP] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, + [POP_TOP] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [PUSH_EXC_INFO] = { true, INSTR_FMT_IX, 0 }, - [PUSH_NULL] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, + [PUSH_NULL] = { true, INSTR_FMT_IX, 0 }, [RAISE_VARARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [RERAISE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [RESERVED] = { true, INSTR_FMT_IX, 0 }, @@ -1267,7 +1267,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = { [STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [STORE_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [STORE_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_PURE_FLAG }, + [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [TO_BOOL] = { true, INSTR_FMT_IXC00, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG }, @@ -1275,8 +1275,8 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = { [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG }, [TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG }, [TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, + [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, [UNARY_NOT] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, [UNPACK_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, @@ -1289,11 +1289,11 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = { [JUMP_IF_FALSE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_IF_TRUE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_NO_INTERRUPT] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [LOAD_CLOSURE] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG }, - [POP_BLOCK] = { true, -1, HAS_PURE_FLAG }, - [SETUP_CLEANUP] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, - [SETUP_FINALLY] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, - [SETUP_WITH] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, + [LOAD_CLOSURE] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [POP_BLOCK] = { true, -1, 0 }, + [SETUP_CLEANUP] = { true, -1, HAS_ARG_FLAG }, + [SETUP_FINALLY] = { true, -1, HAS_ARG_FLAG }, + [SETUP_WITH] = { true, -1, HAS_ARG_FLAG }, [STORE_FAST_MAYBE_NULL] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG }, }; #endif diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 4af1fa63ac1f1a..6fa2337a15f9f8 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -256,6 +256,7 @@ extern bool _Py_uop_sym_is_null(JitOptSymbol *sym); extern bool _Py_uop_sym_is_not_null(JitOptSymbol *sym); extern bool _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym); extern PyObject *_Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym); +extern JitOptSymbol *_Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val); extern JitOptSymbol *_Py_uop_sym_new_unknown(JitOptContext *ctx); extern JitOptSymbol *_Py_uop_sym_new_not_null(JitOptContext *ctx); extern JitOptSymbol *_Py_uop_sym_new_type( diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 874756770c1871..6492855fde9414 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -19,29 +19,29 @@ extern int _PyUop_num_popped(int opcode, int oparg); #ifdef NEED_OPCODE_METADATA const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { - [_NOP] = HAS_PURE_FLAG, + [_NOP] = 0, [_CHECK_PERIODIC] = HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CHECK_PERIODIC_IF_NOT_YIELD_FROM] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_RESUME_CHECK] = HAS_DEOPT_FLAG, [_LOAD_FAST_CHECK] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_LOAD_FAST_0] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_1] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_2] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_3] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_4] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_5] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_6] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_7] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW_0] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW_1] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW_2] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW_3] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW_4] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW_5] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW_6] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW_7] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, - [_LOAD_FAST_BORROW] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_0] = HAS_LOCAL_FLAG, + [_LOAD_FAST_1] = HAS_LOCAL_FLAG, + [_LOAD_FAST_2] = HAS_LOCAL_FLAG, + [_LOAD_FAST_3] = HAS_LOCAL_FLAG, + [_LOAD_FAST_4] = HAS_LOCAL_FLAG, + [_LOAD_FAST_5] = HAS_LOCAL_FLAG, + [_LOAD_FAST_6] = HAS_LOCAL_FLAG, + [_LOAD_FAST_7] = HAS_LOCAL_FLAG, + [_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW_0] = HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW_1] = HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW_2] = HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW_3] = HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW_4] = HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW_5] = HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW_6] = HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW_7] = HAS_LOCAL_FLAG, + [_LOAD_FAST_BORROW] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_LOAD_FAST_AND_CLEAR] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_LOAD_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_LOAD_FAST_BORROW_LOAD_FAST_BORROW] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, @@ -63,11 +63,11 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG, [_STORE_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG, [_STORE_FAST_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG, - [_POP_TOP] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, - [_PUSH_NULL] = HAS_PURE_FLAG, + [_POP_TOP] = HAS_ESCAPES_FLAG, + [_PUSH_NULL] = 0, [_END_FOR] = HAS_ESCAPES_FLAG | HAS_NO_SAVE_IP_FLAG, - [_END_SEND] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, - [_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_END_SEND] = HAS_ESCAPES_FLAG, + [_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_UNARY_NOT] = HAS_PURE_FLAG, [_TO_BOOL] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_TO_BOOL_BOOL] = HAS_EXIT_FLAG, @@ -80,7 +80,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_GUARD_TOS_UNICODE] = HAS_EXIT_FLAG, [_TO_BOOL_STR] = HAS_ESCAPES_FLAG, [_REPLACE_WITH_TRUE] = HAS_ESCAPES_FLAG, - [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, + [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_GUARD_NOS_INT] = HAS_EXIT_FLAG, [_GUARD_TOS_INT] = HAS_EXIT_FLAG, [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, @@ -88,13 +88,13 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_GUARD_NOS_FLOAT] = HAS_EXIT_FLAG, [_GUARD_TOS_FLOAT] = HAS_EXIT_FLAG, - [_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, - [_BINARY_OP_ADD_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, - [_BINARY_OP_SUBTRACT_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG, + [_BINARY_OP_ADD_FLOAT] = HAS_ERROR_FLAG, + [_BINARY_OP_SUBTRACT_FLOAT] = HAS_ERROR_FLAG, [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_BINARY_OP_INPLACE_ADD_UNICODE] = HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_BINARY_OP_EXTEND] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, - [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG, [_BINARY_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_BINARY_OP_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, @@ -233,12 +233,12 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_PEP_523] = HAS_DEOPT_FLAG, [_CHECK_FUNCTION_EXACT_ARGS] = HAS_ARG_FLAG | HAS_EXIT_FLAG, [_CHECK_STACK_SPACE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, - [_INIT_CALL_PY_EXACT_ARGS_0] = HAS_PURE_FLAG, - [_INIT_CALL_PY_EXACT_ARGS_1] = HAS_PURE_FLAG, - [_INIT_CALL_PY_EXACT_ARGS_2] = HAS_PURE_FLAG, - [_INIT_CALL_PY_EXACT_ARGS_3] = HAS_PURE_FLAG, - [_INIT_CALL_PY_EXACT_ARGS_4] = HAS_PURE_FLAG, - [_INIT_CALL_PY_EXACT_ARGS] = HAS_ARG_FLAG | HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_0] = 0, + [_INIT_CALL_PY_EXACT_ARGS_1] = 0, + [_INIT_CALL_PY_EXACT_ARGS_2] = 0, + [_INIT_CALL_PY_EXACT_ARGS_3] = 0, + [_INIT_CALL_PY_EXACT_ARGS_4] = 0, + [_INIT_CALL_PY_EXACT_ARGS] = HAS_ARG_FLAG, [_PUSH_FRAME] = 0, [_CALL_TYPE_1] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, [_CALL_STR_1] = HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, @@ -272,9 +272,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CONVERT_VALUE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_FORMAT_SIMPLE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_FORMAT_WITH_SPEC] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_COPY] = HAS_ARG_FLAG | HAS_PURE_FLAG, + [_COPY] = HAS_ARG_FLAG, [_BINARY_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG, - [_SWAP] = HAS_ARG_FLAG | HAS_PURE_FLAG, + [_SWAP] = HAS_ARG_FLAG, [_GUARD_IS_TRUE_POP] = HAS_EXIT_FLAG, [_GUARD_IS_FALSE_POP] = HAS_EXIT_FLAG, [_GUARD_IS_NONE_POP] = HAS_EXIT_FLAG | HAS_ESCAPES_FLAG, @@ -285,11 +285,11 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_SAVE_RETURN_OFFSET] = HAS_ARG_FLAG, [_EXIT_TRACE] = HAS_ESCAPES_FLAG, [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, - [_LOAD_CONST_INLINE] = HAS_PURE_FLAG, - [_POP_TOP_LOAD_CONST_INLINE] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, - [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, - [_POP_TOP_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, - [_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_LOAD_CONST_INLINE] = 0, + [_POP_TOP_LOAD_CONST_INLINE] = HAS_ESCAPES_FLAG, + [_LOAD_CONST_INLINE_BORROW] = 0, + [_POP_TOP_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, + [_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_CHECK_FUNCTION] = HAS_DEOPT_FLAG, [_START_EXECUTOR] = HAS_ESCAPES_FLAG, [_MAKE_WARM] = 0, diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 2796c3f2e85732..4bedb3a753d729 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -142,7 +142,7 @@ dummy_func( switch (opcode) { // BEGIN BYTECODES // - pure inst(NOP, (--)) { + inst(NOP, (--)) { } family(RESUME, 0) = { @@ -265,12 +265,12 @@ dummy_func( value = PyStackRef_DUP(value_s); } - replicate(8) pure inst(LOAD_FAST, (-- value)) { + replicate(8) inst(LOAD_FAST, (-- value)) { assert(!PyStackRef_IsNull(GETLOCAL(oparg))); value = PyStackRef_DUP(GETLOCAL(oparg)); } - replicate(8) pure inst (LOAD_FAST_BORROW, (-- value)) { + replicate(8) inst (LOAD_FAST_BORROW, (-- value)) { assert(!PyStackRef_IsNull(GETLOCAL(oparg))); value = PyStackRef_Borrow(GETLOCAL(oparg)); } @@ -388,11 +388,11 @@ dummy_func( PyStackRef_XCLOSE(tmp); } - pure inst(POP_TOP, (value --)) { + inst(POP_TOP, (value --)) { PyStackRef_CLOSE(value); } - pure inst(PUSH_NULL, (-- res)) { + inst(PUSH_NULL, (-- res)) { res = PyStackRef_NULL; } @@ -424,7 +424,7 @@ dummy_func( PyStackRef_CLOSE(iter); } - pure inst(END_SEND, (receiver, value -- val)) { + inst(END_SEND, (receiver, value -- val)) { val = value; DEAD(value); PyStackRef_CLOSE(receiver); @@ -443,7 +443,7 @@ dummy_func( PyStackRef_CLOSE(receiver); } - inst(UNARY_NEGATIVE, (value -- res)) { + pure inst(UNARY_NEGATIVE, (value -- res)) { PyObject *res_o = PyNumber_Negative(PyStackRef_AsPyObjectBorrow(value)); PyStackRef_CLOSE(value); ERROR_IF(res_o == NULL, error); @@ -573,7 +573,7 @@ dummy_func( _GUARD_TYPE_VERSION + _REPLACE_WITH_TRUE; - inst(UNARY_INVERT, (value -- res)) { + pure inst(UNARY_INVERT, (value -- res)) { PyObject *res_o = PyNumber_Invert(PyStackRef_AsPyObjectBorrow(value)); PyStackRef_CLOSE(value); ERROR_IF(res_o == NULL, error); @@ -669,7 +669,7 @@ dummy_func( EXIT_IF(!PyFloat_CheckExact(value_o)); } - pure op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { + op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -684,7 +684,7 @@ dummy_func( ERROR_IF(PyStackRef_IsNull(res), error); } - pure op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { + op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -699,7 +699,7 @@ dummy_func( ERROR_IF(PyStackRef_IsNull(res), error); } - pure op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { + op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -799,7 +799,7 @@ dummy_func( DEOPT_IF(!res); } - pure op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) { + op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(INLINE_CACHE_ENTRIES_BINARY_OP == 5); @@ -3906,7 +3906,7 @@ dummy_func( DEOPT_IF(tstate->py_recursion_remaining <= 1); } - replicate(5) pure op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) { + replicate(5) op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) { int has_self = !PyStackRef_IsNull(self_or_null); STAT_INC(CALL, hit); new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame); @@ -4916,7 +4916,7 @@ dummy_func( res = PyStackRef_FromPyObjectSteal(res_o); } - pure inst(COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) { + inst(COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) { assert(oparg > 0); top = PyStackRef_DUP(bottom); } @@ -4950,7 +4950,7 @@ dummy_func( macro(BINARY_OP) = _SPECIALIZE_BINARY_OP + unused/4 + _BINARY_OP; - pure inst(SWAP, (bottom, unused[oparg-2], top -- + inst(SWAP, (bottom, unused[oparg-2], top -- bottom, unused[oparg-2], top)) { _PyStackRef temp = bottom; bottom = top; @@ -5187,25 +5187,25 @@ dummy_func( DEOPT_IF(!current_executor->vm_data.valid); } - tier2 pure op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { + tier2 op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { value = PyStackRef_FromPyObjectNew(ptr); } - tier2 pure op (_POP_TOP_LOAD_CONST_INLINE, (ptr/4, pop -- value)) { + tier2 op (_POP_TOP_LOAD_CONST_INLINE, (ptr/4, pop -- value)) { PyStackRef_CLOSE(pop); value = PyStackRef_FromPyObjectNew(ptr); } - tier2 pure op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { + tier2 op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { value = PyStackRef_FromPyObjectImmortal(ptr); } - tier2 pure op (_POP_TOP_LOAD_CONST_INLINE_BORROW, (ptr/4, pop -- value)) { + tier2 op (_POP_TOP_LOAD_CONST_INLINE_BORROW, (ptr/4, pop -- value)) { PyStackRef_CLOSE(pop); value = PyStackRef_FromPyObjectImmortal(ptr); } - tier2 pure op(_POP_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, pop1, pop2 -- value)) { + tier2 op(_POP_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, pop1, pop2 -- value)) { PyStackRef_CLOSE(pop2); PyStackRef_CLOSE(pop1); value = PyStackRef_FromPyObjectImmortal(ptr); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index ab28fae94a96a9..569436b05b1bf1 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -29,6 +29,7 @@ #include "pycore_uop_metadata.h" #include "pycore_uop_ids.h" #include "pycore_range.h" +#include "pycore_unicodeobject.h" #include #include @@ -321,6 +322,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_is_not_null _Py_uop_sym_is_not_null #define sym_is_const _Py_uop_sym_is_const #define sym_get_const _Py_uop_sym_get_const +#define sym_new_const_steal _Py_uop_sym_new_const_steal #define sym_new_unknown _Py_uop_sym_new_unknown #define sym_new_not_null _Py_uop_sym_new_not_null #define sym_new_type _Py_uop_sym_new_type @@ -346,6 +348,8 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_is_immortal _Py_uop_sym_is_immortal #define sym_new_truthiness _Py_uop_sym_new_truthiness +#define JUMP_TO_LABEL(label) goto label; + static int optimize_to_bool( _PyUOpInstruction *this_instr, @@ -515,6 +519,8 @@ optimize_uops( } return trace_len; +pop_2_error: +pop_1_error: error: DPRINTF(3, "\n"); DPRINTF(1, "Encountered error in abstract interpreter\n"); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index c5d8b536bc6341..e2d58210f2ecc3 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -210,60 +210,27 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyLong_CheckExact(sym_get_const(ctx, left))); - assert(PyLong_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(ctx, left), - (PyLongObject *)sym_get_const(ctx, right)); - if (temp == NULL) { - goto error; - } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); - // TODO gh-115506: - // replace opcode with constant propagated one and add tests! - } - else { - res = sym_new_type(ctx, &PyLong_Type); - } + // We need to tell the cases generator that it's being used by the constant generator. + // We should fix this in the cases generator. + (void)(left); + (void)(right); + res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyLong_CheckExact(sym_get_const(ctx, left))); - assert(PyLong_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(ctx, left), - (PyLongObject *)sym_get_const(ctx, right)); - if (temp == NULL) { - goto error; - } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); - // TODO gh-115506: - // replace opcode with constant propagated one and add tests! - } - else { - res = sym_new_type(ctx, &PyLong_Type); - } + // We need to tell the cases generator that it's being used by the constant generator. + // We should fix this in the cases generator. + (void)(left); + (void)(right); + res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyLong_CheckExact(sym_get_const(ctx, left))); - assert(PyLong_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(ctx, left), - (PyLongObject *)sym_get_const(ctx, right)); - if (temp == NULL) { - goto error; - } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); - // TODO gh-115506: - // replace opcode with constant propagated one and add tests! - } - else { - res = sym_new_type(ctx, &PyLong_Type); - } + // We need to tell the cases generator that it's being used by the constant generator. + // We should fix this in the cases generator. + (void)(left); + (void)(right); + res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { @@ -327,19 +294,11 @@ dummy_func(void) { } op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyUnicode_CheckExact(sym_get_const(ctx, left))); - assert(PyUnicode_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = PyUnicode_Concat(sym_get_const(ctx, left), sym_get_const(ctx, right)); - if (temp == NULL) { - goto error; - } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); - } - else { - res = sym_new_type(ctx, &PyUnicode_Type); - } + // We need to tell the cases generator that it's being used here. + // We should fix this in the cases generator. + (void)(left); + (void)(right); + res = sym_new_type(ctx, &PyUnicode_Type); } op(_BINARY_OP_INPLACE_ADD_UNICODE, (left, right -- )) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 828f0943a8db86..a74f7df9aa56a2 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -141,8 +141,21 @@ } case _UNARY_NEGATIVE: { + JitOptSymbol *value; JitOptSymbol *res; - res = sym_new_not_null(ctx); + value = stack_pointer[-1]; + if ( + sym_is_const(ctx, value) && + 1) { + PyObject *res_o = PyNumber_Negative( sym_get_const(ctx, value)); + if (res_o == NULL) { + JUMP_TO_LABEL(pop_1_error); + } + res = sym_new_const_steal(ctx, res_o); + } + else { + res = sym_new_not_null(ctx); + } stack_pointer[-1] = res; break; } @@ -151,8 +164,15 @@ JitOptSymbol *value; JitOptSymbol *res; value = stack_pointer[-1]; - sym_set_type(value, &PyBool_Type); - res = sym_new_truthiness(ctx, value, false); + if ( + sym_is_const(ctx, value) && + 1) { + res =(sym_is_const(ctx, value) && Py_IsFalse(sym_get_const(ctx, value)))?sym_new_const(ctx, Py_True):sym_new_const(ctx, Py_False); + } + else { + sym_set_type(value, &PyBool_Type); + res = sym_new_truthiness(ctx, value, false); + } stack_pointer[-1] = res; break; } @@ -280,8 +300,21 @@ } case _UNARY_INVERT: { + JitOptSymbol *value; JitOptSymbol *res; - res = sym_new_not_null(ctx); + value = stack_pointer[-1]; + if ( + sym_is_const(ctx, value) && + 1) { + PyObject *res_o = PyNumber_Invert( sym_get_const(ctx, value)); + if (res_o == NULL) { + JUMP_TO_LABEL(pop_1_error); + } + res = sym_new_const_steal(ctx, res_o); + } + else { + res = sym_new_not_null(ctx); + } stack_pointer[-1] = res; break; } @@ -312,25 +345,27 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyLong_CheckExact(sym_get_const(ctx, left))); - assert(PyLong_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(ctx, left), - (PyLongObject *)sym_get_const(ctx, right)); - if (temp == NULL) { - goto error; + if ( + sym_is_const(ctx, left) && + sym_is_const(ctx, right) && + 1) { + PyObject *left_o = sym_get_const(ctx, left); + PyObject *right_o = sym_get_const(ctx, right); + STAT_INC(BINARY_OP, hit); + PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o); + if (res_o == NULL) { + JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const(ctx, temp); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - Py_DECREF(temp); + res = sym_new_const_steal(ctx, res_o); } else { + (void)(left); + (void)(right); res = sym_new_type(ctx, &PyLong_Type); - stack_pointer += -1; } - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -340,25 +375,27 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyLong_CheckExact(sym_get_const(ctx, left))); - assert(PyLong_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(ctx, left), - (PyLongObject *)sym_get_const(ctx, right)); - if (temp == NULL) { - goto error; + if ( + sym_is_const(ctx, left) && + sym_is_const(ctx, right) && + 1) { + PyObject *left_o = sym_get_const(ctx, left); + PyObject *right_o = sym_get_const(ctx, right); + STAT_INC(BINARY_OP, hit); + PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o); + if (res_o == NULL) { + JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const(ctx, temp); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - Py_DECREF(temp); + res = sym_new_const_steal(ctx, res_o); } else { + (void)(left); + (void)(right); res = sym_new_type(ctx, &PyLong_Type); - stack_pointer += -1; } - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -368,25 +405,27 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyLong_CheckExact(sym_get_const(ctx, left))); - assert(PyLong_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(ctx, left), - (PyLongObject *)sym_get_const(ctx, right)); - if (temp == NULL) { - goto error; + if ( + sym_is_const(ctx, left) && + sym_is_const(ctx, right) && + 1) { + PyObject *left_o = sym_get_const(ctx, left); + PyObject *right_o = sym_get_const(ctx, right); + STAT_INC(BINARY_OP, hit); + PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o); + if (res_o == NULL) { + JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const(ctx, temp); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - Py_DECREF(temp); + res = sym_new_const_steal(ctx, res_o); } else { + (void)(left); + (void)(right); res = sym_new_type(ctx, &PyLong_Type); - stack_pointer += -1; } - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -426,16 +465,14 @@ goto error; } res = sym_new_const(ctx, temp); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); Py_DECREF(temp); } else { res = sym_new_type(ctx, &PyFloat_Type); - stack_pointer += -1; } - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -455,16 +492,14 @@ goto error; } res = sym_new_const(ctx, temp); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); Py_DECREF(temp); } else { res = sym_new_type(ctx, &PyFloat_Type); - stack_pointer += -1; } - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -484,16 +519,14 @@ goto error; } res = sym_new_const(ctx, temp); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); Py_DECREF(temp); } else { res = sym_new_type(ctx, &PyFloat_Type); - stack_pointer += -1; } - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -503,24 +536,27 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyUnicode_CheckExact(sym_get_const(ctx, left))); - assert(PyUnicode_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = PyUnicode_Concat(sym_get_const(ctx, left), sym_get_const(ctx, right)); - if (temp == NULL) { - goto error; + if ( + sym_is_const(ctx, left) && + sym_is_const(ctx, right) && + 1) { + PyObject *left_o = sym_get_const(ctx, left); + PyObject *right_o = sym_get_const(ctx, right); + STAT_INC(BINARY_OP, hit); + PyObject *res_o = PyUnicode_Concat(left_o, right_o); + if (res_o == NULL) { + JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const(ctx, temp); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - Py_DECREF(temp); + res = sym_new_const_steal(ctx, res_o); } else { + (void)(left); + (void)(right); res = sym_new_type(ctx, &PyUnicode_Type); - stack_pointer += -1; } - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -1177,7 +1213,6 @@ PyModuleObject *mod = (PyModuleObject *)sym_get_const(ctx, owner); if (PyModule_CheckExact(mod)) { PyObject *dict = mod->md_dict; - stack_pointer[-1] = attr; uint64_t watched_mutations = get_mutations(dict); if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { PyDict_Watch(GLOBALS_WATCHER_ID, dict); @@ -1301,16 +1336,14 @@ assert(_Py_IsImmortal(tmp)); REPLACE_OP(this_instr, _POP_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)tmp); res = sym_new_const(ctx, tmp); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); Py_DECREF(tmp); } else { res = sym_new_type(ctx, &PyBool_Type); - stack_pointer += -1; } - stack_pointer[-1] = res; + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -2079,13 +2112,13 @@ assert(framesize > 0); assert(framesize <= curr_space); curr_space -= framesize; - stack_pointer[0] = res; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); co = get_code(this_instr); if (co == NULL) { ctx->done = true; } + stack_pointer[0] = res; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); break; } diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index e8a4f87031b76a..c0fe70a667830a 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -381,6 +381,15 @@ _Py_uop_sym_new_const(JitOptContext *ctx, PyObject *const_val) return res; } +JitOptSymbol * +_Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val) +{ + assert(const_val != NULL); + JitOptSymbol *res = _Py_uop_sym_new_const(ctx, const_val); + Py_DECREF(const_val); + return res; +} + JitOptSymbol * _Py_uop_sym_new_null(JitOptContext *ctx) { diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 9ba0767cba35a0..5e8f64f1e70943 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -75,6 +75,18 @@ def write_header( """ ) +def skip_to(tkn_iter: TokenIterator, end: str) -> Token: + tkn = None + parens = 0 + for tkn in tkn_iter: + if tkn.kind == end and parens == 0: + return tkn + if tkn.kind == "LPAREN": + parens += 1 + if tkn.kind == "RPAREN": + parens -= 1 + assert tkn is not None + return tkn def emit_to(out: CWriter, tkn_iter: TokenIterator, end: str) -> Token: parens = 0 @@ -463,12 +475,13 @@ def _emit_stmt( uop: CodeSection, storage: Storage, inst: Instruction | None, + is_abstract: bool, ) -> tuple[bool, Token | None, Storage]: method_name = "emit_" + stmt.__class__.__name__ method = getattr(self, method_name, None) if method is None: raise NotImplementedError - return method(stmt, uop, storage, inst) # type: ignore[no-any-return] + return method(stmt, uop, storage, inst, is_abstract) # type: ignore[no-any-return] def emit_SimpleStmt( self, @@ -476,12 +489,13 @@ def emit_SimpleStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, + is_abstract: bool, ) -> tuple[bool, Token | None, Storage]: local_stores = set(uop.local_stores) reachable = True tkn = stmt.contents[-1] try: - if stmt in uop.properties.escaping_calls: + if stmt in uop.properties.escaping_calls and not is_abstract: escape = uop.properties.escaping_calls[stmt] if escape.kills is not None: self.stackref_kill(escape.kills, storage, True) @@ -513,7 +527,7 @@ def emit_SimpleStmt( self.out.emit(tkn) else: self.out.emit(tkn) - if stmt in uop.properties.escaping_calls: + if stmt in uop.properties.escaping_calls and not is_abstract: self.emit_reload(storage) return reachable, None, storage except StackError as ex: @@ -526,6 +540,7 @@ def emit_MacroIfStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, + is_abstract: bool ) -> tuple[bool, Token | None, Storage]: self.out.emit(stmt.condition) branch = stmt.else_ is not None @@ -533,7 +548,7 @@ def emit_MacroIfStmt( if branch: else_storage = storage.copy() for s in stmt.body: - r, tkn, storage = self._emit_stmt(s, uop, storage, inst) + r, tkn, storage = self._emit_stmt(s, uop, storage, inst, is_abstract) if tkn is not None: self.out.emit(tkn) if not r: @@ -543,7 +558,7 @@ def emit_MacroIfStmt( self.out.emit(stmt.else_) assert stmt.else_body is not None for s in stmt.else_body: - r, tkn, else_storage = self._emit_stmt(s, uop, else_storage, inst) + r, tkn, else_storage = self._emit_stmt(s, uop, else_storage, inst, is_abstract) if tkn is not None: self.out.emit(tkn) if not r: @@ -560,6 +575,7 @@ def emit_IfStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, + is_abstract: bool ) -> tuple[bool, Token | None, Storage]: self.out.emit(stmt.if_) for tkn in stmt.condition: @@ -567,13 +583,13 @@ def emit_IfStmt( if_storage = storage.copy() rbrace: Token | None = stmt.if_ try: - reachable, rbrace, if_storage = self._emit_stmt(stmt.body, uop, if_storage, inst) + reachable, rbrace, if_storage = self._emit_stmt(stmt.body, uop, if_storage, inst, is_abstract) if stmt.else_ is not None: assert rbrace is not None self.out.emit(rbrace) self.out.emit(stmt.else_) if stmt.else_body is not None: - else_reachable, rbrace, else_storage = self._emit_stmt(stmt.else_body, uop, storage, inst) + else_reachable, rbrace, else_storage = self._emit_stmt(stmt.else_body, uop, storage, inst, is_abstract) if not reachable: reachable, storage = else_reachable, else_storage elif not else_reachable: @@ -601,6 +617,7 @@ def emit_BlockStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, + is_abstract: bool, emit_braces: bool = True, ) -> tuple[bool, Token | None, Storage]: """ Returns (reachable?, closing '}', stack).""" @@ -610,7 +627,7 @@ def emit_BlockStmt( self.out.emit(stmt.open) reachable = True for s in stmt.body: - reachable, tkn, storage = self._emit_stmt(s, uop, storage, inst) + reachable, tkn, storage = self._emit_stmt(s, uop, storage, inst, is_abstract) if tkn is not None: self.out.emit(tkn) if not reachable: @@ -627,12 +644,13 @@ def emit_ForStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, + is_abstract: bool, ) -> tuple[bool, Token | None, Storage]: """ Returns (reachable?, closing '}', stack).""" self.out.emit(stmt.for_) for tkn in stmt.header: self.out.emit(tkn) - return self._emit_stmt(stmt.body, uop, storage, inst) + return self._emit_stmt(stmt.body, uop, storage, inst, is_abstract) def emit_WhileStmt( self, @@ -640,12 +658,13 @@ def emit_WhileStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, + is_abstract: bool, ) -> tuple[bool, Token | None, Storage]: """ Returns (reachable?, closing '}', stack).""" self.out.emit(stmt.while_) for tkn in stmt.condition: self.out.emit(tkn) - return self._emit_stmt(stmt.body, uop, storage, inst) + return self._emit_stmt(stmt.body, uop, storage, inst, is_abstract) def emit_tokens( @@ -653,10 +672,11 @@ def emit_tokens( code: CodeSection, storage: Storage, inst: Instruction | None, - emit_braces: bool = True + emit_braces: bool = True, + is_abstract: bool = False, ) -> tuple[bool, Storage]: self.out.start_line() - reachable, tkn, storage = self.emit_BlockStmt(code.body, code, storage, inst, emit_braces) + reachable, tkn, storage = self.emit_BlockStmt(code.body, code, storage, inst, is_abstract, emit_braces) assert tkn is not None try: if reachable: diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 7a32275347e896..4ef1def139715f 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -12,6 +12,8 @@ analyze_files, StackItem, analysis_error, + CodeSection, + Label, ) from generators_common import ( DEFAULT_INPUT, @@ -19,6 +21,8 @@ write_header, Emitter, TokenIterator, + emit_to, + skip_to, ) from cwriter import CWriter from typing import TextIO @@ -111,19 +115,151 @@ def goto_label(self, goto: Token, label: Token, storage: Storage) -> None: self.out.emit(goto) self.out.emit(label) +class OptimizerConstantEmitter(OptimizerEmitter): + def __init__(self, out: CWriter, labels: dict[str, Label]): + super().__init__(out, labels) + overrides = { + "PyStackRef_AsPyObjectBorrow": self.emit_stackref_borrow, + "PyStackRef_CLOSE_SPECIALIZED": self.emit_nothing, + "PyStackRef_CLOSE": self.emit_nothing, + "PyStackRef_FromPyObjectSteal": self.emit_stackref_steal, + "PyStackRef_IsNull": self.emit_stackref_null, + "PyStackRef_IsFalse": self.emit_stackref_isfalse, + "PyStackRef_IsTrue": self.emit_stackref_istrue, + "PyStackRef_False": self.emit_stackref_false, + "PyStackRef_True": self.emit_stackref_true, + "assert": self.emit_nothing, + } + self._replacers = {**self._replacers, **overrides} + + def emit_stackref_borrow( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ): + next(tkn_iter) + self.out.emit(" sym_get_const(ctx, ") + rparen = emit_to(self.out, tkn_iter, "RPAREN") + self.emit(rparen) + return True + + def emit_stackref_steal( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ): + next(tkn_iter) + self.out.emit(" sym_new_const_steal(ctx, ") + rparen = emit_to(self.out, tkn_iter, "RPAREN") + self.emit(rparen) + return True + + def emit_nothing( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ): + while (tkn := next(tkn_iter)).kind != "SEMI": + pass + return True + + def emit_stackref_null( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ): + next(tkn_iter) + self.out.emit(" sym_is_null(") + rparen = emit_to(self.out, tkn_iter, "RPAREN") + self.emit(rparen) + return True + + def emit_stackref_isfalse( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ): + next(tkn_iter) + name = next(tkn_iter) + assert name.kind == "IDENTIFIER", \ + "PyStackRef_IsFalse(target), target must be a simple identifier" + self.out.emit(f"(sym_is_const(ctx, {name.text}) && " + f"Py_IsFalse(sym_get_const(ctx, {name.text})))") + next(tkn_iter) + return True + + def emit_stackref_istrue( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ): + next(tkn_iter) + name = next(tkn_iter) + assert name.kind == "IDENTIFIER", \ + "PyStackRef_IsTrue(target), target must be a simple identifier" + self.out.emit(f"(sym_is_const(ctx, {name.text}_o) && " + f"Py_IsTrue(sym_get_const(ctx, {name.text}_o)))") + next(tkn_iter) + return True + + def emit_stackref_false( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ): + name = tkn + assert name.kind == "IDENTIFIER", \ + "PyStackRef_False must be a simple identifier" + self.out.emit(f"sym_new_const(ctx, Py_False)") + return True + + def emit_stackref_true( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ): + name = tkn + assert name.kind == "IDENTIFIER", \ + "PyStackRef_True must be a simple identifier" + self.out.emit(f"sym_new_const(ctx, Py_True)") + return True + def write_uop( override: Uop | None, uop: Uop, out: CWriter, stack: Stack, debug: bool, - skip_inputs: bool, ) -> None: locals: dict[str, Local] = {} prototype = override if override else uop try: out.start_line() - if override: + if override or uop.properties.pure: storage = Storage.for_uop(stack, prototype, out, check_liveness=False) if debug: args = [] @@ -140,13 +276,29 @@ def write_uop( type = f"uint{cache.size*16}_t " cast = f"uint{cache.size*16}_t" out.emit(f"{type}{cache.name} = ({cast})this_instr->operand0;\n") - if override: - emitter = OptimizerEmitter(out, {}) + if override or uop.properties.pure: # No reference management of inputs needed. for var in storage.inputs: # type: ignore[possibly-undefined] var.in_local = False - _, storage = emitter.emit_tokens(override, storage, None, False) + if uop.properties.pure: + emitter = OptimizerConstantEmitter(out, {}) + emitter.emit("if (\n") + for inp in uop.stack.inputs: + emitter.emit(f"sym_is_const(ctx, {inp.name}) &&\n") + emitter.emit("1) {\n") + _, storage = emitter.emit_tokens(uop, storage, None, False, is_abstract=True) + out.start_line() + emitter.emit("}\n") + emitter.emit("else {\n") + out.start_line() + if override: + emitter = OptimizerEmitter(out, {}) + _, storage = emitter.emit_tokens(override, storage, None, False, is_abstract=True) + else: + emit_default(out, uop, stack) out.start_line() + if uop.properties.pure: + emitter.emit("}\n") storage.flush(out) else: emit_default(out, uop, stack) @@ -193,9 +345,9 @@ def generate_abstract_interpreter( if override: declare_variables(override, out, skip_inputs=False) else: - declare_variables(uop, out, skip_inputs=True) + declare_variables(uop, out, skip_inputs=not uop.properties.pure) stack = Stack(extract_bits=False, cast_type="JitOptSymbol *") - write_uop(override, uop, out, stack, debug, skip_inputs=(override is None)) + write_uop(override, uop, out, stack, debug) out.start_line() out.emit("break;\n") out.emit("}") @@ -207,7 +359,7 @@ def generate_tier2_abstract_from_files( ) -> None: assert len(filenames) == 2, "Need a base file and an abstract cases file." base = analyze_files([filenames[0]]) - abstract = analyze_files([filenames[1]]) + abstract = analyze_files([filenames[1]], abstract=True) with open(outfilename, "w") as outfile: generate_abstract_interpreter(filenames, abstract, base, outfile, debug) From 691084db029fbcafd51d030446e402af45805c13 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 19 Apr 2025 16:22:52 +0000 Subject: [PATCH 02/43] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst new file mode 100644 index 00000000000000..aadaf2169fd01a --- /dev/null +++ b/Misc/NEWS.d/next/Core_and_Builtins/2025-04-19-16-22-47.gh-issue-132732.jgqhlF.rst @@ -0,0 +1 @@ +Automatically constant evaluate bytecode operations marked as pure in the JIT optimizer. From b89e4dc0478fce5a9a36349458c265e681e2fdf8 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sun, 20 Apr 2025 00:41:44 +0800 Subject: [PATCH 03/43] Fix tests --- Lib/test/test_generated_cases.py | 12 ++++++------ Tools/cases_generator/optimizer_generator.py | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 5b120f28131d51..9c29f859caa78b 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -1972,12 +1972,12 @@ def run_cases_test(self, input: str, input2: str, expected: str): def test_overridden_abstract(self): input = """ - pure op(OP, (--)) { + op(OP, (--)) { SPAM(); } """ input2 = """ - pure op(OP, (--)) { + op(OP, (--)) { eggs(); } """ @@ -1991,7 +1991,7 @@ def test_overridden_abstract(self): def test_overridden_abstract_args(self): input = """ - pure op(OP, (arg1 -- out)) { + op(OP, (arg1 -- out)) { out = SPAM(arg1); } op(OP2, (arg1 -- out)) { @@ -2024,16 +2024,16 @@ def test_overridden_abstract_args(self): def test_no_overridden_case(self): input = """ - pure op(OP, (arg1 -- out)) { + op(OP, (arg1 -- out)) { out = SPAM(arg1); } - pure op(OP2, (arg1 -- out)) { + op(OP2, (arg1 -- out)) { } """ input2 = """ - pure op(OP2, (arg1 -- out)) { + op(OP2, (arg1 -- out)) { out = NULL; } """ diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 4ef1def139715f..55d10f12366372 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -359,7 +359,7 @@ def generate_tier2_abstract_from_files( ) -> None: assert len(filenames) == 2, "Need a base file and an abstract cases file." base = analyze_files([filenames[0]]) - abstract = analyze_files([filenames[1]], abstract=True) + abstract = analyze_files([filenames[1]]) with open(outfilename, "w") as outfile: generate_abstract_interpreter(filenames, abstract, base, outfile, debug) From d5b2208a531424b22d53b79230eebe77159df506 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 25 Apr 2025 09:05:11 +0800 Subject: [PATCH 04/43] Apply review suggestions --- Include/internal/pycore_opcode_metadata.h | 4 +- Include/internal/pycore_optimizer.h | 2 + Include/internal/pycore_stackref.h | 26 ++ Include/internal/pycore_uop_metadata.h | 10 +- Python/bytecodes.c | 10 +- Python/optimizer_analysis.c | 2 + Python/optimizer_bytecodes.c | 66 ++--- Python/optimizer_cases.c.h | 267 ++++++++++++------- Python/optimizer_symbols.c | 12 + Tools/cases_generator/generators_common.py | 28 +- Tools/cases_generator/optimizer_generator.py | 199 +++++--------- Tools/cases_generator/stack.py | 1 - 12 files changed, 336 insertions(+), 291 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 7d45f16966a019..88fcf5d801fcf5 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1275,8 +1275,8 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[266] = { [TO_BOOL_LIST] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG }, [TO_BOOL_NONE] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG }, [TO_BOOL_STR] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, - [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, + [UNARY_INVERT] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [UNARY_NEGATIVE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [UNARY_NOT] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, [UNPACK_EX] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [UNPACK_SEQUENCE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index 6fa2337a15f9f8..864213b2ac9379 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -10,6 +10,7 @@ extern "C" { #include "pycore_typedefs.h" // _PyInterpreterFrame #include "pycore_uop_ids.h" +#include "pycore_stackref.h" // _PyStackRef #include @@ -257,6 +258,7 @@ extern bool _Py_uop_sym_is_not_null(JitOptSymbol *sym); extern bool _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym); extern PyObject *_Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym); extern JitOptSymbol *_Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val); +extern _PyStackRef _Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptSymbol *sym); extern JitOptSymbol *_Py_uop_sym_new_unknown(JitOptContext *ctx); extern JitOptSymbol *_Py_uop_sym_new_not_null(JitOptContext *ctx); extern JitOptSymbol *_Py_uop_sym_new_type( diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 5683b98470d3ea..99581f6e14fc2c 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -132,6 +132,15 @@ _PyStackRef_FromPyObjectImmortal(PyObject *obj, const char *filename, int linenu } #define PyStackRef_FromPyObjectImmortal(obj) _PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj), __FILE__, __LINE__) +static inline _PyStackRef +_PyStackRef_FromPyObjectImmortalUnchecked(PyObject *obj, const char *filename, int linenumber) +{ + return _Py_stackref_create(obj, filename, linenumber); +} +#define PyStackRef_FromPyObjectImmortalUnchecked(obj) _PyStackRef_FromPyObjectImmortalUnchecked(_PyObject_CAST(obj), __FILE__, __LINE__) + + + static inline void _PyStackRef_CLOSE(_PyStackRef ref, const char *filename, int linenumber) { @@ -324,6 +333,17 @@ PyStackRef_FromPyObjectImmortal(PyObject *obj) } #define PyStackRef_FromPyObjectImmortal(obj) PyStackRef_FromPyObjectImmortal(_PyObject_CAST(obj)) +static inline _PyStackRef +PyStackRef_FromPyObjectImmortalUnchecked(PyObject *obj) +{ + // Make sure we don't take an already tagged value. + assert(((uintptr_t)obj & Py_TAG_BITS) == 0); + assert(obj != NULL); + return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED }; +} +#define PyStackRef_FromPyObjectImmortalUnchecked(obj) PyStackRef_FromPyObjectImmortalUnchecked(_PyObject_CAST(obj)) + + #define PyStackRef_CLOSE(REF) \ do { \ _PyStackRef _close_tmp = (REF); \ @@ -535,6 +555,12 @@ PyStackRef_FromPyObjectImmortal(PyObject *obj) return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT}; } +static inline _PyStackRef +PyStackRef_FromPyObjectImmortalUnchecked(PyObject *obj) +{ + return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT}; +} + /* WARNING: This macro evaluates its argument more than once */ #ifdef _WIN32 #define PyStackRef_DUP(REF) \ diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 9bc83ac4caf510..8b36103cc4888d 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -67,7 +67,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_PUSH_NULL] = 0, [_END_FOR] = HAS_ESCAPES_FLAG | HAS_NO_SAVE_IP_FLAG, [_END_SEND] = HAS_ESCAPES_FLAG, - [_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_UNARY_NOT] = HAS_PURE_FLAG, [_TO_BOOL] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_TO_BOOL_BOOL] = HAS_EXIT_FLAG, @@ -80,7 +80,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_GUARD_TOS_UNICODE] = HAS_EXIT_FLAG, [_TO_BOOL_STR] = HAS_ESCAPES_FLAG, [_REPLACE_WITH_TRUE] = HAS_ESCAPES_FLAG, - [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_NOS_INT] = HAS_EXIT_FLAG, [_GUARD_TOS_INT] = HAS_EXIT_FLAG, [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, @@ -88,9 +88,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_GUARD_NOS_FLOAT] = HAS_EXIT_FLAG, [_GUARD_TOS_FLOAT] = HAS_EXIT_FLAG, - [_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG, - [_BINARY_OP_ADD_FLOAT] = HAS_ERROR_FLAG, - [_BINARY_OP_SUBTRACT_FLOAT] = HAS_ERROR_FLAG, + [_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_ADD_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_SUBTRACT_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_BINARY_OP_INPLACE_ADD_UNICODE] = HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_BINARY_OP_EXTEND] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 3ff1835ba2e3fe..6c025d2f9d39ac 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -443,7 +443,7 @@ dummy_func( PyStackRef_CLOSE(receiver); } - pure inst(UNARY_NEGATIVE, (value -- res)) { + inst(UNARY_NEGATIVE, (value -- res)) { PyObject *res_o = PyNumber_Negative(PyStackRef_AsPyObjectBorrow(value)); PyStackRef_CLOSE(value); ERROR_IF(res_o == NULL, error); @@ -573,7 +573,7 @@ dummy_func( _GUARD_TYPE_VERSION + _REPLACE_WITH_TRUE; - pure inst(UNARY_INVERT, (value -- res)) { + inst(UNARY_INVERT, (value -- res)) { PyObject *res_o = PyNumber_Invert(PyStackRef_AsPyObjectBorrow(value)); PyStackRef_CLOSE(value); ERROR_IF(res_o == NULL, error); @@ -669,7 +669,7 @@ dummy_func( EXIT_IF(!PyFloat_CheckExact(value_o)); } - op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { + pure op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -684,7 +684,7 @@ dummy_func( ERROR_IF(PyStackRef_IsNull(res), error); } - op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { + pure op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -699,7 +699,7 @@ dummy_func( ERROR_IF(PyStackRef_IsNull(res), error); } - op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { + pure op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index cf56dd806c6f79..43e99d125009da 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -30,6 +30,7 @@ #include "pycore_uop_ids.h" #include "pycore_range.h" #include "pycore_unicodeobject.h" +#include "pycore_ceval.h" #include #include @@ -323,6 +324,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, #define sym_is_const _Py_uop_sym_is_const #define sym_get_const _Py_uop_sym_get_const #define sym_new_const_steal _Py_uop_sym_new_const_steal +#define sym_get_const_as_stackref _Py_uop_sym_get_const_as_stackref #define sym_new_unknown _Py_uop_sym_new_unknown #define sym_new_not_null _Py_uop_sym_new_not_null #define sym_new_type _Py_uop_sym_new_type diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index acad877e0d2466..3a2cdb35efc5a4 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -234,63 +234,27 @@ dummy_func(void) { } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyFloat_CheckExact(sym_get_const(ctx, left))); - assert(PyFloat_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) + - PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); - if (temp == NULL) { - goto error; - } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); - // TODO gh-115506: - // replace opcode with constant propagated one and update tests! - } - else { - res = sym_new_type(ctx, &PyFloat_Type); - } + // We need to tell the cases generator that it's being used by the constant generator. + // We should fix this in the cases generator. + (void)(left); + (void)(right); + res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyFloat_CheckExact(sym_get_const(ctx, left))); - assert(PyFloat_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) - - PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); - if (temp == NULL) { - goto error; - } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); - // TODO gh-115506: - // replace opcode with constant propagated one and update tests! - } - else { - res = sym_new_type(ctx, &PyFloat_Type); - } + // We need to tell the cases generator that it's being used by the constant generator. + // We should fix this in the cases generator. + (void)(left); + (void)(right); + res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyFloat_CheckExact(sym_get_const(ctx, left))); - assert(PyFloat_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) * - PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); - if (temp == NULL) { - goto error; - } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); - // TODO gh-115506: - // replace opcode with constant propagated one and update tests! - } - else { - res = sym_new_type(ctx, &PyFloat_Type); - } + // We need to tell the cases generator that it's being used by the constant generator. + // We should fix this in the cases generator. + (void)(left); + (void)(right); + res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 5f677106401f75..24bdbb476195bf 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -141,21 +141,8 @@ } case _UNARY_NEGATIVE: { - JitOptSymbol *value; JitOptSymbol *res; - value = stack_pointer[-1]; - if ( - sym_is_const(ctx, value) && - 1) { - PyObject *res_o = PyNumber_Negative( sym_get_const(ctx, value)); - if (res_o == NULL) { - JUMP_TO_LABEL(pop_1_error); - } - res = sym_new_const_steal(ctx, res_o); - } - else { - res = sym_new_not_null(ctx); - } + res = sym_new_not_null(ctx); stack_pointer[-1] = res; break; } @@ -167,13 +154,20 @@ if ( sym_is_const(ctx, value) && 1) { - res =(sym_is_const(ctx, value) && Py_IsFalse(sym_get_const(ctx, value)))?sym_new_const(ctx, Py_True):sym_new_const(ctx, Py_False); + JitOptSymbol *value_sym = value; + _PyStackRef value = sym_get_const_as_stackref(ctx, value_sym); + _PyStackRef res_stackref; + assert(PyStackRef_BoolCheck(value)); + res_stackref= PyStackRef_IsFalse(value) + ? PyStackRef_True : PyStackRef_False; + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-1] = res; } else { sym_set_type(value, &PyBool_Type); res = sym_new_truthiness(ctx, value, false); + stack_pointer[-1] = res; } - stack_pointer[-1] = res; break; } @@ -301,21 +295,8 @@ } case _UNARY_INVERT: { - JitOptSymbol *value; JitOptSymbol *res; - value = stack_pointer[-1]; - if ( - sym_is_const(ctx, value) && - 1) { - PyObject *res_o = PyNumber_Invert( sym_get_const(ctx, value)); - if (res_o == NULL) { - JUMP_TO_LABEL(pop_1_error); - } - res = sym_new_const_steal(ctx, res_o); - } - else { - res = sym_new_not_null(ctx); - } + res = sym_new_not_null(ctx); stack_pointer[-1] = res; break; } @@ -350,23 +331,36 @@ sym_is_const(ctx, left) && sym_is_const(ctx, right) && 1) { - PyObject *left_o = sym_get_const(ctx, left); - PyObject *right_o = sym_get_const(ctx, right); + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyLong_CheckExact(left_o)); + assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o); + PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const_steal(ctx, res_o); + res_stackref= PyStackRef_FromPyObjectSteal(res_o); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } else { (void)(left); (void)(right); res = sym_new_type(ctx, &PyLong_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -380,23 +374,36 @@ sym_is_const(ctx, left) && sym_is_const(ctx, right) && 1) { - PyObject *left_o = sym_get_const(ctx, left); - PyObject *right_o = sym_get_const(ctx, right); + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyLong_CheckExact(left_o)); + assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o); + PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const_steal(ctx, res_o); + res_stackref= PyStackRef_FromPyObjectSteal(res_o); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } else { (void)(left); (void)(right); res = sym_new_type(ctx, &PyLong_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -410,23 +417,36 @@ sym_is_const(ctx, left) && sym_is_const(ctx, right) && 1) { - PyObject *left_o = sym_get_const(ctx, left); - PyObject *right_o = sym_get_const(ctx, right); + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyLong_CheckExact(left_o)); + assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o); + PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const_steal(ctx, res_o); + res_stackref= PyStackRef_FromPyObjectSteal(res_o); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } else { (void)(left); (void)(right); res = sym_new_type(ctx, &PyLong_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -456,24 +476,40 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyFloat_CheckExact(sym_get_const(ctx, left))); - assert(PyFloat_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) * - PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); - if (temp == NULL) { - goto error; + if ( + sym_is_const(ctx, left) && + sym_is_const(ctx, right) && + 1) { + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyFloat_CheckExact(left_o)); + assert(PyFloat_CheckExact(right_o)); + STAT_INC(BINARY_OP, hit); + double dres = + ((PyFloatObject *)left_o)->ob_fval * + ((PyFloatObject *)right_o)->ob_fval; + res_stackref= _PyFloat_FromDouble_ConsumeInputs(left, right, dres); + if (PyStackRef_IsNull(res_stackref)) { + JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } else { + (void)(left); + (void)(right); res = sym_new_type(ctx, &PyFloat_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -483,24 +519,40 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyFloat_CheckExact(sym_get_const(ctx, left))); - assert(PyFloat_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) + - PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); - if (temp == NULL) { - goto error; + if ( + sym_is_const(ctx, left) && + sym_is_const(ctx, right) && + 1) { + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyFloat_CheckExact(left_o)); + assert(PyFloat_CheckExact(right_o)); + STAT_INC(BINARY_OP, hit); + double dres = + ((PyFloatObject *)left_o)->ob_fval + + ((PyFloatObject *)right_o)->ob_fval; + res_stackref= _PyFloat_FromDouble_ConsumeInputs(left, right, dres); + if (PyStackRef_IsNull(res_stackref)) { + JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } else { + (void)(left); + (void)(right); res = sym_new_type(ctx, &PyFloat_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -510,24 +562,40 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { - assert(PyFloat_CheckExact(sym_get_const(ctx, left))); - assert(PyFloat_CheckExact(sym_get_const(ctx, right))); - PyObject *temp = PyFloat_FromDouble( - PyFloat_AS_DOUBLE(sym_get_const(ctx, left)) - - PyFloat_AS_DOUBLE(sym_get_const(ctx, right))); - if (temp == NULL) { - goto error; + if ( + sym_is_const(ctx, left) && + sym_is_const(ctx, right) && + 1) { + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyFloat_CheckExact(left_o)); + assert(PyFloat_CheckExact(right_o)); + STAT_INC(BINARY_OP, hit); + double dres = + ((PyFloatObject *)left_o)->ob_fval - + ((PyFloatObject *)right_o)->ob_fval; + res_stackref= _PyFloat_FromDouble_ConsumeInputs(left, right, dres); + if (PyStackRef_IsNull(res_stackref)) { + JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const(ctx, temp); - Py_DECREF(temp); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } else { + (void)(left); + (void)(right); res = sym_new_type(ctx, &PyFloat_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } @@ -541,23 +609,36 @@ sym_is_const(ctx, left) && sym_is_const(ctx, right) && 1) { - PyObject *left_o = sym_get_const(ctx, left); - PyObject *right_o = sym_get_const(ctx, right); + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyUnicode_CheckExact(left_o)); + assert(PyUnicode_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); PyObject *res_o = PyUnicode_Concat(left_o, right_o); + PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); if (res_o == NULL) { JUMP_TO_LABEL(pop_2_error); } - res = sym_new_const_steal(ctx, res_o); + res_stackref= PyStackRef_FromPyObjectSteal(res_o); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } else { (void)(left); (void)(right); res = sym_new_type(ctx, &PyUnicode_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); } - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); break; } diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index c0fe70a667830a..ccebd00688c72a 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -147,6 +147,18 @@ _Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym) return NULL; } +_PyStackRef +_Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptSymbol *sym) +{ + PyObject *const_val = _Py_uop_sym_get_const(ctx, sym); + if (const_val == NULL) { + return PyStackRef_NULL; + } + // This is actually more like a borrow, but it doesn't matter here. + // Eventually we discard the stackref anyways. + return PyStackRef_FromPyObjectImmortalUnchecked(const_val); +} + void _Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeObject *typ) { diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 5e8f64f1e70943..f1c6613557d57e 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -142,6 +142,30 @@ def __init__(self, out: CWriter, labels: dict[str, Label]): self.out = out self.labels = labels + def emit_to_with_replacement( + self, + out: CWriter, + tkn_iter: TokenIterator, + end: str, + uop: CodeSection, + storage: Storage, + inst: Instruction | None + ) -> Token: + parens = 0 + for tkn in tkn_iter: + if tkn.kind == end and parens == 0: + return tkn + if tkn.kind == "LPAREN": + parens += 1 + if tkn.kind == "RPAREN": + parens -= 1 + if tkn.text in self._replacers: + self._replacers[tkn.text](tkn, tkn_iter, uop, storage, inst) + else: + out.emit(tkn) + raise analysis_error(f"Expecting {end}. Reached end of file", tkn) + + def dispatch( self, tkn: Token, @@ -168,7 +192,7 @@ def deopt_if( lparen = next(tkn_iter) assert lparen.kind == "LPAREN" first_tkn = tkn_iter.peek() - emit_to(self.out, tkn_iter, "RPAREN") + self.emit_to_with_replacement(self.out, tkn_iter, "RPAREN", uop, storage, inst) self.emit(") {\n") next(tkn_iter) # Semi colon assert inst is not None @@ -210,7 +234,7 @@ def error_if( else: self.out.emit_at("if ", tkn) self.emit(lparen) - emit_to(self.out, tkn_iter, "COMMA") + self.emit_to_with_replacement(self.out, tkn_iter, "COMMA", uop, storage, inst) self.out.emit(") {\n") label = next(tkn_iter).text next(tkn_iter) # RPAREN diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 55d10f12366372..d7a41902e82177 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -4,6 +4,7 @@ """ import argparse +import copy from analyzer import ( Analysis, @@ -25,7 +26,7 @@ skip_to, ) from cwriter import CWriter -from typing import TextIO +from typing import TextIO, Callable from lexer import Token from stack import Local, Stack, StackError, Storage @@ -45,6 +46,12 @@ def type_name(var: StackItem) -> str: return var.type return f"JitOptSymbol *" +def stackref_type_name(var: StackItem) -> str: + if var.is_array(): + assert False, "Unsafe to convert a symbol to an array-like StackRef." + if var.type: + return var.type + return f"_PyStackRef " def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: variables = {"unused"} @@ -115,148 +122,85 @@ def goto_label(self, goto: Token, label: Token, storage: Storage) -> None: self.out.emit(goto) self.out.emit(label) + class OptimizerConstantEmitter(OptimizerEmitter): - def __init__(self, out: CWriter, labels: dict[str, Label]): + def __init__(self, out: CWriter, labels: dict[str, Label], uop: Uop): super().__init__(out, labels) + # Replace all outputs to point to their stackref versions. overrides = { - "PyStackRef_AsPyObjectBorrow": self.emit_stackref_borrow, - "PyStackRef_CLOSE_SPECIALIZED": self.emit_nothing, - "PyStackRef_CLOSE": self.emit_nothing, - "PyStackRef_FromPyObjectSteal": self.emit_stackref_steal, - "PyStackRef_IsNull": self.emit_stackref_null, - "PyStackRef_IsFalse": self.emit_stackref_isfalse, - "PyStackRef_IsTrue": self.emit_stackref_istrue, - "PyStackRef_False": self.emit_stackref_false, - "PyStackRef_True": self.emit_stackref_true, - "assert": self.emit_nothing, + outp.name: self.emit_stackref_override for outp in uop.stack.outputs } self._replacers = {**self._replacers, **overrides} - def emit_stackref_borrow( - self, - tkn: Token, - tkn_iter: TokenIterator, - uop: CodeSection, - storage: Storage, - inst: Instruction | None, - ): - next(tkn_iter) - self.out.emit(" sym_get_const(ctx, ") - rparen = emit_to(self.out, tkn_iter, "RPAREN") - self.emit(rparen) - return True - - def emit_stackref_steal( - self, - tkn: Token, - tkn_iter: TokenIterator, - uop: CodeSection, - storage: Storage, - inst: Instruction | None, - ): - next(tkn_iter) - self.out.emit(" sym_new_const_steal(ctx, ") - rparen = emit_to(self.out, tkn_iter, "RPAREN") - self.emit(rparen) - return True - - def emit_nothing( - self, - tkn: Token, - tkn_iter: TokenIterator, - uop: CodeSection, - storage: Storage, - inst: Instruction | None, - ): - while (tkn := next(tkn_iter)).kind != "SEMI": - pass - return True - - def emit_stackref_null( - self, - tkn: Token, - tkn_iter: TokenIterator, - uop: CodeSection, - storage: Storage, - inst: Instruction | None, - ): - next(tkn_iter) - self.out.emit(" sym_is_null(") - rparen = emit_to(self.out, tkn_iter, "RPAREN") - self.emit(rparen) - return True - - def emit_stackref_isfalse( + def emit_stackref_override( self, tkn: Token, tkn_iter: TokenIterator, uop: CodeSection, storage: Storage, inst: Instruction | None, - ): - next(tkn_iter) - name = next(tkn_iter) - assert name.kind == "IDENTIFIER", \ - "PyStackRef_IsFalse(target), target must be a simple identifier" - self.out.emit(f"(sym_is_const(ctx, {name.text}) && " - f"Py_IsFalse(sym_get_const(ctx, {name.text})))") - next(tkn_iter) + ) -> bool: + self.out.emit(tkn) + self.out.emit("_stackref") return True - def emit_stackref_istrue( - self, - tkn: Token, - tkn_iter: TokenIterator, - uop: CodeSection, - storage: Storage, - inst: Instruction | None, - ): - next(tkn_iter) - name = next(tkn_iter) - assert name.kind == "IDENTIFIER", \ - "PyStackRef_IsTrue(target), target must be a simple identifier" - self.out.emit(f"(sym_is_const(ctx, {name.text}_o) && " - f"Py_IsTrue(sym_get_const(ctx, {name.text}_o)))") - next(tkn_iter) - return True - - def emit_stackref_false( - self, - tkn: Token, - tkn_iter: TokenIterator, - uop: CodeSection, - storage: Storage, - inst: Instruction | None, - ): - name = tkn - assert name.kind == "IDENTIFIER", \ - "PyStackRef_False must be a simple identifier" - self.out.emit(f"sym_new_const(ctx, Py_False)") - return True +def write_uop_pure_evaluation_region_header( + uop: Uop, + out: CWriter, + stack: Stack, +) -> None: + emitter = OptimizerConstantEmitter(out, {}, uop) + emitter.emit("if (\n") + assert len(uop.stack.inputs) > 0, "Pure operations must have at least 1 input" + for inp in uop.stack.inputs: + emitter.emit(f"sym_is_const(ctx, {inp.name}) &&\n") + emitter.emit("1) {\n") + # Declare variables, before they are shadowed. + for inp in uop.stack.inputs: + if inp.used: + emitter.emit(f"{type_name(inp)}{inp.name}_sym = {inp.name};\n") + # Shadow the symbolic variables with stackrefs. + for inp in uop.stack.inputs: + if inp.used: + emitter.emit(f"{stackref_type_name(inp)}{inp.name} = sym_get_const_as_stackref(ctx, {inp.name}_sym);\n") + # Rename all output variables to stackref variant. + for outp in uop.stack.outputs: + assert not outp.is_array(), "Array output StackRefs not supported for pure ops." + emitter.emit(f"_PyStackRef {outp.name}_stackref;\n") + stack = copy.deepcopy(stack) + + storage = Storage.for_uop(stack, uop, CWriter.null(), check_liveness=False) + # No reference management of outputs needed. + for var in storage.outputs: + var.in_local = True + emitter.emit_tokens(uop, storage, None, False, is_abstract=True) + out.start_line() + # Finally, assign back the output stackrefs to symbolics. + for outp in uop.stack.outputs: + # All new stackrefs are created from new references. + # That's how the stackref contract works. + if not outp.peek: + out.emit(f"{outp.name} = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") + else: + out.emit(f"{outp.name} = sym_new_const(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") + storage.flush(out) + emitter.emit("}\n") + emitter.emit("else {\n") - def emit_stackref_true( - self, - tkn: Token, - tkn_iter: TokenIterator, - uop: CodeSection, - storage: Storage, - inst: Instruction | None, - ): - name = tkn - assert name.kind == "IDENTIFIER", \ - "PyStackRef_True must be a simple identifier" - self.out.emit(f"sym_new_const(ctx, Py_True)") - return True +def write_uop_pure_evaluation_region_footer( + out: CWriter, +) -> None: + out.emit("}\n") def write_uop( override: Uop | None, uop: Uop, out: CWriter, - stack: Stack, debug: bool, ) -> None: locals: dict[str, Local] = {} prototype = override if override else uop + stack = Stack(extract_bits=False, cast_type="JitOptSymbol *") try: out.start_line() if override or uop.properties.pure: @@ -281,25 +225,17 @@ def write_uop( for var in storage.inputs: # type: ignore[possibly-undefined] var.in_local = False if uop.properties.pure: - emitter = OptimizerConstantEmitter(out, {}) - emitter.emit("if (\n") - for inp in uop.stack.inputs: - emitter.emit(f"sym_is_const(ctx, {inp.name}) &&\n") - emitter.emit("1) {\n") - _, storage = emitter.emit_tokens(uop, storage, None, False, is_abstract=True) - out.start_line() - emitter.emit("}\n") - emitter.emit("else {\n") + write_uop_pure_evaluation_region_header(uop, out, stack) out.start_line() if override: emitter = OptimizerEmitter(out, {}) _, storage = emitter.emit_tokens(override, storage, None, False, is_abstract=True) + storage.flush(out) else: emit_default(out, uop, stack) out.start_line() if uop.properties.pure: - emitter.emit("}\n") - storage.flush(out) + write_uop_pure_evaluation_region_footer(out) else: emit_default(out, uop, stack) out.start_line() @@ -346,8 +282,7 @@ def generate_abstract_interpreter( declare_variables(override, out, skip_inputs=False) else: declare_variables(uop, out, skip_inputs=not uop.properties.pure) - stack = Stack(extract_bits=False, cast_type="JitOptSymbol *") - write_uop(override, uop, out, stack, debug) + write_uop(override, uop, out, debug) out.start_line() out.emit("break;\n") out.emit("}") diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index 6b681775f48c81..f4b623d153fd9d 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -557,7 +557,6 @@ def for_uop(stack: Stack, uop: Uop, out: CWriter, check_liveness: bool = True) - stack.push(var) outputs = peeks + [ Local.undefined(var) for var in uop.stack.outputs if not var.peek ] return Storage(stack, inputs, outputs, len(peeks), check_liveness) - @staticmethod def copy_list(arg: list[Local]) -> list[Local]: return [ l.copy() for l in arg ] From 71ced861d851b42c1e747f51a0fb5fc16e470014 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 25 Apr 2025 09:09:06 +0800 Subject: [PATCH 05/43] reduce diff --- Tools/cases_generator/stack.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/cases_generator/stack.py b/Tools/cases_generator/stack.py index f4b623d153fd9d..6b681775f48c81 100644 --- a/Tools/cases_generator/stack.py +++ b/Tools/cases_generator/stack.py @@ -557,6 +557,7 @@ def for_uop(stack: Stack, uop: Uop, out: CWriter, check_liveness: bool = True) - stack.push(var) outputs = peeks + [ Local.undefined(var) for var in uop.stack.outputs if not var.peek ] return Storage(stack, inputs, outputs, len(peeks), check_liveness) + @staticmethod def copy_list(arg: list[Local]) -> list[Local]: return [ l.copy() for l in arg ] From d22f1650203a50f6a68dc25577a5263e9339470b Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 7 May 2025 06:48:20 +0800 Subject: [PATCH 06/43] Update pycore_opcode_metadata.h --- Include/internal/pycore_opcode_metadata.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index fae075895367ef..28ee42cae59bfe 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1305,7 +1305,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [ANNOTATIONS_PLACEHOLDER] = { true, -1, HAS_PURE_FLAG }, + [ANNOTATIONS_PLACEHOLDER] = { true, -1, 0 }, [JUMP] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_IF_FALSE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_IF_TRUE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, From 8ae38c794acc16eeea611bf8a7e78ef31197b375 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sat, 10 May 2025 12:06:36 +0800 Subject: [PATCH 07/43] Apply changes from code review Co-Authored-By: Tomas R. --- Python/optimizer_analysis.c | 1 - Python/optimizer_cases.c.h | 54 ++++++++++---------- Tools/cases_generator/optimizer_generator.py | 7 +-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index b62ca5c61727e5..c3cbb1fa24c29c 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -519,7 +519,6 @@ optimize_uops( return trace_len; pop_2_error: -pop_1_error: error: DPRINTF(3, "\n"); DPRINTF(1, "Encountered error in abstract interpreter\n"); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index d61b431728ba2d..03f1d234f28375 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -152,13 +152,13 @@ JitOptSymbol *res; value = stack_pointer[-1]; if ( - sym_is_const(ctx, value) && - 1) { + sym_is_const(ctx, value) + ) { JitOptSymbol *value_sym = value; _PyStackRef value = sym_get_const_as_stackref(ctx, value_sym); _PyStackRef res_stackref; assert(PyStackRef_BoolCheck(value)); - res_stackref= PyStackRef_IsFalse(value) + res_stackref = PyStackRef_IsFalse(value) ? PyStackRef_True : PyStackRef_False; res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-1] = res; @@ -333,8 +333,8 @@ left = stack_pointer[-2]; if ( sym_is_const(ctx, left) && - sym_is_const(ctx, right) && - 1) { + sym_is_const(ctx, right) + ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); @@ -351,7 +351,7 @@ if (res_o == NULL) { JUMP_TO_LABEL(pop_2_error); } - res_stackref= PyStackRef_FromPyObjectSteal(res_o); + res_stackref = PyStackRef_FromPyObjectSteal(res_o); res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -376,8 +376,8 @@ left = stack_pointer[-2]; if ( sym_is_const(ctx, left) && - sym_is_const(ctx, right) && - 1) { + sym_is_const(ctx, right) + ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); @@ -394,7 +394,7 @@ if (res_o == NULL) { JUMP_TO_LABEL(pop_2_error); } - res_stackref= PyStackRef_FromPyObjectSteal(res_o); + res_stackref = PyStackRef_FromPyObjectSteal(res_o); res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -419,8 +419,8 @@ left = stack_pointer[-2]; if ( sym_is_const(ctx, left) && - sym_is_const(ctx, right) && - 1) { + sym_is_const(ctx, right) + ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); @@ -437,7 +437,7 @@ if (res_o == NULL) { JUMP_TO_LABEL(pop_2_error); } - res_stackref= PyStackRef_FromPyObjectSteal(res_o); + res_stackref = PyStackRef_FromPyObjectSteal(res_o); res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -482,8 +482,8 @@ left = stack_pointer[-2]; if ( sym_is_const(ctx, left) && - sym_is_const(ctx, right) && - 1) { + sym_is_const(ctx, right) + ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); @@ -497,8 +497,8 @@ double dres = ((PyFloatObject *)left_o)->ob_fval * ((PyFloatObject *)right_o)->ob_fval; - res_stackref= _PyFloat_FromDouble_ConsumeInputs(left, right, dres); - if (PyStackRef_IsNull(res_stackref)) { + res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres); + if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); @@ -525,8 +525,8 @@ left = stack_pointer[-2]; if ( sym_is_const(ctx, left) && - sym_is_const(ctx, right) && - 1) { + sym_is_const(ctx, right) + ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); @@ -540,8 +540,8 @@ double dres = ((PyFloatObject *)left_o)->ob_fval + ((PyFloatObject *)right_o)->ob_fval; - res_stackref= _PyFloat_FromDouble_ConsumeInputs(left, right, dres); - if (PyStackRef_IsNull(res_stackref)) { + res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres); + if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); @@ -568,8 +568,8 @@ left = stack_pointer[-2]; if ( sym_is_const(ctx, left) && - sym_is_const(ctx, right) && - 1) { + sym_is_const(ctx, right) + ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); @@ -583,8 +583,8 @@ double dres = ((PyFloatObject *)left_o)->ob_fval - ((PyFloatObject *)right_o)->ob_fval; - res_stackref= _PyFloat_FromDouble_ConsumeInputs(left, right, dres); - if (PyStackRef_IsNull(res_stackref)) { + res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres); + if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); @@ -611,8 +611,8 @@ left = stack_pointer[-2]; if ( sym_is_const(ctx, left) && - sym_is_const(ctx, right) && - 1) { + sym_is_const(ctx, right) + ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); @@ -629,7 +629,7 @@ if (res_o == NULL) { JUMP_TO_LABEL(pop_2_error); } - res_stackref= PyStackRef_FromPyObjectSteal(res_o); + res_stackref = PyStackRef_FromPyObjectSteal(res_o); res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index d7a41902e82177..7ddeb98e2231e6 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -141,7 +141,7 @@ def emit_stackref_override( inst: Instruction | None, ) -> bool: self.out.emit(tkn) - self.out.emit("_stackref") + self.out.emit("_stackref ") return True def write_uop_pure_evaluation_region_header( @@ -152,9 +152,10 @@ def write_uop_pure_evaluation_region_header( emitter = OptimizerConstantEmitter(out, {}, uop) emitter.emit("if (\n") assert len(uop.stack.inputs) > 0, "Pure operations must have at least 1 input" - for inp in uop.stack.inputs: + for inp in uop.stack.inputs[:-1]: emitter.emit(f"sym_is_const(ctx, {inp.name}) &&\n") - emitter.emit("1) {\n") + emitter.emit(f"sym_is_const(ctx, {uop.stack.inputs[-1].name})\n") + emitter.emit(') {\n') # Declare variables, before they are shadowed. for inp in uop.stack.inputs: if inp.used: From dc2d92254da8d3b26fddd4cbb8710cf5f2b96e25 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Sun, 11 May 2025 00:36:08 +0800 Subject: [PATCH 08/43] Address review, add test --- Lib/test/test_generated_cases.py | 35 ++++++++++++++++++++ Python/optimizer_cases.c.h | 16 +++++++++ Tools/cases_generator/generators_common.py | 13 -------- Tools/cases_generator/optimizer_generator.py | 19 ++++++----- 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index c94015d522e56c..2599f9a78cbd01 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2253,5 +2253,40 @@ def test_validate_uop_unused_size_mismatch(self): "Inputs must have equal sizes"): self.run_cases_test(input, input2, output) + def test_pure_uop_body_copied_in(self): + input = """ + pure op(OP, (foo -- res)) { + res = body(foo); + } + """ + input2 = """ + op(OP, (foo -- res)) { + res = sym_new_unknown(ctx); + } + """ + output = """ + case OP: { + JitOptSymbol *res; + if ( + sym_is_const(ctx, foo) + ) { + JitOptSymbol *foo_sym = foo; + _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); + _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ + res_stackref = body(foo); + /* End of pure uop copied from bytecodes for constant evaluation */ + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-1] = res; + } + else { + res = sym_new_unknown(ctx); + stack_pointer[-1] = res; + } + break; + } + """ + self.run_cases_test(input, input2, output) + if __name__ == "__main__": unittest.main() diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index f78d03d3701336..79dde14c60d846 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -157,9 +157,11 @@ JitOptSymbol *value_sym = value; _PyStackRef value = sym_get_const_as_stackref(ctx, value_sym); _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ assert(PyStackRef_BoolCheck(value)); res_stackref = PyStackRef_IsFalse(value) ? PyStackRef_True : PyStackRef_False; + /* End of pure uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-1] = res; } @@ -339,6 +341,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyLong_CheckExact(left_o)); @@ -351,6 +354,7 @@ JUMP_TO_LABEL(pop_2_error); } res_stackref = PyStackRef_FromPyObjectSteal(res_o); + /* End of pure uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -382,6 +386,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyLong_CheckExact(left_o)); @@ -394,6 +399,7 @@ JUMP_TO_LABEL(pop_2_error); } res_stackref = PyStackRef_FromPyObjectSteal(res_o); + /* End of pure uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -425,6 +431,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyLong_CheckExact(left_o)); @@ -437,6 +444,7 @@ JUMP_TO_LABEL(pop_2_error); } res_stackref = PyStackRef_FromPyObjectSteal(res_o); + /* End of pure uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -488,6 +496,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -500,6 +509,7 @@ if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } + /* End of pure uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -531,6 +541,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -543,6 +554,7 @@ if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } + /* End of pure uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -574,6 +586,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -586,6 +599,7 @@ if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } + /* End of pure uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -617,6 +631,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyUnicode_CheckExact(left_o)); @@ -629,6 +644,7 @@ JUMP_TO_LABEL(pop_2_error); } res_stackref = PyStackRef_FromPyObjectSteal(res_o); + /* End of pure uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 28576a3be16158..253c1d38285f5e 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -75,19 +75,6 @@ def write_header( """ ) -def skip_to(tkn_iter: TokenIterator, end: str) -> Token: - tkn = None - parens = 0 - for tkn in tkn_iter: - if tkn.kind == end and parens == 0: - return tkn - if tkn.kind == "LPAREN": - parens += 1 - if tkn.kind == "RPAREN": - parens -= 1 - assert tkn is not None - return tkn - def emit_to(out: CWriter, tkn_iter: TokenIterator, end: str) -> Token: parens = 0 for tkn in tkn_iter: diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index e673b017c3c6ef..939e8339ea70c8 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -22,11 +22,9 @@ write_header, Emitter, TokenIterator, - emit_to, - skip_to, ) from cwriter import CWriter -from typing import TextIO, Callable +from typing import TextIO from lexer import Token from stack import Local, Stack, StackError, Storage @@ -87,7 +85,7 @@ def stackref_type_name(var: StackItem) -> str: assert False, "Unsafe to convert a symbol to an array-like StackRef." if var.type: return var.type - return f"_PyStackRef " + return "_PyStackRef " def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: variables = {"unused"} @@ -210,17 +208,19 @@ def write_uop_pure_evaluation_region_header( # No reference management of outputs needed. for var in storage.outputs: var.in_local = True - emitter.emit_tokens(uop, storage, None, False, is_abstract=True) + emitter.emit("/* Start of pure uop copied from bytecodes for constant evaluation */\n") + emitter.emit_tokens(uop, storage, inst=None, emit_braces=False, is_abstract=True) out.start_line() + emitter.emit("/* End of pure uop copied from bytecodes for constant evaluation */\n") # Finally, assign back the output stackrefs to symbolics. for outp in uop.stack.outputs: # All new stackrefs are created from new references. # That's how the stackref contract works. if not outp.peek: - out.emit(f"{outp.name} = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") + emitter.emit(f"{outp.name} = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") else: - out.emit(f"{outp.name} = sym_new_const(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") - storage.flush(out) + emitter.emit(f"{outp.name} = sym_new_const(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") + storage.flush(out) emitter.emit("}\n") emitter.emit("else {\n") @@ -266,7 +266,8 @@ def write_uop( out.start_line() if override: emitter = OptimizerEmitter(out, {}) - _, storage = emitter.emit_tokens(override, storage, None, False, is_abstract=True) + _, storage = emitter.emit_tokens(override, storage, inst=None, + emit_braces=False, is_abstract=True) storage.flush(out) else: emit_default(out, uop, stack) From f3f2a691126f53a66fff6b4d222174be59196ab3 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Mon, 12 May 2025 08:50:11 +0800 Subject: [PATCH 09/43] Add more tests --- Lib/test/test_generated_cases.py | 67 ++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 2599f9a78cbd01..d9235d1ea76b54 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2288,5 +2288,72 @@ def test_pure_uop_body_copied_in(self): """ self.run_cases_test(input, input2, output) + def test_pure_uop_body_copied_in_complex(self): + input = """ + pure op(OP, (foo -- res)) { + if (foo) { + res = body(foo); + } + else { + res = 1; + } + } + """ + input2 = """ + op(OP, (foo -- res)) { + res = sym_new_unknown(ctx); + } + """ + output = """ + case OP: { + JitOptSymbol *res; + if ( + sym_is_const(ctx, foo) + ) { + JitOptSymbol *foo_sym = foo; + _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); + _PyStackRef res_stackref; + /* Start of pure uop copied from bytecodes for constant evaluation */ + if (foo) { + res_stackref = body(foo); + } + else { + res_stackref = 1; + } + /* End of pure uop copied from bytecodes for constant evaluation */ + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + stack_pointer[-1] = res; + } + else { + res = sym_new_unknown(ctx); + stack_pointer[-1] = res; + } + break; + } + """ + self.run_cases_test(input, input2, output) + + def test_pure_uop_reject_array_effects(self): + input = """ + pure op(OP, (foo[2] -- res)) { + if (foo) { + res = body(foo); + } + else { + res = 1; + } + } + """ + input2 = """ + op(OP, (foo[2] -- res)) { + res = sym_new_unknown(ctx); + } + """ + output = """ + """ + with self.assertRaisesRegex(AssertionError, + "Unsafe to convert a symbol to an array-like StackRef."): + self.run_cases_test(input, input2, output) + if __name__ == "__main__": unittest.main() From 53ce10ffbe9e2bb45f7e7582668091a8a22326b9 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Mon, 12 May 2025 08:54:08 +0800 Subject: [PATCH 10/43] Fix tests --- Lib/test/test_generated_cases.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index d9235d1ea76b54..d7832c01768e88 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2261,12 +2261,14 @@ def test_pure_uop_body_copied_in(self): """ input2 = """ op(OP, (foo -- res)) { - res = sym_new_unknown(ctx); + res = sym_new_known(ctx, foo); } """ output = """ case OP: { + JitOptSymbol *foo; JitOptSymbol *res; + foo = stack_pointer[-1]; if ( sym_is_const(ctx, foo) ) { @@ -2280,7 +2282,7 @@ def test_pure_uop_body_copied_in(self): stack_pointer[-1] = res; } else { - res = sym_new_unknown(ctx); + res = sym_new_known(ctx, foo); stack_pointer[-1] = res; } break; @@ -2301,12 +2303,14 @@ def test_pure_uop_body_copied_in_complex(self): """ input2 = """ op(OP, (foo -- res)) { - res = sym_new_unknown(ctx); + res = sym_new_known(ctx, foo); } """ output = """ case OP: { + JitOptSymbol *foo; JitOptSymbol *res; + foo = stack_pointer[-1]; if ( sym_is_const(ctx, foo) ) { @@ -2325,7 +2329,7 @@ def test_pure_uop_body_copied_in_complex(self): stack_pointer[-1] = res; } else { - res = sym_new_unknown(ctx); + res = sym_new_known(ctx, foo); stack_pointer[-1] = res; } break; From 17634a8f716421b7b9adc69358b76e1691d3fcab Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Mon, 19 May 2025 23:52:13 +0800 Subject: [PATCH 11/43] Push fix noticed by Mark and Brandt --- Include/internal/pycore_optimizer.h | 1 + Lib/test/test_generated_cases.py | 4 +-- Python/optimizer_analysis.c | 1 + Python/optimizer_cases.c.h | 30 ++++++++++---------- Python/optimizer_symbols.c | 20 +++++++++++++ Tools/cases_generator/optimizer_generator.py | 4 +-- 6 files changed, 41 insertions(+), 19 deletions(-) diff --git a/Include/internal/pycore_optimizer.h b/Include/internal/pycore_optimizer.h index f6f45291f9494b..ea5650f7fd756d 100644 --- a/Include/internal/pycore_optimizer.h +++ b/Include/internal/pycore_optimizer.h @@ -260,6 +260,7 @@ typedef struct _JitOptContext { extern bool _Py_uop_sym_is_null(JitOptSymbol *sym); extern bool _Py_uop_sym_is_not_null(JitOptSymbol *sym); extern bool _Py_uop_sym_is_const(JitOptContext *ctx, JitOptSymbol *sym); +extern bool _Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptSymbol *sym); extern PyObject *_Py_uop_sym_get_const(JitOptContext *ctx, JitOptSymbol *sym); extern JitOptSymbol *_Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val); extern _PyStackRef _Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptSymbol *sym); diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index d7832c01768e88..54be059c9f1bf6 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2270,7 +2270,7 @@ def test_pure_uop_body_copied_in(self): JitOptSymbol *res; foo = stack_pointer[-1]; if ( - sym_is_const(ctx, foo) + sym_is_safe_const(ctx, foo) ) { JitOptSymbol *foo_sym = foo; _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); @@ -2312,7 +2312,7 @@ def test_pure_uop_body_copied_in_complex(self): JitOptSymbol *res; foo = stack_pointer[-1]; if ( - sym_is_const(ctx, foo) + sym_is_safe_const(ctx, foo) ) { JitOptSymbol *foo_sym = foo; _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index c3cbb1fa24c29c..779d680aad5621 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -319,6 +319,7 @@ remove_globals(_PyInterpreterFrame *frame, _PyUOpInstruction *buffer, /* Shortened forms for convenience, used in optimizer_bytecodes.c */ #define sym_is_not_null _Py_uop_sym_is_not_null #define sym_is_const _Py_uop_sym_is_const +#define sym_is_safe_const _Py_uop_sym_is_safe_const #define sym_get_const _Py_uop_sym_get_const #define sym_new_const_steal _Py_uop_sym_new_const_steal #define sym_get_const_as_stackref _Py_uop_sym_get_const_as_stackref diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 79dde14c60d846..2454fc7f189bcc 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -152,7 +152,7 @@ JitOptSymbol *res; value = stack_pointer[-1]; if ( - sym_is_const(ctx, value) + sym_is_safe_const(ctx, value) ) { JitOptSymbol *value_sym = value; _PyStackRef value = sym_get_const_as_stackref(ctx, value_sym); @@ -333,8 +333,8 @@ right = stack_pointer[-1]; left = stack_pointer[-2]; if ( - sym_is_const(ctx, left) && - sym_is_const(ctx, right) + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; @@ -378,8 +378,8 @@ right = stack_pointer[-1]; left = stack_pointer[-2]; if ( - sym_is_const(ctx, left) && - sym_is_const(ctx, right) + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; @@ -423,8 +423,8 @@ right = stack_pointer[-1]; left = stack_pointer[-2]; if ( - sym_is_const(ctx, left) && - sym_is_const(ctx, right) + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; @@ -488,8 +488,8 @@ right = stack_pointer[-1]; left = stack_pointer[-2]; if ( - sym_is_const(ctx, left) && - sym_is_const(ctx, right) + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; @@ -533,8 +533,8 @@ right = stack_pointer[-1]; left = stack_pointer[-2]; if ( - sym_is_const(ctx, left) && - sym_is_const(ctx, right) + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; @@ -578,8 +578,8 @@ right = stack_pointer[-1]; left = stack_pointer[-2]; if ( - sym_is_const(ctx, left) && - sym_is_const(ctx, right) + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; @@ -623,8 +623,8 @@ right = stack_pointer[-1]; left = stack_pointer[-2]; if ( - sym_is_const(ctx, left) && - sym_is_const(ctx, right) + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) ) { JitOptSymbol *left_sym = left; JitOptSymbol *right_sym = right; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index ccebd00688c72a..26c2c73e1659f7 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -159,6 +159,26 @@ _Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptSymbol *sym) return PyStackRef_FromPyObjectImmortalUnchecked(const_val); } +/* + Indicates whether the constant is safe to constant evaluate + (without side effects). + */ +bool +_Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptSymbol *sym) +{ + PyObject *const_val = _Py_uop_sym_get_const(ctx, sym); + if (const_val == NULL) { + return false; + } + PyTypeObject *typ = Py_TYPE(const_val); + return (typ == &PyLong_Type) || + (typ == &PyUnicode_Type) || + (typ == &PyFloat_Type) || + (typ == &PyDict_Type) || + (typ == &PyTuple_Type) || + (typ == &PyList_Type); +} + void _Py_uop_sym_set_type(JitOptContext *ctx, JitOptSymbol *sym, PyTypeObject *typ) { diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 939e8339ea70c8..2aaf976eb0ba76 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -187,8 +187,8 @@ def write_uop_pure_evaluation_region_header( emitter.emit("if (\n") assert len(uop.stack.inputs) > 0, "Pure operations must have at least 1 input" for inp in uop.stack.inputs[:-1]: - emitter.emit(f"sym_is_const(ctx, {inp.name}) &&\n") - emitter.emit(f"sym_is_const(ctx, {uop.stack.inputs[-1].name})\n") + emitter.emit(f"sym_is_safe_const(ctx, {inp.name}) &&\n") + emitter.emit(f"sym_is_safe_const(ctx, {uop.stack.inputs[-1].name})\n") emitter.emit(') {\n') # Declare variables, before they are shadowed. for inp in uop.stack.inputs: From c0c660025de1b984324003ea0e7902e0de0aabe7 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 21 May 2025 11:57:46 +0800 Subject: [PATCH 12/43] remove pure from _POP_CALL_TWO_LOAD_CONST_INLINE_BORROW --- Include/internal/pycore_uop_metadata.h | 2 +- Python/bytecodes.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 7e0fb245e9ceae..b792aa2a1bca26 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -305,7 +305,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_LOAD_CONST_INLINE_BORROW] = 0, [_POP_TOP_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_POP_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, - [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW] = HAS_ESCAPES_FLAG, [_CHECK_FUNCTION] = HAS_DEOPT_FLAG, [_START_EXECUTOR] = 0, [_MAKE_WARM] = 0, diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 5896ed9e431273..9a0564a5a45c58 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -5342,7 +5342,7 @@ dummy_func( value = PyStackRef_FromPyObjectImmortal(ptr); } - tier2 pure op(_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, pop1, pop2 -- value)) { + tier2 op(_POP_CALL_TWO_LOAD_CONST_INLINE_BORROW, (ptr/4, callable, null, pop1, pop2 -- value)) { PyStackRef_CLOSE(pop2); PyStackRef_CLOSE(pop1); (void)null; // Silence compiler warnings about unused variables From de8e170e62ad4759b9a271a776e91edd9474b3b3 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 21 May 2025 12:02:25 +0800 Subject: [PATCH 13/43] use upstream changes for stackref --- Include/internal/pycore_stackref.h | 23 ----------------------- Python/optimizer_cases.c.h | 4 ---- Python/optimizer_symbols.c | 2 +- 3 files changed, 1 insertion(+), 28 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 21f1d859578427..317768cf9d3317 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -140,13 +140,6 @@ _PyStackRef_FromPyObjectBorrow(PyObject *obj, const char *filename, int linenumb } #define PyStackRef_FromPyObjectBorrow(obj) _PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj), __FILE__, __LINE__) -static inline _PyStackRef -_PyStackRef_FromPyObjectImmortalUnchecked(PyObject *obj, const char *filename, int linenumber) -{ - return _Py_stackref_create(obj, filename, linenumber); -} -#define PyStackRef_FromPyObjectImmortalUnchecked(obj) _PyStackRef_FromPyObjectImmortalUnchecked(_PyObject_CAST(obj), __FILE__, __LINE__) - static inline void _PyStackRef_CLOSE(_PyStackRef ref, const char *filename, int linenumber) { @@ -381,16 +374,6 @@ PyStackRef_FromPyObjectBorrow(PyObject *obj) } #define PyStackRef_FromPyObjectBorrow(obj) PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj)) -static inline _PyStackRef -PyStackRef_FromPyObjectImmortalUnchecked(PyObject *obj) -{ - // Make sure we don't take an already tagged value. - assert(((uintptr_t)obj & Py_TAG_BITS) == 0); - assert(obj != NULL); - return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_DEFERRED }; -} -#define PyStackRef_FromPyObjectImmortalUnchecked(obj) PyStackRef_FromPyObjectImmortalUnchecked(_PyObject_CAST(obj)) - #define PyStackRef_CLOSE(REF) \ do { \ @@ -603,12 +586,6 @@ PyStackRef_FromPyObjectBorrow(PyObject *obj) return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT}; } -static inline _PyStackRef -PyStackRef_FromPyObjectImmortalUnchecked(PyObject *obj) -{ - return (_PyStackRef){ .bits = (uintptr_t)obj | Py_TAG_REFCNT}; -} - /* WARNING: This macro evaluates its argument more than once */ #ifdef _WIN32 #define PyStackRef_DUP(REF) \ diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 141aba20e80dfb..3ee8a68b63a56e 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1628,11 +1628,7 @@ REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); } len = sym_new_const(ctx, temp); - stack_pointer[0] = len; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); Py_DECREF(temp); - stack_pointer += -1; } stack_pointer[0] = len; stack_pointer += 1; diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 638ac1c0b603cf..08c7894ecaceb2 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -156,7 +156,7 @@ _Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptSymbol *sym) } // This is actually more like a borrow, but it doesn't matter here. // Eventually we discard the stackref anyways. - return PyStackRef_FromPyObjectImmortalUnchecked(const_val); + return PyStackRef_FromPyObjectBorrow(const_val); } /* From c2f8e2223f6e76f1b72787879c0b0a44e5cb1748 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 21 May 2025 12:22:09 +0800 Subject: [PATCH 14/43] remove unused comment --- Python/optimizer_symbols.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 08c7894ecaceb2..3fb7ee827dbe87 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -154,8 +154,6 @@ _Py_uop_sym_get_const_as_stackref(JitOptContext *ctx, JitOptSymbol *sym) if (const_val == NULL) { return PyStackRef_NULL; } - // This is actually more like a borrow, but it doesn't matter here. - // Eventually we discard the stackref anyways. return PyStackRef_FromPyObjectBorrow(const_val); } From ac7e34357ea57a6daf45ab1ea3d791cb8e36a431 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 22 May 2025 15:50:25 +0800 Subject: [PATCH 15/43] Address review --- Include/internal/pycore_stackref.h | 1 - Python/optimizer_cases.c.h | 16 ++++++++-------- Python/optimizer_symbols.c | 13 ++++++++----- Tools/cases_generator/optimizer_generator.py | 2 +- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Include/internal/pycore_stackref.h b/Include/internal/pycore_stackref.h index 317768cf9d3317..3ead6bc63c1d12 100644 --- a/Include/internal/pycore_stackref.h +++ b/Include/internal/pycore_stackref.h @@ -374,7 +374,6 @@ PyStackRef_FromPyObjectBorrow(PyObject *obj) } #define PyStackRef_FromPyObjectBorrow(obj) PyStackRef_FromPyObjectBorrow(_PyObject_CAST(obj)) - #define PyStackRef_CLOSE(REF) \ do { \ _PyStackRef _close_tmp = (REF); \ diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 3ee8a68b63a56e..d7cda6893b0dd2 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -154,7 +154,7 @@ res_stackref = PyStackRef_IsFalse(value) ? PyStackRef_True : PyStackRef_False; /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; } else { @@ -347,7 +347,7 @@ } res_stackref = PyStackRef_FromPyObjectSteal(res_o); /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -392,7 +392,7 @@ } res_stackref = PyStackRef_FromPyObjectSteal(res_o); /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -437,7 +437,7 @@ } res_stackref = PyStackRef_FromPyObjectSteal(res_o); /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -502,7 +502,7 @@ JUMP_TO_LABEL(pop_2_error); } /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -547,7 +547,7 @@ JUMP_TO_LABEL(pop_2_error); } /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -592,7 +592,7 @@ JUMP_TO_LABEL(pop_2_error); } /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -637,7 +637,7 @@ } res_stackref = PyStackRef_FromPyObjectSteal(res_o); /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 3fb7ee827dbe87..587974d7693c69 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -170,11 +170,10 @@ _Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptSymbol *sym) } PyTypeObject *typ = Py_TYPE(const_val); return (typ == &PyLong_Type) || - (typ == &PyUnicode_Type) || - (typ == &PyFloat_Type) || - (typ == &PyDict_Type) || - (typ == &PyTuple_Type) || - (typ == &PyList_Type); + (typ == &PyUnicode_Type) || + (typ == &PyFloat_Type) || + (typ == &PyDict_Type) || + (typ == &PyTuple_Type); } void @@ -438,6 +437,10 @@ _Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val) { assert(const_val != NULL); JitOptSymbol *res = _Py_uop_sym_new_const(ctx, const_val); + // Decref once because sym_new_const increfs it. + Py_DECREF(const_val); + // Decref it another time, because we are a steal operation. + // (Ownership now belongs to the symbol). Py_DECREF(const_val); return res; } diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 2aaf976eb0ba76..609f8e802e66e0 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -217,7 +217,7 @@ def write_uop_pure_evaluation_region_header( # All new stackrefs are created from new references. # That's how the stackref contract works. if not outp.peek: - emitter.emit(f"{outp.name} = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") + emitter.emit(f"{outp.name} = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal({outp.name}_stackref));\n") else: emitter.emit(f"{outp.name} = sym_new_const(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") storage.flush(out) From 05b822f7bcda8f4695c4e23a0378fb9b188eaf24 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 22 May 2025 16:42:48 +0800 Subject: [PATCH 16/43] fix test --- Lib/test/test_generated_cases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 54be059c9f1bf6..60089a7c806efe 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2278,7 +2278,7 @@ def test_pure_uop_body_copied_in(self): /* Start of pure uop copied from bytecodes for constant evaluation */ res_stackref = body(foo); /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; } else { @@ -2325,7 +2325,7 @@ def test_pure_uop_body_copied_in_complex(self): res_stackref = 1; } /* End of pure uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectBorrow(res_stackref)); + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; } else { From b4c2e930f7ea728aa880937eb576823747f7dcbe Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 23 May 2025 17:18:38 +0800 Subject: [PATCH 17/43] fix negative refcount --- Python/optimizer_symbols.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 587974d7693c69..f96e2461928fa2 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -439,9 +439,6 @@ _Py_uop_sym_new_const_steal(JitOptContext *ctx, PyObject *const_val) JitOptSymbol *res = _Py_uop_sym_new_const(ctx, const_val); // Decref once because sym_new_const increfs it. Py_DECREF(const_val); - // Decref it another time, because we are a steal operation. - // (Ownership now belongs to the symbol). - Py_DECREF(const_val); return res; } From 8552182acbc94dab9519657aa68ad8466202f3f6 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 28 May 2025 21:57:29 +0800 Subject: [PATCH 18/43] Use `REPLACE_OPCODE_IF_EVALUTES_PURE` --- Python/optimizer_bytecodes.c | 36 +++++--------------- Python/optimizer_cases.c.h | 14 -------- Tools/cases_generator/analyzer.py | 6 ++++ Tools/cases_generator/generators_common.py | 10 ++++++ Tools/cases_generator/optimizer_generator.py | 27 ++++++++++++--- 5 files changed, 46 insertions(+), 47 deletions(-) diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index e4985f472bd9ad..ab09a835ad1963 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -222,58 +222,37 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - // We need to tell the cases generator that it's being used by the constant generator. - // We should fix this in the cases generator. - (void)(left); - (void)(right); + REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - // We need to tell the cases generator that it's being used by the constant generator. - // We should fix this in the cases generator. - (void)(left); - (void)(right); + REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - // We need to tell the cases generator that it's being used by the constant generator. - // We should fix this in the cases generator. - (void)(left); - (void)(right); + REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - // We need to tell the cases generator that it's being used by the constant generator. - // We should fix this in the cases generator. - (void)(left); - (void)(right); + REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - // We need to tell the cases generator that it's being used by the constant generator. - // We should fix this in the cases generator. - (void)(left); - (void)(right); + REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - // We need to tell the cases generator that it's being used by the constant generator. - // We should fix this in the cases generator. - (void)(left); - (void)(right); + REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { - // We need to tell the cases generator that it's being used here. - // We should fix this in the cases generator. - (void)(left); - (void)(right); + REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); res = sym_new_type(ctx, &PyUnicode_Type); } @@ -386,6 +365,7 @@ dummy_func(void) { } op(_UNARY_NOT, (value -- res)) { + REPLACE_OPCODE_IF_EVALUTES_PURE(value); sym_set_type(value, &PyBool_Type); res = sym_new_truthiness(ctx, value, false); } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 510c0ac0267180..af6ddce7c8ef4c 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -362,8 +362,6 @@ assert(WITHIN_STACK_BOUNDS()); } else { - (void)(left); - (void)(right); res = sym_new_type(ctx, &PyLong_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -407,8 +405,6 @@ assert(WITHIN_STACK_BOUNDS()); } else { - (void)(left); - (void)(right); res = sym_new_type(ctx, &PyLong_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -452,8 +448,6 @@ assert(WITHIN_STACK_BOUNDS()); } else { - (void)(left); - (void)(right); res = sym_new_type(ctx, &PyLong_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -517,8 +511,6 @@ assert(WITHIN_STACK_BOUNDS()); } else { - (void)(left); - (void)(right); res = sym_new_type(ctx, &PyFloat_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -562,8 +554,6 @@ assert(WITHIN_STACK_BOUNDS()); } else { - (void)(left); - (void)(right); res = sym_new_type(ctx, &PyFloat_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -607,8 +597,6 @@ assert(WITHIN_STACK_BOUNDS()); } else { - (void)(left); - (void)(right); res = sym_new_type(ctx, &PyFloat_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -652,8 +640,6 @@ assert(WITHIN_STACK_BOUNDS()); } else { - (void)(left); - (void)(right); res = sym_new_type(ctx, &PyUnicode_Type); stack_pointer[-2] = res; stack_pointer += -1; diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 3070559db8ae57..a6ac0789b18d2a 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -520,6 +520,12 @@ def variable_used(node: parser.CodeDef, name: str) -> bool: ) +def uop_variable_used(uop: Uop, text: str) -> bool: + return any( + token.kind == "IDENTIFIER" and token.text == text for token in uop.body.tokens() + ) + + def oparg_used(node: parser.CodeDef) -> bool: """Determine whether `oparg` is used in a node.""" return any( diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 3bd3cc28fcb324..675f7d067122d8 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -87,6 +87,16 @@ def emit_to(out: CWriter, tkn_iter: TokenIterator, end: str) -> Token: out.emit(tkn) raise analysis_error(f"Expecting {end}. Reached end of file", tkn) +def skip_to(tkn_iter: TokenIterator, end: str) -> Token: + parens = 0 + for tkn in tkn_iter: + if tkn.kind == end and parens == 0: + return tkn + if tkn.kind == "LPAREN": + parens += 1 + if tkn.kind == "RPAREN": + parens -= 1 + raise analysis_error(f"Expecting {end}. Reached end of file", tkn) ReplacementFunctionType = Callable[ [Token, TokenIterator, CodeSection, Storage, Instruction | None], bool diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 609f8e802e66e0..6603077801a6d9 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -15,6 +15,7 @@ analysis_error, CodeSection, Label, + uop_variable_used, ) from generators_common import ( DEFAULT_INPUT, @@ -22,6 +23,7 @@ write_header, Emitter, TokenIterator, + skip_to, ) from cwriter import CWriter from typing import TextIO @@ -146,6 +148,10 @@ def emit_default(out: CWriter, uop: Uop, stack: Stack) -> None: class OptimizerEmitter(Emitter): + def __init__(self, out: CWriter, labels: dict[str, Label]): + super().__init__(out, labels) + self._replacers["REPLACE_OPCODE_IF_EVALUTES_PURE"] = self.replace_opcode_if_evaluates_pure + def emit_save(self, storage: Storage) -> None: storage.flush(self.out) @@ -156,6 +162,16 @@ def goto_label(self, goto: Token, label: Token, storage: Storage) -> None: self.out.emit(goto) self.out.emit(label) + def replace_opcode_if_evaluates_pure( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ) -> bool: + skip_to(tkn_iter, "SEMI") + return True class OptimizerConstantEmitter(OptimizerEmitter): def __init__(self, out: CWriter, labels: dict[str, Label], uop: Uop): @@ -240,7 +256,7 @@ def write_uop( stack = Stack(extract_bits=False, cast_type="JitOptSymbol *") try: out.start_line() - if override or uop.properties.pure: + if override: storage = Storage.for_uop(stack, prototype, out, check_liveness=False) if debug: args = [] @@ -257,11 +273,12 @@ def write_uop( type = f"uint{cache.size*16}_t " cast = f"uint{cache.size*16}_t" out.emit(f"{type}{cache.name} = ({cast})this_instr->operand0;\n") - if override or uop.properties.pure: + if override: # No reference management of inputs needed. for var in storage.inputs: # type: ignore[possibly-undefined] var.in_local = False - if uop.properties.pure: + replace_opcode_if_evaluates_pure = uop_variable_used(override, "REPLACE_OPCODE_IF_EVALUTES_PURE") + if replace_opcode_if_evaluates_pure: write_uop_pure_evaluation_region_header(uop, out, stack) out.start_line() if override: @@ -272,7 +289,7 @@ def write_uop( else: emit_default(out, uop, stack) out.start_line() - if uop.properties.pure: + if replace_opcode_if_evaluates_pure: write_uop_pure_evaluation_region_footer(out) else: emit_default(out, uop, stack) @@ -319,7 +336,7 @@ def generate_abstract_interpreter( if override: declare_variables(override, out, skip_inputs=False) else: - declare_variables(uop, out, skip_inputs=not uop.properties.pure) + declare_variables(uop, out, skip_inputs=True) write_uop(override, uop, out, debug) out.start_line() out.emit("break;\n") From 703dfc90a216268dc36577bf32ca0ea2d098c8ae Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 28 May 2025 22:03:40 +0800 Subject: [PATCH 19/43] Fix test, move is_abstract to subclass attribute --- Lib/test/test_generated_cases.py | 11 ++++--- Python/optimizer_bytecodes.c | 16 +++++----- Python/optimizer_cases.c.h | 32 ++++++++++---------- Tools/cases_generator/generators_common.py | 32 ++++++++------------ Tools/cases_generator/optimizer_generator.py | 14 ++++----- 5 files changed, 51 insertions(+), 54 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 8a14e20b0b98eb..6832fb720241f5 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2258,6 +2258,7 @@ def test_pure_uop_body_copied_in(self): """ input2 = """ op(OP, (foo -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(foo); res = sym_new_known(ctx, foo); } """ @@ -2272,9 +2273,9 @@ def test_pure_uop_body_copied_in(self): JitOptSymbol *foo_sym = foo; _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ res_stackref = body(foo); - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; } @@ -2300,6 +2301,7 @@ def test_pure_uop_body_copied_in_complex(self): """ input2 = """ op(OP, (foo -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(foo); res = sym_new_known(ctx, foo); } """ @@ -2314,14 +2316,14 @@ def test_pure_uop_body_copied_in_complex(self): JitOptSymbol *foo_sym = foo; _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ if (foo) { res_stackref = body(foo); } else { res_stackref = 1; } - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; } @@ -2347,6 +2349,7 @@ def test_pure_uop_reject_array_effects(self): """ input2 = """ op(OP, (foo[2] -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(foo[2]); res = sym_new_unknown(ctx); } """ diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index ab09a835ad1963..93bc506ad98606 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -222,37 +222,37 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); } op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUTES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyUnicode_Type); } @@ -365,7 +365,7 @@ dummy_func(void) { } op(_UNARY_NOT, (value -- res)) { - REPLACE_OPCODE_IF_EVALUTES_PURE(value); + REPLACE_OPCODE_IF_EVALUATES_PURE(value); sym_set_type(value, &PyBool_Type); res = sym_new_truthiness(ctx, value, false); } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index af6ddce7c8ef4c..aabb4e77db46f3 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -158,11 +158,11 @@ JitOptSymbol *value_sym = value; _PyStackRef value = sym_get_const_as_stackref(ctx, value_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ assert(PyStackRef_BoolCheck(value)); res_stackref = PyStackRef_IsFalse(value) ? PyStackRef_True : PyStackRef_False; - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; } @@ -342,7 +342,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyLong_CheckExact(left_o)); @@ -355,7 +355,7 @@ JUMP_TO_LABEL(pop_2_error); } res_stackref = PyStackRef_FromPyObjectSteal(res_o); - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -385,7 +385,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyLong_CheckExact(left_o)); @@ -398,7 +398,7 @@ JUMP_TO_LABEL(pop_2_error); } res_stackref = PyStackRef_FromPyObjectSteal(res_o); - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -428,7 +428,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyLong_CheckExact(left_o)); @@ -441,7 +441,7 @@ JUMP_TO_LABEL(pop_2_error); } res_stackref = PyStackRef_FromPyObjectSteal(res_o); - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -491,7 +491,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -504,7 +504,7 @@ if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -534,7 +534,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -547,7 +547,7 @@ if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -577,7 +577,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyFloat_CheckExact(left_o)); @@ -590,7 +590,7 @@ if (PyStackRef_IsNull(res_stackref )) { JUMP_TO_LABEL(pop_2_error); } - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; @@ -620,7 +620,7 @@ _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); _PyStackRef res_stackref; - /* Start of pure uop copied from bytecodes for constant evaluation */ + /* Start of uop copied from bytecodes for constant evaluation */ PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(PyUnicode_CheckExact(left_o)); @@ -633,7 +633,7 @@ JUMP_TO_LABEL(pop_2_error); } res_stackref = PyStackRef_FromPyObjectSteal(res_o); - /* End of pure uop copied from bytecodes for constant evaluation */ + /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-2] = res; stack_pointer += -1; diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 675f7d067122d8..b5f4191341517e 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -117,6 +117,7 @@ class Emitter: out: CWriter labels: dict[str, Label] _replacers: dict[str, ReplacementFunctionType] + is_abstract: bool def __init__(self, out: CWriter, labels: dict[str, Label]): self._replacers = { @@ -138,6 +139,7 @@ def __init__(self, out: CWriter, labels: dict[str, Label]): } self.out = out self.labels = labels + self.is_abstract = False def emit_to_with_replacement( self, @@ -493,13 +495,12 @@ def _emit_stmt( uop: CodeSection, storage: Storage, inst: Instruction | None, - is_abstract: bool, ) -> tuple[bool, Token | None, Storage]: method_name = "emit_" + stmt.__class__.__name__ method = getattr(self, method_name, None) if method is None: raise NotImplementedError - return method(stmt, uop, storage, inst, is_abstract) # type: ignore[no-any-return] + return method(stmt, uop, storage, inst) # type: ignore[no-any-return] def emit_SimpleStmt( self, @@ -507,13 +508,12 @@ def emit_SimpleStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, - is_abstract: bool, ) -> tuple[bool, Token | None, Storage]: local_stores = set(uop.local_stores) reachable = True tkn = stmt.contents[-1] try: - if stmt in uop.properties.escaping_calls and not is_abstract: + if stmt in uop.properties.escaping_calls and not self.is_abstract: escape = uop.properties.escaping_calls[stmt] if escape.kills is not None: self.stackref_kill(escape.kills, storage, True) @@ -550,7 +550,7 @@ def emit_SimpleStmt( self.out.emit(tkn) else: self.out.emit(tkn) - if stmt in uop.properties.escaping_calls and not is_abstract: + if stmt in uop.properties.escaping_calls and not self.is_abstract: self.emit_reload(storage) return reachable, None, storage except StackError as ex: @@ -563,7 +563,6 @@ def emit_MacroIfStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, - is_abstract: bool ) -> tuple[bool, Token | None, Storage]: self.out.emit(stmt.condition) branch = stmt.else_ is not None @@ -571,7 +570,7 @@ def emit_MacroIfStmt( if branch: else_storage = storage.copy() for s in stmt.body: - r, tkn, storage = self._emit_stmt(s, uop, storage, inst, is_abstract) + r, tkn, storage = self._emit_stmt(s, uop, storage, inst) if tkn is not None: self.out.emit(tkn) if not r: @@ -581,7 +580,7 @@ def emit_MacroIfStmt( self.out.emit(stmt.else_) assert stmt.else_body is not None for s in stmt.else_body: - r, tkn, else_storage = self._emit_stmt(s, uop, else_storage, inst, is_abstract) + r, tkn, else_storage = self._emit_stmt(s, uop, else_storage, inst) if tkn is not None: self.out.emit(tkn) if not r: @@ -598,7 +597,6 @@ def emit_IfStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, - is_abstract: bool ) -> tuple[bool, Token | None, Storage]: self.out.emit(stmt.if_) for tkn in stmt.condition: @@ -606,13 +604,13 @@ def emit_IfStmt( if_storage = storage.copy() rbrace: Token | None = stmt.if_ try: - reachable, rbrace, if_storage = self._emit_stmt(stmt.body, uop, if_storage, inst, is_abstract) + reachable, rbrace, if_storage = self._emit_stmt(stmt.body, uop, if_storage, inst) if stmt.else_ is not None: assert rbrace is not None self.out.emit(rbrace) self.out.emit(stmt.else_) if stmt.else_body is not None: - else_reachable, rbrace, else_storage = self._emit_stmt(stmt.else_body, uop, storage, inst, is_abstract) + else_reachable, rbrace, else_storage = self._emit_stmt(stmt.else_body, uop, storage, inst) if not reachable: reachable, storage = else_reachable, else_storage elif not else_reachable: @@ -640,7 +638,6 @@ def emit_BlockStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, - is_abstract: bool, emit_braces: bool = True, ) -> tuple[bool, Token | None, Storage]: """ Returns (reachable?, closing '}', stack).""" @@ -650,7 +647,7 @@ def emit_BlockStmt( self.out.emit(stmt.open) reachable = True for s in stmt.body: - reachable, tkn, storage = self._emit_stmt(s, uop, storage, inst, is_abstract) + reachable, tkn, storage = self._emit_stmt(s, uop, storage, inst) if tkn is not None: self.out.emit(tkn) if not reachable: @@ -667,13 +664,12 @@ def emit_ForStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, - is_abstract: bool, ) -> tuple[bool, Token | None, Storage]: """ Returns (reachable?, closing '}', stack).""" self.out.emit(stmt.for_) for tkn in stmt.header: self.out.emit(tkn) - return self._emit_stmt(stmt.body, uop, storage, inst, is_abstract) + return self._emit_stmt(stmt.body, uop, storage, inst) def emit_WhileStmt( self, @@ -681,13 +677,12 @@ def emit_WhileStmt( uop: CodeSection, storage: Storage, inst: Instruction | None, - is_abstract: bool, ) -> tuple[bool, Token | None, Storage]: """ Returns (reachable?, closing '}', stack).""" self.out.emit(stmt.while_) for tkn in stmt.condition: self.out.emit(tkn) - return self._emit_stmt(stmt.body, uop, storage, inst, is_abstract) + return self._emit_stmt(stmt.body, uop, storage, inst) def emit_tokens( @@ -696,10 +691,9 @@ def emit_tokens( storage: Storage, inst: Instruction | None, emit_braces: bool = True, - is_abstract: bool = False, ) -> tuple[bool, Storage]: self.out.start_line() - reachable, tkn, storage = self.emit_BlockStmt(code.body, code, storage, inst, is_abstract, emit_braces) + reachable, tkn, storage = self.emit_BlockStmt(code.body, code, storage, inst, emit_braces) assert tkn is not None try: if reachable: diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 6603077801a6d9..080508584fd1e2 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -150,7 +150,8 @@ class OptimizerEmitter(Emitter): def __init__(self, out: CWriter, labels: dict[str, Label]): super().__init__(out, labels) - self._replacers["REPLACE_OPCODE_IF_EVALUTES_PURE"] = self.replace_opcode_if_evaluates_pure + self._replacers["REPLACE_OPCODE_IF_EVALUATES_PURE"] = self.replace_opcode_if_evaluates_pure + self.is_abstract = True def emit_save(self, storage: Storage) -> None: storage.flush(self.out) @@ -224,10 +225,10 @@ def write_uop_pure_evaluation_region_header( # No reference management of outputs needed. for var in storage.outputs: var.in_local = True - emitter.emit("/* Start of pure uop copied from bytecodes for constant evaluation */\n") - emitter.emit_tokens(uop, storage, inst=None, emit_braces=False, is_abstract=True) + emitter.emit("/* Start of uop copied from bytecodes for constant evaluation */\n") + emitter.emit_tokens(uop, storage, inst=None, emit_braces=False) out.start_line() - emitter.emit("/* End of pure uop copied from bytecodes for constant evaluation */\n") + emitter.emit("/* End of uop copied from bytecodes for constant evaluation */\n") # Finally, assign back the output stackrefs to symbolics. for outp in uop.stack.outputs: # All new stackrefs are created from new references. @@ -277,14 +278,13 @@ def write_uop( # No reference management of inputs needed. for var in storage.inputs: # type: ignore[possibly-undefined] var.in_local = False - replace_opcode_if_evaluates_pure = uop_variable_used(override, "REPLACE_OPCODE_IF_EVALUTES_PURE") + replace_opcode_if_evaluates_pure = uop_variable_used(override, "REPLACE_OPCODE_IF_EVALUATES_PURE") if replace_opcode_if_evaluates_pure: write_uop_pure_evaluation_region_header(uop, out, stack) out.start_line() if override: emitter = OptimizerEmitter(out, {}) - _, storage = emitter.emit_tokens(override, storage, inst=None, - emit_braces=False, is_abstract=True) + _, storage = emitter.emit_tokens(override, storage, inst=None, emit_braces=False) storage.flush(out) else: emit_default(out, uop, stack) From b278734532ed844c3dddd68258841bbf0eb180fb Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 28 May 2025 22:32:26 +0800 Subject: [PATCH 20/43] fix linter/mypy --- Tools/cases_generator/optimizer_generator.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 080508584fd1e2..d7abcdf7fc5a3d 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -282,12 +282,9 @@ def write_uop( if replace_opcode_if_evaluates_pure: write_uop_pure_evaluation_region_header(uop, out, stack) out.start_line() - if override: - emitter = OptimizerEmitter(out, {}) - _, storage = emitter.emit_tokens(override, storage, inst=None, emit_braces=False) - storage.flush(out) - else: - emit_default(out, uop, stack) + emitter = OptimizerEmitter(out, {}) + _, storage = emitter.emit_tokens(override, storage, inst=None, emit_braces=False) + storage.flush(out) out.start_line() if replace_opcode_if_evaluates_pure: write_uop_pure_evaluation_region_footer(out) From 73a8b00906e6711fbb4515310365fa2bf4668b7c Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 28 May 2025 22:42:07 +0800 Subject: [PATCH 21/43] remove whitespace --- Lib/test/test_generated_cases.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 6832fb720241f5..b699feca9df748 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2301,7 +2301,7 @@ def test_pure_uop_body_copied_in_complex(self): """ input2 = """ op(OP, (foo -- res)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(foo); + REPLACE_OPCODE_IF_EVALUATES_PURE(foo); res = sym_new_known(ctx, foo); } """ From 4116a3173259f6f524e72ffe42749619f9c9d93d Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 29 May 2025 02:45:23 +0800 Subject: [PATCH 22/43] Remove PyDict_Type --- Python/optimizer_symbols.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index 92bda910bd0da3..c77e02e88b84cb 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -196,7 +196,6 @@ _Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptSymbol *sym) return (typ == &PyLong_Type) || (typ == &PyUnicode_Type) || (typ == &PyFloat_Type) || - (typ == &PyDict_Type) || (typ == &PyTuple_Type); } From 548b67c97bf17922f3e1e85e30aa96ec0f9ed792 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Thu, 29 May 2025 02:45:51 +0800 Subject: [PATCH 23/43] add bool type --- Python/optimizer_symbols.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Python/optimizer_symbols.c b/Python/optimizer_symbols.c index c77e02e88b84cb..657949ef9d21ed 100644 --- a/Python/optimizer_symbols.c +++ b/Python/optimizer_symbols.c @@ -196,7 +196,8 @@ _Py_uop_sym_is_safe_const(JitOptContext *ctx, JitOptSymbol *sym) return (typ == &PyLong_Type) || (typ == &PyUnicode_Type) || (typ == &PyFloat_Type) || - (typ == &PyTuple_Type); + (typ == &PyTuple_Type) || + (typ == &PyBool_Type); } void From 74a0208a330ffad4c710f81089bc5f30c95da214 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 6 Jun 2025 20:42:55 +0800 Subject: [PATCH 24/43] reduce diff --- Include/internal/pycore_opcode_metadata.h | 30 +++++----- Include/internal/pycore_uop_metadata.h | 68 +++++++++++----------- Lib/test/test_generated_cases.py | 12 ++-- Python/bytecodes.c | 26 ++++----- Tools/cases_generator/generators_common.py | 1 + 5 files changed, 69 insertions(+), 68 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index ad19f94ef14b55..00e918cb8f0cd1 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1135,7 +1135,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [CONTAINS_OP_DICT] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CONTAINS_OP_SET] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [CONVERT_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [COPY] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_PURE_FLAG }, [COPY_FREE_VARS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [DELETE_ATTR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_NAME_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [DELETE_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_FREE_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, @@ -1147,7 +1147,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [DICT_UPDATE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [END_ASYNC_FOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [END_FOR] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_NO_SAVE_IP_FLAG }, - [END_SEND] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [END_SEND] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, [ENTER_EXECUTOR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [EXIT_INIT_CHECK] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [EXTENDED_ARG] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, @@ -1214,9 +1214,9 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [LOAD_COMMON_CONSTANT] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, [LOAD_CONST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_CONST_FLAG }, [LOAD_DEREF] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, - [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG }, [LOAD_FAST_AND_CLEAR] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [LOAD_FAST_BORROW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, + [LOAD_FAST_BORROW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG }, [LOAD_FAST_BORROW_LOAD_FAST_BORROW] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, [LOAD_FAST_CHECK] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [LOAD_FAST_LOAD_FAST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, @@ -1239,17 +1239,17 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [MATCH_KEYS] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [MATCH_MAPPING] = { true, INSTR_FMT_IX, 0 }, [MATCH_SEQUENCE] = { true, INSTR_FMT_IX, 0 }, - [NOP] = { true, INSTR_FMT_IX, 0 }, - [NOT_TAKEN] = { true, INSTR_FMT_IX, 0 }, + [NOP] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, + [NOT_TAKEN] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, [POP_EXCEPT] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [POP_ITER] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, [POP_JUMP_IF_FALSE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, [POP_JUMP_IF_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ESCAPES_FLAG }, [POP_JUMP_IF_NOT_NONE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ESCAPES_FLAG }, [POP_JUMP_IF_TRUE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [POP_TOP] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG }, + [POP_TOP] = { true, INSTR_FMT_IX, HAS_ESCAPES_FLAG | HAS_PURE_FLAG }, [PUSH_EXC_INFO] = { true, INSTR_FMT_IX, 0 }, - [PUSH_NULL] = { true, INSTR_FMT_IX, 0 }, + [PUSH_NULL] = { true, INSTR_FMT_IX, HAS_PURE_FLAG }, [RAISE_VARARGS] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [RERAISE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [RESERVED] = { true, INSTR_FMT_IX, 0 }, @@ -1277,7 +1277,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [STORE_SUBSCR] = { true, INSTR_FMT_IXC, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [STORE_SUBSCR_DICT] = { true, INSTR_FMT_IXC, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [STORE_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, - [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, + [SWAP] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_PURE_FLAG }, [TO_BOOL] = { true, INSTR_FMT_IXC00, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [TO_BOOL_ALWAYS_TRUE] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [TO_BOOL_BOOL] = { true, INSTR_FMT_IXC00, HAS_EXIT_FLAG }, @@ -1295,16 +1295,16 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [UNPACK_SEQUENCE_TWO_TUPLE] = { true, INSTR_FMT_IBC, HAS_ARG_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [WITH_EXCEPT_START] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [YIELD_VALUE] = { true, INSTR_FMT_IB, HAS_ARG_FLAG }, - [ANNOTATIONS_PLACEHOLDER] = { true, -1, 0 }, + [ANNOTATIONS_PLACEHOLDER] = { true, -1, HAS_PURE_FLAG }, [JUMP] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_IF_FALSE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_IF_TRUE] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [JUMP_NO_INTERRUPT] = { true, -1, HAS_ARG_FLAG | HAS_JUMP_FLAG }, - [LOAD_CLOSURE] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG }, - [POP_BLOCK] = { true, -1, 0 }, - [SETUP_CLEANUP] = { true, -1, HAS_ARG_FLAG }, - [SETUP_FINALLY] = { true, -1, HAS_ARG_FLAG }, - [SETUP_WITH] = { true, -1, HAS_ARG_FLAG }, + [LOAD_CLOSURE] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG }, + [POP_BLOCK] = { true, -1, HAS_PURE_FLAG }, + [SETUP_CLEANUP] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, + [SETUP_FINALLY] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, + [SETUP_WITH] = { true, -1, HAS_PURE_FLAG | HAS_ARG_FLAG }, [STORE_FAST_MAYBE_NULL] = { true, -1, HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG }, }; #endif diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index b4b2dd0d4cf8b6..b08909e72c4f43 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -19,29 +19,29 @@ extern int _PyUop_num_popped(int opcode, int oparg); #ifdef NEED_OPCODE_METADATA const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { - [_NOP] = 0, + [_NOP] = HAS_PURE_FLAG, [_CHECK_PERIODIC] = HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_CHECK_PERIODIC_IF_NOT_YIELD_FROM] = HAS_ARG_FLAG | HAS_EVAL_BREAK_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_RESUME_CHECK] = HAS_DEOPT_FLAG, [_LOAD_FAST_CHECK] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_LOAD_FAST_0] = HAS_LOCAL_FLAG, - [_LOAD_FAST_1] = HAS_LOCAL_FLAG, - [_LOAD_FAST_2] = HAS_LOCAL_FLAG, - [_LOAD_FAST_3] = HAS_LOCAL_FLAG, - [_LOAD_FAST_4] = HAS_LOCAL_FLAG, - [_LOAD_FAST_5] = HAS_LOCAL_FLAG, - [_LOAD_FAST_6] = HAS_LOCAL_FLAG, - [_LOAD_FAST_7] = HAS_LOCAL_FLAG, - [_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW_0] = HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW_1] = HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW_2] = HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW_3] = HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW_4] = HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW_5] = HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW_6] = HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW_7] = HAS_LOCAL_FLAG, - [_LOAD_FAST_BORROW] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, + [_LOAD_FAST_0] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_1] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_2] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_3] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_4] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_5] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_6] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_7] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW_0] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW_1] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW_2] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW_3] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW_4] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW_5] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW_6] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW_7] = HAS_LOCAL_FLAG | HAS_PURE_FLAG, + [_LOAD_FAST_BORROW] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_PURE_FLAG, [_LOAD_FAST_AND_CLEAR] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_LOAD_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, [_LOAD_FAST_BORROW_LOAD_FAST_BORROW] = HAS_ARG_FLAG | HAS_LOCAL_FLAG, @@ -62,12 +62,12 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG, [_STORE_FAST_LOAD_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG, [_STORE_FAST_STORE_FAST] = HAS_ARG_FLAG | HAS_LOCAL_FLAG | HAS_ESCAPES_FLAG, - [_POP_TOP] = HAS_ESCAPES_FLAG, + [_POP_TOP] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_POP_TWO] = HAS_ESCAPES_FLAG, - [_PUSH_NULL] = 0, + [_PUSH_NULL] = HAS_PURE_FLAG, [_END_FOR] = HAS_ESCAPES_FLAG | HAS_NO_SAVE_IP_FLAG, [_POP_ITER] = HAS_ESCAPES_FLAG, - [_END_SEND] = HAS_ESCAPES_FLAG, + [_END_SEND] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_UNARY_NEGATIVE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_UNARY_NOT] = HAS_PURE_FLAG, [_TO_BOOL] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, @@ -96,7 +96,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_BINARY_OP_INPLACE_ADD_UNICODE] = HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_BINARY_OP_EXTEND] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, - [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG, + [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_BINARY_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_BINARY_OP_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, @@ -240,12 +240,12 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CHECK_FUNCTION_EXACT_ARGS] = HAS_ARG_FLAG | HAS_EXIT_FLAG, [_CHECK_STACK_SPACE] = HAS_ARG_FLAG | HAS_DEOPT_FLAG, [_CHECK_RECURSION_REMAINING] = HAS_DEOPT_FLAG, - [_INIT_CALL_PY_EXACT_ARGS_0] = 0, - [_INIT_CALL_PY_EXACT_ARGS_1] = 0, - [_INIT_CALL_PY_EXACT_ARGS_2] = 0, - [_INIT_CALL_PY_EXACT_ARGS_3] = 0, - [_INIT_CALL_PY_EXACT_ARGS_4] = 0, - [_INIT_CALL_PY_EXACT_ARGS] = HAS_ARG_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_0] = HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_1] = HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_2] = HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_3] = HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS_4] = HAS_PURE_FLAG, + [_INIT_CALL_PY_EXACT_ARGS] = HAS_ARG_FLAG | HAS_PURE_FLAG, [_PUSH_FRAME] = 0, [_GUARD_NOS_NULL] = HAS_DEOPT_FLAG, [_GUARD_NOS_NOT_NULL] = HAS_EXIT_FLAG, @@ -288,9 +288,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_CONVERT_VALUE] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_FORMAT_SIMPLE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_FORMAT_WITH_SPEC] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, - [_COPY] = HAS_ARG_FLAG, + [_COPY] = HAS_ARG_FLAG | HAS_PURE_FLAG, [_BINARY_OP] = HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG, - [_SWAP] = HAS_ARG_FLAG, + [_SWAP] = HAS_ARG_FLAG | HAS_PURE_FLAG, [_GUARD_IS_TRUE_POP] = HAS_EXIT_FLAG, [_GUARD_IS_FALSE_POP] = HAS_EXIT_FLAG, [_GUARD_IS_NONE_POP] = HAS_EXIT_FLAG | HAS_ESCAPES_FLAG, @@ -301,9 +301,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_SAVE_RETURN_OFFSET] = HAS_ARG_FLAG, [_EXIT_TRACE] = HAS_ESCAPES_FLAG, [_CHECK_VALIDITY] = HAS_DEOPT_FLAG, - [_LOAD_CONST_INLINE] = 0, - [_POP_TOP_LOAD_CONST_INLINE] = HAS_ESCAPES_FLAG, - [_LOAD_CONST_INLINE_BORROW] = 0, + [_LOAD_CONST_INLINE] = HAS_PURE_FLAG, + [_POP_TOP_LOAD_CONST_INLINE] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_LOAD_CONST_INLINE_BORROW] = HAS_PURE_FLAG, [_POP_CALL] = HAS_ESCAPES_FLAG, [_POP_CALL_ONE] = HAS_ESCAPES_FLAG, [_POP_CALL_TWO] = HAS_ESCAPES_FLAG, diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index b699feca9df748..c2428318d9b473 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -1969,12 +1969,12 @@ def run_cases_test(self, input: str, input2: str, expected: str): def test_overridden_abstract(self): input = """ - op(OP, (--)) { + pure op(OP, (--)) { SPAM(); } """ input2 = """ - op(OP, (--)) { + pure op(OP, (--)) { eggs(); } """ @@ -1988,7 +1988,7 @@ def test_overridden_abstract(self): def test_overridden_abstract_args(self): input = """ - op(OP, (arg1 -- out)) { + pure op(OP, (arg1 -- out)) { out = SPAM(arg1); } op(OP2, (arg1 -- out)) { @@ -2021,16 +2021,16 @@ def test_overridden_abstract_args(self): def test_no_overridden_case(self): input = """ - op(OP, (arg1 -- out)) { + pure op(OP, (arg1 -- out)) { out = SPAM(arg1); } - op(OP2, (arg1 -- out)) { + pure op(OP2, (arg1 -- out)) { } """ input2 = """ - op(OP2, (arg1 -- out)) { + pure op(OP2, (arg1 -- out)) { out = NULL; } """ diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 8d4928349f7a89..6a5bed7ced1d96 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -143,7 +143,7 @@ dummy_func( switch (opcode) { // BEGIN BYTECODES // - inst(NOP, (--)) { + pure inst(NOP, (--)) { } family(RESUME, 0) = { @@ -266,12 +266,12 @@ dummy_func( value = PyStackRef_DUP(value_s); } - replicate(8) inst(LOAD_FAST, (-- value)) { + replicate(8) pure inst(LOAD_FAST, (-- value)) { assert(!PyStackRef_IsNull(GETLOCAL(oparg))); value = PyStackRef_DUP(GETLOCAL(oparg)); } - replicate(8) inst (LOAD_FAST_BORROW, (-- value)) { + replicate(8) pure inst (LOAD_FAST_BORROW, (-- value)) { assert(!PyStackRef_IsNull(GETLOCAL(oparg))); value = PyStackRef_Borrow(GETLOCAL(oparg)); } @@ -340,7 +340,7 @@ dummy_func( PyStackRef_XCLOSE(tmp); } - inst(POP_TOP, (value --)) { + pure inst(POP_TOP, (value --)) { PyStackRef_XCLOSE(value); } @@ -349,7 +349,7 @@ dummy_func( PyStackRef_CLOSE(nos); } - inst(PUSH_NULL, (-- res)) { + pure inst(PUSH_NULL, (-- res)) { res = PyStackRef_NULL; } @@ -388,7 +388,7 @@ dummy_func( PyStackRef_CLOSE(iter); } - inst(END_SEND, (receiver, value -- val)) { + pure inst(END_SEND, (receiver, value -- val)) { val = value; DEAD(value); PyStackRef_CLOSE(receiver); @@ -769,7 +769,7 @@ dummy_func( DEOPT_IF(!res); } - op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) { + pure op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(INLINE_CACHE_ENTRIES_BINARY_OP == 5); @@ -3925,7 +3925,7 @@ dummy_func( DEOPT_IF(tstate->py_recursion_remaining <= 1); } - replicate(5) op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) { + replicate(5) pure op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _PyInterpreterFrame*)) { int has_self = !PyStackRef_IsNull(self_or_null); STAT_INC(CALL, hit); new_frame = _PyFrame_PushUnchecked(tstate, callable, oparg + has_self, frame); @@ -4991,7 +4991,7 @@ dummy_func( res = PyStackRef_FromPyObjectSteal(res_o); } - inst(COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) { + pure inst(COPY, (bottom, unused[oparg-1] -- bottom, unused[oparg-1], top)) { assert(oparg > 0); top = PyStackRef_DUP(bottom); } @@ -5025,7 +5025,7 @@ dummy_func( macro(BINARY_OP) = _SPECIALIZE_BINARY_OP + unused/4 + _BINARY_OP; - inst(SWAP, (bottom, unused[oparg-2], top -- + pure inst(SWAP, (bottom, unused[oparg-2], top -- bottom, unused[oparg-2], top)) { _PyStackRef temp = bottom; bottom = top; @@ -5260,16 +5260,16 @@ dummy_func( DEOPT_IF(!current_executor->vm_data.valid); } - tier2 op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { + tier2 pure op(_LOAD_CONST_INLINE, (ptr/4 -- value)) { value = PyStackRef_FromPyObjectNew(ptr); } - tier2 op (_POP_TOP_LOAD_CONST_INLINE, (ptr/4, pop -- value)) { + tier2 pure op (_POP_TOP_LOAD_CONST_INLINE, (ptr/4, pop -- value)) { PyStackRef_CLOSE(pop); value = PyStackRef_FromPyObjectNew(ptr); } - tier2 op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { + tier2 pure op(_LOAD_CONST_INLINE_BORROW, (ptr/4 -- value)) { value = PyStackRef_FromPyObjectBorrow(ptr); } diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index b5f4191341517e..ec8f0f8e4fea61 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -75,6 +75,7 @@ def write_header( """ ) + def emit_to(out: CWriter, tkn_iter: TokenIterator, end: str) -> Token: parens = 0 for tkn in tkn_iter: From 6a5dc128ba9b7e47e2c4f4e724f94a51426877d7 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Mon, 9 Jun 2025 18:50:55 +0800 Subject: [PATCH 25/43] Grab identifiers from REPLACE_OPCODE_IF_EVALUATES_PURE --- Tools/cases_generator/optimizer_generator.py | 31 ++++++++++++++++---- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index d7abcdf7fc5a3d..31e3013b441d80 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -195,17 +195,38 @@ def emit_stackref_override( self.out.emit("_stackref ") return True + +def replace_opcode_if_evaluates_pure_identifiers(uop: Uop) -> list[str]: + token = None + iterator = uop.body.tokens() + for token in iterator: + if token.kind == "IDENTIFIER" and token.text == "REPLACE_OPCODE_IF_EVALUATES_PURE": + break + assert token is not None + assert token.kind == "IDENTIFIER" and token.text == "REPLACE_OPCODE_IF_EVALUATES_PURE", uop.name + assert next(iterator).kind == "LPAREN" + idents = [] + for token in iterator: + if token.kind == "RPAREN": + break + if token.kind == "IDENTIFIER": + idents.append(token.text) + return idents + + def write_uop_pure_evaluation_region_header( uop: Uop, + override: Uop, out: CWriter, stack: Stack, ) -> None: emitter = OptimizerConstantEmitter(out, {}, uop) emitter.emit("if (\n") - assert len(uop.stack.inputs) > 0, "Pure operations must have at least 1 input" - for inp in uop.stack.inputs[:-1]: - emitter.emit(f"sym_is_safe_const(ctx, {inp.name}) &&\n") - emitter.emit(f"sym_is_safe_const(ctx, {uop.stack.inputs[-1].name})\n") + input_identifiers = replace_opcode_if_evaluates_pure_identifiers(override) + assert len(input_identifiers) > 0, "Pure operations must have at least 1 input" + for inp in input_identifiers[:-1]: + emitter.emit(f"sym_is_safe_const(ctx, {inp}) &&\n") + emitter.emit(f"sym_is_safe_const(ctx, {input_identifiers[-1]})\n") emitter.emit(') {\n') # Declare variables, before they are shadowed. for inp in uop.stack.inputs: @@ -280,7 +301,7 @@ def write_uop( var.in_local = False replace_opcode_if_evaluates_pure = uop_variable_used(override, "REPLACE_OPCODE_IF_EVALUATES_PURE") if replace_opcode_if_evaluates_pure: - write_uop_pure_evaluation_region_header(uop, out, stack) + write_uop_pure_evaluation_region_header(uop, override, out, stack) out.start_line() emitter = OptimizerEmitter(out, {}) _, storage = emitter.emit_tokens(override, storage, inst=None, emit_braces=False) From 507c80ac48776f20ef89351b3cc075127602718d Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 13 Jun 2025 11:29:42 +0800 Subject: [PATCH 26/43] Use replacer --- Python/optimizer_cases.c.h | 86 +++++++------- Tools/cases_generator/analyzer.py | 6 - Tools/cases_generator/optimizer_generator.py | 112 ++++++++----------- 3 files changed, 87 insertions(+), 117 deletions(-) diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index a4247f5c0e8b59..82e687125d1227 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -165,12 +165,11 @@ /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; + break; } - else { - sym_set_type(value, &PyBool_Type); - res = sym_new_truthiness(ctx, value, false); - stack_pointer[-1] = res; - } + sym_set_type(value, &PyBool_Type); + res = sym_new_truthiness(ctx, value, false); + stack_pointer[-1] = res; break; } @@ -367,13 +366,12 @@ stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); + break; } - else { - res = sym_new_type(ctx, &PyLong_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - } + res = sym_new_type(ctx, &PyLong_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -410,13 +408,12 @@ stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); + break; } - else { - res = sym_new_type(ctx, &PyLong_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - } + res = sym_new_type(ctx, &PyLong_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -453,13 +450,12 @@ stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); + break; } - else { - res = sym_new_type(ctx, &PyLong_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - } + res = sym_new_type(ctx, &PyLong_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -516,13 +512,12 @@ stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); + break; } - else { - res = sym_new_type(ctx, &PyFloat_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - } + res = sym_new_type(ctx, &PyFloat_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -559,13 +554,12 @@ stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); + break; } - else { - res = sym_new_type(ctx, &PyFloat_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - } + res = sym_new_type(ctx, &PyFloat_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -602,13 +596,12 @@ stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); + break; } - else { - res = sym_new_type(ctx, &PyFloat_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - } + res = sym_new_type(ctx, &PyFloat_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } @@ -645,13 +638,12 @@ stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); + break; } - else { - res = sym_new_type(ctx, &PyUnicode_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); - } + res = sym_new_type(ctx, &PyUnicode_Type); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); break; } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 4ac3c891bf0a5c..1447f365336d82 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -520,12 +520,6 @@ def variable_used(node: parser.CodeDef, name: str) -> bool: ) -def uop_variable_used(uop: Uop, text: str) -> bool: - return any( - token.kind == "IDENTIFIER" and token.text == text for token in uop.body.tokens() - ) - - def oparg_used(node: parser.CodeDef) -> bool: """Determine whether `oparg` is used in a node.""" return any( diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 31e3013b441d80..c384befb8015db 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -15,7 +15,6 @@ analysis_error, CodeSection, Label, - uop_variable_used, ) from generators_common import ( DEFAULT_INPUT, @@ -148,10 +147,12 @@ def emit_default(out: CWriter, uop: Uop, stack: Stack) -> None: class OptimizerEmitter(Emitter): - def __init__(self, out: CWriter, labels: dict[str, Label]): + def __init__(self, out: CWriter, labels: dict[str, Label], original_uop: Uop, stack: Stack): super().__init__(out, labels) self._replacers["REPLACE_OPCODE_IF_EVALUATES_PURE"] = self.replace_opcode_if_evaluates_pure self.is_abstract = True + self.original_uop = original_uop + self.stack = stack def emit_save(self, storage: Storage) -> None: storage.flush(self.out) @@ -172,14 +173,55 @@ def replace_opcode_if_evaluates_pure( inst: Instruction | None, ) -> bool: skip_to(tkn_iter, "SEMI") + emitter = OptimizerConstantEmitter(self.out, {}, self.original_uop, copy.deepcopy(self.stack)) + emitter.emit("if (\n") + input_identifiers = replace_opcode_if_evaluates_pure_identifiers(uop) + assert len(input_identifiers) > 0, "Pure operations must have at least 1 input" + for inp in input_identifiers[:-1]: + emitter.emit(f"sym_is_safe_const(ctx, {inp}) &&\n") + emitter.emit(f"sym_is_safe_const(ctx, {input_identifiers[-1]})\n") + emitter.emit(') {\n') + # Declare variables, before they are shadowed. + for inp in self.original_uop.stack.inputs: + if inp.used: + emitter.emit(f"{type_name(inp)}{inp.name}_sym = {inp.name};\n") + # Shadow the symbolic variables with stackrefs. + for inp in self.original_uop.stack.inputs: + if inp.used: + emitter.emit(f"{stackref_type_name(inp)}{inp.name} = sym_get_const_as_stackref(ctx, {inp.name}_sym);\n") + # Rename all output variables to stackref variant. + for outp in self.original_uop.stack.outputs: + assert not outp.is_array(), "Array output StackRefs not supported for pure ops." + emitter.emit(f"_PyStackRef {outp.name}_stackref;\n") + + + storage = Storage.for_uop(self.stack, self.original_uop, CWriter.null(), check_liveness=False) + # No reference management of outputs needed. + for var in storage.outputs: + var.in_local = True + emitter.emit("/* Start of uop copied from bytecodes for constant evaluation */\n") + emitter.emit_tokens(self.original_uop, storage, inst=None, emit_braces=False) + self.out.start_line() + emitter.emit("/* End of uop copied from bytecodes for constant evaluation */\n") + # Finally, assign back the output stackrefs to symbolics. + for outp in self.original_uop.stack.outputs: + # All new stackrefs are created from new references. + # That's how the stackref contract works. + if not outp.peek: + emitter.emit(f"{outp.name} = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal({outp.name}_stackref));\n") + else: + emitter.emit(f"{outp.name} = sym_new_const(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") + storage.flush(self.out) + emitter.emit("break;\n") + emitter.emit("}\n") return True class OptimizerConstantEmitter(OptimizerEmitter): - def __init__(self, out: CWriter, labels: dict[str, Label], uop: Uop): - super().__init__(out, labels) + def __init__(self, out: CWriter, labels: dict[str, Label], original_uop: Uop, stack: Stack): + super().__init__(out, labels, original_uop, stack) # Replace all outputs to point to their stackref versions. overrides = { - outp.name: self.emit_stackref_override for outp in uop.stack.outputs + outp.name: self.emit_stackref_override for outp in self.original_uop.stack.outputs } self._replacers = {**self._replacers, **overrides} @@ -214,59 +256,6 @@ def replace_opcode_if_evaluates_pure_identifiers(uop: Uop) -> list[str]: return idents -def write_uop_pure_evaluation_region_header( - uop: Uop, - override: Uop, - out: CWriter, - stack: Stack, -) -> None: - emitter = OptimizerConstantEmitter(out, {}, uop) - emitter.emit("if (\n") - input_identifiers = replace_opcode_if_evaluates_pure_identifiers(override) - assert len(input_identifiers) > 0, "Pure operations must have at least 1 input" - for inp in input_identifiers[:-1]: - emitter.emit(f"sym_is_safe_const(ctx, {inp}) &&\n") - emitter.emit(f"sym_is_safe_const(ctx, {input_identifiers[-1]})\n") - emitter.emit(') {\n') - # Declare variables, before they are shadowed. - for inp in uop.stack.inputs: - if inp.used: - emitter.emit(f"{type_name(inp)}{inp.name}_sym = {inp.name};\n") - # Shadow the symbolic variables with stackrefs. - for inp in uop.stack.inputs: - if inp.used: - emitter.emit(f"{stackref_type_name(inp)}{inp.name} = sym_get_const_as_stackref(ctx, {inp.name}_sym);\n") - # Rename all output variables to stackref variant. - for outp in uop.stack.outputs: - assert not outp.is_array(), "Array output StackRefs not supported for pure ops." - emitter.emit(f"_PyStackRef {outp.name}_stackref;\n") - stack = copy.deepcopy(stack) - - storage = Storage.for_uop(stack, uop, CWriter.null(), check_liveness=False) - # No reference management of outputs needed. - for var in storage.outputs: - var.in_local = True - emitter.emit("/* Start of uop copied from bytecodes for constant evaluation */\n") - emitter.emit_tokens(uop, storage, inst=None, emit_braces=False) - out.start_line() - emitter.emit("/* End of uop copied from bytecodes for constant evaluation */\n") - # Finally, assign back the output stackrefs to symbolics. - for outp in uop.stack.outputs: - # All new stackrefs are created from new references. - # That's how the stackref contract works. - if not outp.peek: - emitter.emit(f"{outp.name} = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal({outp.name}_stackref));\n") - else: - emitter.emit(f"{outp.name} = sym_new_const(ctx, PyStackRef_AsPyObjectBorrow({outp.name}_stackref));\n") - storage.flush(out) - emitter.emit("}\n") - emitter.emit("else {\n") - -def write_uop_pure_evaluation_region_footer( - out: CWriter, -) -> None: - out.emit("}\n") - def write_uop( override: Uop | None, uop: Uop, @@ -299,16 +288,11 @@ def write_uop( # No reference management of inputs needed. for var in storage.inputs: # type: ignore[possibly-undefined] var.in_local = False - replace_opcode_if_evaluates_pure = uop_variable_used(override, "REPLACE_OPCODE_IF_EVALUATES_PURE") - if replace_opcode_if_evaluates_pure: - write_uop_pure_evaluation_region_header(uop, override, out, stack) out.start_line() - emitter = OptimizerEmitter(out, {}) + emitter = OptimizerEmitter(out, {}, uop, copy.deepcopy(stack)) _, storage = emitter.emit_tokens(override, storage, inst=None, emit_braces=False) storage.flush(out) out.start_line() - if replace_opcode_if_evaluates_pure: - write_uop_pure_evaluation_region_footer(out) else: emit_default(out, uop, stack) out.start_line() From dc68b457e63d0e377704c5885678cb8516e2699b Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 13 Jun 2025 11:35:56 +0800 Subject: [PATCH 27/43] reduce diff --- Tools/cases_generator/optimizer_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index c384befb8015db..c672fee4933a6b 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -285,11 +285,11 @@ def write_uop( cast = f"uint{cache.size*16}_t" out.emit(f"{type}{cache.name} = ({cast})this_instr->operand0;\n") if override: + emitter = OptimizerEmitter(out, {}, uop, copy.deepcopy(stack)) # No reference management of inputs needed. for var in storage.inputs: # type: ignore[possibly-undefined] var.in_local = False out.start_line() - emitter = OptimizerEmitter(out, {}, uop, copy.deepcopy(stack)) _, storage = emitter.emit_tokens(override, storage, inst=None, emit_braces=False) storage.flush(out) out.start_line() From e88b71a0987e6d6d887e275bd260a65fa597979c Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 13 Jun 2025 11:38:40 +0800 Subject: [PATCH 28/43] Update optimizer_generator.py --- Tools/cases_generator/optimizer_generator.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 7d3f027dbcd4c3..5054a750d16a4b 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -82,8 +82,6 @@ def type_name(var: StackItem) -> str: def stackref_type_name(var: StackItem) -> str: if var.is_array(): assert False, "Unsafe to convert a symbol to an array-like StackRef." - if var.type: - return var.type return "_PyStackRef " def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: @@ -262,7 +260,7 @@ def write_uop( ) -> None: locals: dict[str, Local] = {} prototype = override if override else uop - stack = Stack(extract_bits=False, cast_type="JitOptSymbol *") + stack = Stack() try: out.start_line() if override: @@ -337,7 +335,7 @@ def generate_abstract_interpreter( declare_variables(override, out, skip_inputs=False) else: declare_variables(uop, out, skip_inputs=True) - write_uop(override, uop, out, stack, debug, skip_inputs=(override is None)) + write_uop(override, uop, out, debug) out.start_line() out.emit("break;\n") out.emit("}") From 866510fe3d48442d5b467265cf1ec87013afc64c Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:02:20 +0800 Subject: [PATCH 29/43] Address review (add long functions to allowlist) --- Include/internal/pycore_opcode_metadata.h | 6 ++--- Include/internal/pycore_uop_metadata.h | 6 ++--- Lib/test/test_generated_cases.py | 24 ++++++++++---------- Python/executor_cases.c.h | 6 ----- Python/generated_cases.c.h | 6 ----- Python/optimizer_cases.c.h | 19 +++++++++++----- Tools/cases_generator/analyzer.py | 3 +++ Tools/cases_generator/generators_common.py | 5 ++-- Tools/cases_generator/optimizer_generator.py | 15 ++++++++---- 9 files changed, 47 insertions(+), 43 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 00e918cb8f0cd1..1af8fa2ae667de 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1072,12 +1072,12 @@ extern const struct opcode_metadata _PyOpcode_opcode_metadata[267]; const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [BINARY_OP] = { true, INSTR_FMT_IBC0000, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, [BINARY_OP_EXTEND] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, [BINARY_OP_SUBSCR_DICT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_SUBSCR_GETITEM] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG }, [BINARY_OP_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, @@ -1085,7 +1085,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [BINARY_OP_SUBSCR_STR_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, + [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, [BINARY_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BUILD_INTERPOLATION] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index b08909e72c4f43..3bdab69f6e6ab7 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -85,9 +85,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_NOS_INT] = HAS_EXIT_FLAG, [_GUARD_TOS_INT] = HAS_EXIT_FLAG, - [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, - [_BINARY_OP_ADD_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, - [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_ADD_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_GUARD_NOS_FLOAT] = HAS_EXIT_FLAG, [_GUARD_TOS_FLOAT] = HAS_EXIT_FLAG, [_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 05ddfcf8a3d7e6..90cba2d0e64baa 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2225,9 +2225,11 @@ def test_validate_uop_unused_size_mismatch(self): self.run_cases_test(input, input2, output) def test_pure_uop_body_copied_in(self): + # Note: any non-escaping call works. + # In this case, we use _PyLong_Add. input = """ pure op(OP, (foo -- res)) { - res = body(foo); + res = _PyLong_Add(foo); } """ input2 = """ @@ -2248,15 +2250,14 @@ def test_pure_uop_body_copied_in(self): _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); _PyStackRef res_stackref; /* Start of uop copied from bytecodes for constant evaluation */ - res_stackref = body(foo); + res_stackref = _PyLong_Add(foo); /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; + break; } - else { - res = sym_new_known(ctx, foo); - stack_pointer[-1] = res; - } + res = sym_new_known(ctx, foo); + stack_pointer[-1] = res; break; } """ @@ -2266,7 +2267,7 @@ def test_pure_uop_body_copied_in_complex(self): input = """ pure op(OP, (foo -- res)) { if (foo) { - res = body(foo); + res = _PyLong_Add(foo); } else { res = 1; @@ -2292,7 +2293,7 @@ def test_pure_uop_body_copied_in_complex(self): _PyStackRef res_stackref; /* Start of uop copied from bytecodes for constant evaluation */ if (foo) { - res_stackref = body(foo); + res_stackref = _PyLong_Add(foo); } else { res_stackref = 1; @@ -2300,11 +2301,10 @@ def test_pure_uop_body_copied_in_complex(self): /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; + break; } - else { - res = sym_new_known(ctx, foo); - stack_pointer[-1] = res; - } + res = sym_new_known(ctx, foo); + stack_pointer[-1] = res; break; } """ diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 4f772f916d1152..350de2c7de4546 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -881,9 +881,7 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); - _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o); - stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { @@ -909,9 +907,7 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); - _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o); - stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { @@ -937,9 +933,7 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); - _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o); - stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 5ac519bb1b6093..3ef3208081e459 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -183,9 +183,7 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); - _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o); - stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { @@ -508,9 +506,7 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); - _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o); - stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { @@ -1089,9 +1085,7 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); - _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o); - stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index b41d0e641398d2..b317ae1fa32620 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -1367,6 +1367,7 @@ PyModuleObject *mod = (PyModuleObject *)sym_get_const(ctx, owner); if (PyModule_CheckExact(mod)) { PyObject *dict = mod->md_dict; + stack_pointer[-1] = attr; uint64_t watched_mutations = get_mutations(dict); if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) { PyDict_Watch(GLOBALS_WATCHER_ID, dict); @@ -1508,14 +1509,16 @@ assert(_Py_IsImmortal(tmp)); REPLACE_OP(this_instr, _POP_TWO_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)tmp); res = sym_new_const(ctx, tmp); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); Py_DECREF(tmp); } else { res = sym_new_type(ctx, &PyBool_Type); + stack_pointer += -1; } - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = res; break; } @@ -1639,7 +1642,11 @@ REPLACE_OP(this_instr, _LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)temp); } len = sym_new_const(ctx, temp); + stack_pointer[0] = len; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); Py_DECREF(temp); + stack_pointer += -1; } stack_pointer[0] = len; stack_pointer += 1; @@ -2498,13 +2505,13 @@ assert(framesize > 0); assert(framesize <= curr_space); curr_space -= framesize; + stack_pointer[0] = res; + stack_pointer += 1; + assert(WITHIN_STACK_BOUNDS()); co = get_code(this_instr); if (co == NULL) { ctx->done = true; } - stack_pointer[0] = res; - stack_pointer += 1; - assert(WITHIN_STACK_BOUNDS()); break; } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index fca9b29f9ebc2e..87db28a3de52d4 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -635,6 +635,9 @@ def has_error_without_pop(op: parser.CodeDef) -> bool: "_PyLong_IsNegative", "_PyLong_IsNonNegativeCompact", "_PyLong_IsZero", + "_PyLong_Add", + "_PyLong_Multiply", + "_PyLong_Subtract", "_PyManagedDictPointer_IsValues", "_PyObject_GC_IS_SHARED", "_PyObject_GC_IS_TRACKED", diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 077300d9ef7b93..5784de32355d82 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -138,7 +138,6 @@ def __init__(self, out: CWriter, labels: dict[str, Label]): } self.out = out self.labels = labels - self.is_abstract = False def emit_to_with_replacement( self, @@ -512,7 +511,7 @@ def emit_SimpleStmt( reachable = True tkn = stmt.contents[-1] try: - if stmt in uop.properties.escaping_calls and not self.is_abstract: + if stmt in uop.properties.escaping_calls: escape = uop.properties.escaping_calls[stmt] if escape.kills is not None: self.stackref_kill(escape.kills, storage, True) @@ -549,7 +548,7 @@ def emit_SimpleStmt( self.out.emit(tkn) else: self.out.emit(tkn) - if stmt in uop.properties.escaping_calls and not self.is_abstract: + if stmt in uop.properties.escaping_calls: self.emit_reload(storage) return reachable, None, storage except StackError as ex: diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 5054a750d16a4b..c7fa562e0c27b1 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -146,7 +146,6 @@ class OptimizerEmitter(Emitter): def __init__(self, out: CWriter, labels: dict[str, Label], original_uop: Uop, stack: Stack): super().__init__(out, labels) self._replacers["REPLACE_OPCODE_IF_EVALUATES_PURE"] = self.replace_opcode_if_evaluates_pure - self.is_abstract = True self.original_uop = original_uop self.stack = stack @@ -169,6 +168,12 @@ def replace_opcode_if_evaluates_pure( inst: Instruction | None, ) -> bool: skip_to(tkn_iter, "SEMI") + + if self.original_uop.properties.escapes: + raise analysis_error( + f"REPLACE_OPCODE_IF_EVALUATES_PURE cannot be used with an escaping uop {self.original_uop.properties.escaping_calls}", + self.original_uop.body.open + ) emitter = OptimizerConstantEmitter(self.out, {}, self.original_uop, copy.deepcopy(self.stack)) emitter.emit("if (\n") input_identifiers = replace_opcode_if_evaluates_pure_identifiers(uop) @@ -256,11 +261,12 @@ def write_uop( override: Uop | None, uop: Uop, out: CWriter, + stack: Stack, debug: bool, + skip_inputs: bool, ) -> None: locals: dict[str, Local] = {} prototype = override if override else uop - stack = Stack() try: out.start_line() if override: @@ -285,8 +291,8 @@ def write_uop( # No reference management of inputs needed. for var in storage.inputs: # type: ignore[possibly-undefined] var.in_local = False + _, storage = emitter.emit_tokens(override, storage, None, False) out.start_line() - _, storage = emitter.emit_tokens(override, storage, inst=None, emit_braces=False) storage.flush(out) out.start_line() else: @@ -335,7 +341,8 @@ def generate_abstract_interpreter( declare_variables(override, out, skip_inputs=False) else: declare_variables(uop, out, skip_inputs=True) - write_uop(override, uop, out, debug) + stack = Stack() + write_uop(override, uop, out, stack, debug, skip_inputs=(override is None)) out.start_line() out.emit("break;\n") out.emit("}") From 01be0c6d927bdb289173dfa74b73c042c1a2cc93 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 13 Jun 2025 16:04:38 +0800 Subject: [PATCH 30/43] fix mypy --- Tools/cases_generator/optimizer_generator.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index c7fa562e0c27b1..6d1bfe41ec01c3 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -176,6 +176,7 @@ def replace_opcode_if_evaluates_pure( ) emitter = OptimizerConstantEmitter(self.out, {}, self.original_uop, copy.deepcopy(self.stack)) emitter.emit("if (\n") + assert isinstance(uop, Uop) input_identifiers = replace_opcode_if_evaluates_pure_identifiers(uop) assert len(input_identifiers) > 0, "Pure operations must have at least 1 input" for inp in input_identifiers[:-1]: From 9bef4a45f0f61cd517515c6544da96c0359513a0 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:24:21 +0800 Subject: [PATCH 31/43] revert changes for add,multiply,sub int --- Include/internal/pycore_opcode_metadata.h | 6 +- Include/internal/pycore_uop_metadata.h | 6 +- Python/executor_cases.c.h | 6 ++ Python/generated_cases.c.h | 6 ++ Python/optimizer_bytecodes.c | 52 ++++++++- Python/optimizer_cases.c.h | 124 ++++++++-------------- Tools/cases_generator/analyzer.py | 3 - 7 files changed, 108 insertions(+), 95 deletions(-) diff --git a/Include/internal/pycore_opcode_metadata.h b/Include/internal/pycore_opcode_metadata.h index 1af8fa2ae667de..00e918cb8f0cd1 100644 --- a/Include/internal/pycore_opcode_metadata.h +++ b/Include/internal/pycore_opcode_metadata.h @@ -1072,12 +1072,12 @@ extern const struct opcode_metadata _PyOpcode_opcode_metadata[267]; const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [BINARY_OP] = { true, INSTR_FMT_IBC0000, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_ADD_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_ADD_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, [BINARY_OP_EXTEND] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_INPLACE_ADD_UNICODE] = { true, INSTR_FMT_IXC0000, HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_MULTIPLY_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_MULTIPLY_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_SUBSCR_DICT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_SUBSCR_GETITEM] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG }, [BINARY_OP_SUBSCR_LIST_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, @@ -1085,7 +1085,7 @@ const struct opcode_metadata _PyOpcode_opcode_metadata[267] = { [BINARY_OP_SUBSCR_STR_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_SUBSCR_TUPLE_INT] = { true, INSTR_FMT_IXC0000, HAS_DEOPT_FLAG | HAS_EXIT_FLAG | HAS_ESCAPES_FLAG }, [BINARY_OP_SUBTRACT_FLOAT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, - [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG }, + [BINARY_OP_SUBTRACT_INT] = { true, INSTR_FMT_IXC0000, HAS_EXIT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BINARY_SLICE] = { true, INSTR_FMT_IX, HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BUILD_INTERPOLATION] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG }, [BUILD_LIST] = { true, INSTR_FMT_IB, HAS_ARG_FLAG | HAS_ERROR_FLAG | HAS_ERROR_NO_POP_FLAG | HAS_ESCAPES_FLAG }, diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 3bdab69f6e6ab7..b08909e72c4f43 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -85,9 +85,9 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_UNARY_INVERT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_NOS_INT] = HAS_EXIT_FLAG, [_GUARD_TOS_INT] = HAS_EXIT_FLAG, - [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, - [_BINARY_OP_ADD_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, - [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_MULTIPLY_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_ADD_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_SUBTRACT_INT] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG | HAS_PURE_FLAG, [_GUARD_NOS_FLOAT] = HAS_EXIT_FLAG, [_GUARD_TOS_FLOAT] = HAS_EXIT_FLAG, [_BINARY_OP_MULTIPLY_FLOAT] = HAS_ERROR_FLAG | HAS_PURE_FLAG, diff --git a/Python/executor_cases.c.h b/Python/executor_cases.c.h index 350de2c7de4546..4f772f916d1152 100644 --- a/Python/executor_cases.c.h +++ b/Python/executor_cases.c.h @@ -881,7 +881,9 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); + _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o); + stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { @@ -907,7 +909,9 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); + _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o); + stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { @@ -933,7 +937,9 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); + _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o); + stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { diff --git a/Python/generated_cases.c.h b/Python/generated_cases.c.h index 3ef3208081e459..5ac519bb1b6093 100644 --- a/Python/generated_cases.c.h +++ b/Python/generated_cases.c.h @@ -183,7 +183,9 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); + _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o); + stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { @@ -506,7 +508,9 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); + _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o); + stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { @@ -1085,7 +1089,9 @@ assert(PyLong_CheckExact(left_o)); assert(PyLong_CheckExact(right_o)); STAT_INC(BINARY_OP, hit); + _PyFrame_SetStackPointer(frame, stack_pointer); PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o); + stack_pointer = _PyFrame_GetStackPointer(frame); PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); if (res_o == NULL) { diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index a76121f3b9b523..6f064c7e67149a 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -222,17 +222,61 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); - res = sym_new_type(ctx, &PyLong_Type); + if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_type(ctx, &PyLong_Type); + } } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); + if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_type(ctx, &PyLong_Type); + } res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); + if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); + if (temp == NULL) { + goto error; + } + res = sym_new_const(ctx, temp); + Py_DECREF(temp); + // TODO gh-115506: + // replace opcode with constant propagated one and add tests! + } + else { + res = sym_new_type(ctx, &PyLong_Type); + } res = sym_new_type(ctx, &PyLong_Type); } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index b317ae1fa32620..e8b4a84f71f682 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -339,39 +339,26 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if ( - sym_is_safe_const(ctx, left) && - sym_is_safe_const(ctx, right) - ) { - JitOptSymbol *left_sym = left; - JitOptSymbol *right_sym = right; - _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); - _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); - _PyStackRef res_stackref; - /* Start of uop copied from bytecodes for constant evaluation */ - PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); - PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); - assert(PyLong_CheckExact(left_o)); - assert(PyLong_CheckExact(right_o)); - STAT_INC(BINARY_OP, hit); - PyObject *res_o = _PyLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o); - PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); - PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); - if (res_o == NULL) { - JUMP_TO_LABEL(pop_2_error); + if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Multiply((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); + if (temp == NULL) { + goto error; } - res_stackref = PyStackRef_FromPyObjectSteal(res_o); - /* End of uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + res = sym_new_const(ctx, temp); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); - break; + Py_DECREF(temp); + } + else { + res = sym_new_type(ctx, &PyLong_Type); + stack_pointer += -1; } res = sym_new_type(ctx, &PyLong_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = res; break; } @@ -381,39 +368,25 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if ( - sym_is_safe_const(ctx, left) && - sym_is_safe_const(ctx, right) - ) { - JitOptSymbol *left_sym = left; - JitOptSymbol *right_sym = right; - _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); - _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); - _PyStackRef res_stackref; - /* Start of uop copied from bytecodes for constant evaluation */ - PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); - PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); - assert(PyLong_CheckExact(left_o)); - assert(PyLong_CheckExact(right_o)); - STAT_INC(BINARY_OP, hit); - PyObject *res_o = _PyLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o); - PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); - PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); - if (res_o == NULL) { - JUMP_TO_LABEL(pop_2_error); + if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Add((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); + if (temp == NULL) { + goto error; } - res_stackref = PyStackRef_FromPyObjectSteal(res_o); - /* End of uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + res = sym_new_const(ctx, temp); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); - break; + Py_DECREF(temp); } - res = sym_new_type(ctx, &PyLong_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); + else { + res = sym_new_type(ctx, &PyLong_Type); + stack_pointer += -1; + } + stack_pointer[-1] = res; break; } @@ -423,39 +396,26 @@ JitOptSymbol *res; right = stack_pointer[-1]; left = stack_pointer[-2]; - if ( - sym_is_safe_const(ctx, left) && - sym_is_safe_const(ctx, right) - ) { - JitOptSymbol *left_sym = left; - JitOptSymbol *right_sym = right; - _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); - _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); - _PyStackRef res_stackref; - /* Start of uop copied from bytecodes for constant evaluation */ - PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); - PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); - assert(PyLong_CheckExact(left_o)); - assert(PyLong_CheckExact(right_o)); - STAT_INC(BINARY_OP, hit); - PyObject *res_o = _PyLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o); - PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); - PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); - if (res_o == NULL) { - JUMP_TO_LABEL(pop_2_error); + if (sym_is_const(ctx, left) && sym_is_const(ctx, right)) { + assert(PyLong_CheckExact(sym_get_const(ctx, left))); + assert(PyLong_CheckExact(sym_get_const(ctx, right))); + PyObject *temp = _PyLong_Subtract((PyLongObject *)sym_get_const(ctx, left), + (PyLongObject *)sym_get_const(ctx, right)); + if (temp == NULL) { + goto error; } - res_stackref = PyStackRef_FromPyObjectSteal(res_o); - /* End of uop copied from bytecodes for constant evaluation */ - res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + res = sym_new_const(ctx, temp); stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); - break; + Py_DECREF(temp); + } + else { + res = sym_new_type(ctx, &PyLong_Type); + stack_pointer += -1; } res = sym_new_type(ctx, &PyLong_Type); - stack_pointer[-2] = res; - stack_pointer += -1; - assert(WITHIN_STACK_BOUNDS()); + stack_pointer[-1] = res; break; } diff --git a/Tools/cases_generator/analyzer.py b/Tools/cases_generator/analyzer.py index 87db28a3de52d4..fca9b29f9ebc2e 100644 --- a/Tools/cases_generator/analyzer.py +++ b/Tools/cases_generator/analyzer.py @@ -635,9 +635,6 @@ def has_error_without_pop(op: parser.CodeDef) -> bool: "_PyLong_IsNegative", "_PyLong_IsNonNegativeCompact", "_PyLong_IsZero", - "_PyLong_Add", - "_PyLong_Multiply", - "_PyLong_Subtract", "_PyManagedDictPointer_IsValues", "_PyObject_GC_IS_SHARED", "_PyObject_GC_IS_TRACKED", From 1f76e2cf0d9c411efcf1b61c48f90ed8eafba947 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 13 Jun 2025 23:36:43 +0800 Subject: [PATCH 32/43] Fix tests --- Lib/test/test_generated_cases.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 90cba2d0e64baa..ac4aa910c965c8 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2226,10 +2226,10 @@ def test_validate_uop_unused_size_mismatch(self): def test_pure_uop_body_copied_in(self): # Note: any non-escaping call works. - # In this case, we use _PyLong_Add. + # In this case, we use PyStackRef_IsNone. input = """ pure op(OP, (foo -- res)) { - res = _PyLong_Add(foo); + res = PyStackRef_IsNone(foo); } """ input2 = """ @@ -2250,7 +2250,7 @@ def test_pure_uop_body_copied_in(self): _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); _PyStackRef res_stackref; /* Start of uop copied from bytecodes for constant evaluation */ - res_stackref = _PyLong_Add(foo); + res_stackref = PyStackRef_IsNone(foo); /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); stack_pointer[-1] = res; @@ -2267,7 +2267,7 @@ def test_pure_uop_body_copied_in_complex(self): input = """ pure op(OP, (foo -- res)) { if (foo) { - res = _PyLong_Add(foo); + res = PyStackRef_IsNone(foo); } else { res = 1; @@ -2293,7 +2293,7 @@ def test_pure_uop_body_copied_in_complex(self): _PyStackRef res_stackref; /* Start of uop copied from bytecodes for constant evaluation */ if (foo) { - res_stackref = _PyLong_Add(foo); + res_stackref = PyStackRef_IsNone(foo); } else { res_stackref = 1; @@ -2314,7 +2314,7 @@ def test_pure_uop_reject_array_effects(self): input = """ pure op(OP, (foo[2] -- res)) { if (foo) { - res = body(foo); + res = PyStackRef_IsNone(foo); } else { res = 1; From 029c92bdb3b479c61da2beac5c76b435c9e139e6 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:50:38 +0800 Subject: [PATCH 33/43] Add deopt and error_if, and tests --- Lib/test/test_generated_cases.py | 92 +++++++++++++- Python/optimizer_analysis.c | 1 - Python/optimizer_bytecodes.c | 3 + Python/optimizer_cases.c.h | 119 ++++++++++++++++++- Tools/cases_generator/generators_common.py | 28 +---- Tools/cases_generator/optimizer_generator.py | 78 ++++++++++++ 6 files changed, 288 insertions(+), 33 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index ac4aa910c965c8..2cb272451286a4 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2263,7 +2263,95 @@ def test_pure_uop_body_copied_in(self): """ self.run_cases_test(input, input2, output) - def test_pure_uop_body_copied_in_complex(self): + def test_pure_uop_body_copied_in_deopt(self): + # Note: any non-escaping call works. + # In this case, we use PyStackRef_IsNone. + input = """ + pure op(OP, (foo -- res)) { + DEOPT_IF(PyStackRef_IsNull(foo)); + res = foo; + } + """ + input2 = """ + op(OP, (foo -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(foo); + res = foo; + } + """ + output = """ + case OP: { + JitOptSymbol *foo; + JitOptSymbol *res; + foo = stack_pointer[-1]; + if ( + sym_is_safe_const(ctx, foo) + ) { + JitOptSymbol *foo_sym = foo; + _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); + _PyStackRef res_stackref; + /* Start of uop copied from bytecodes for constant evaluation */ + if (PyStackRef_IsNull(foo)) { + ctx->done = true; + break; + } + res_stackref = foo; + /* End of uop copied from bytecodes for constant evaluation */ + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + stack_pointer[-1] = res; + break; + } + res = foo; + stack_pointer[-1] = res; + break; + } + """ + self.run_cases_test(input, input2, output) + + def test_pure_uop_body_copied_in_error_if(self): + # Note: any non-escaping call works. + # In this case, we use PyStackRef_IsNone. + input = """ + pure op(OP, (foo -- res)) { + ERROR_IF(PyStackRef_IsNull(foo)); + res = foo; + } + """ + input2 = """ + op(OP, (foo -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(foo); + res = foo; + } + """ + output = """ + case OP: { + JitOptSymbol *foo; + JitOptSymbol *res; + foo = stack_pointer[-1]; + if ( + sym_is_safe_const(ctx, foo) + ) { + JitOptSymbol *foo_sym = foo; + _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); + _PyStackRef res_stackref; + /* Start of uop copied from bytecodes for constant evaluation */ + if (PyStackRef_IsNull(foo)) { + goto error; + } + res_stackref = foo; + /* End of uop copied from bytecodes for constant evaluation */ + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + stack_pointer[-1] = res; + break; + } + res = foo; + stack_pointer[-1] = res; + break; + } + """ + self.run_cases_test(input, input2, output) + + + def test_replace_opcode_uop_body_copied_in_complex(self): input = """ pure op(OP, (foo -- res)) { if (foo) { @@ -2310,7 +2398,7 @@ def test_pure_uop_body_copied_in_complex(self): """ self.run_cases_test(input, input2, output) - def test_pure_uop_reject_array_effects(self): + def test_replace_opocode_uop_reject_array_effects(self): input = """ pure op(OP, (foo[2] -- res)) { if (foo) { diff --git a/Python/optimizer_analysis.c b/Python/optimizer_analysis.c index a8337a741d7bbc..08c45db6ee6c5f 100644 --- a/Python/optimizer_analysis.c +++ b/Python/optimizer_analysis.c @@ -536,7 +536,6 @@ optimize_uops( } return trace_len; -pop_2_error: error: DPRINTF(3, "\n"); DPRINTF(1, "Encountered error in abstract interpreter\n"); diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index de8d446e8549a4..6bb2e585a7a792 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -222,14 +222,17 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyLong_Type); } diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 41cd2b3ec6a81f..e7885d6d63f20e 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -334,7 +334,44 @@ } case _BINARY_OP_MULTIPLY_INT: { + JitOptSymbol *right; + JitOptSymbol *left; JitOptSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if ( + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) + ) { + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + /* Start of uop copied from bytecodes for constant evaluation */ + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyLong_CheckExact(left_o)); + assert(PyLong_CheckExact(right_o)); + if (!_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o)) { + ctx->done = true; + break; + } + STAT_INC(BINARY_OP, hit); + PyObject *res_o = _PyCompactLong_Multiply((PyLongObject *)left_o, (PyLongObject *)right_o); + PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); + if (res_o == NULL) { + goto error; + } + res_stackref = PyStackRef_FromPyObjectSteal(res_o); + /* End of uop copied from bytecodes for constant evaluation */ + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); + break; + } res = sym_new_type(ctx, &PyLong_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -343,7 +380,44 @@ } case _BINARY_OP_ADD_INT: { + JitOptSymbol *right; + JitOptSymbol *left; JitOptSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if ( + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) + ) { + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + /* Start of uop copied from bytecodes for constant evaluation */ + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyLong_CheckExact(left_o)); + assert(PyLong_CheckExact(right_o)); + if (!_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o)) { + ctx->done = true; + break; + } + STAT_INC(BINARY_OP, hit); + PyObject *res_o = _PyCompactLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o); + PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); + if (res_o == NULL) { + goto error; + } + res_stackref = PyStackRef_FromPyObjectSteal(res_o); + /* End of uop copied from bytecodes for constant evaluation */ + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); + break; + } res = sym_new_type(ctx, &PyLong_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -352,7 +426,44 @@ } case _BINARY_OP_SUBTRACT_INT: { + JitOptSymbol *right; + JitOptSymbol *left; JitOptSymbol *res; + right = stack_pointer[-1]; + left = stack_pointer[-2]; + if ( + sym_is_safe_const(ctx, left) && + sym_is_safe_const(ctx, right) + ) { + JitOptSymbol *left_sym = left; + JitOptSymbol *right_sym = right; + _PyStackRef left = sym_get_const_as_stackref(ctx, left_sym); + _PyStackRef right = sym_get_const_as_stackref(ctx, right_sym); + _PyStackRef res_stackref; + /* Start of uop copied from bytecodes for constant evaluation */ + PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); + PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); + assert(PyLong_CheckExact(left_o)); + assert(PyLong_CheckExact(right_o)); + if (!_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o)) { + ctx->done = true; + break; + } + STAT_INC(BINARY_OP, hit); + PyObject *res_o = _PyCompactLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o); + PyStackRef_CLOSE_SPECIALIZED(right, _PyLong_ExactDealloc); + PyStackRef_CLOSE_SPECIALIZED(left, _PyLong_ExactDealloc); + if (res_o == NULL) { + goto error; + } + res_stackref = PyStackRef_FromPyObjectSteal(res_o); + /* End of uop copied from bytecodes for constant evaluation */ + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); + break; + } res = sym_new_type(ctx, &PyLong_Type); stack_pointer[-2] = res; stack_pointer += -1; @@ -406,7 +517,7 @@ ((PyFloatObject *)right_o)->ob_fval; res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres); if (PyStackRef_IsNull(res_stackref )) { - JUMP_TO_LABEL(pop_2_error); + goto error; } /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); @@ -448,7 +559,7 @@ ((PyFloatObject *)right_o)->ob_fval; res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres); if (PyStackRef_IsNull(res_stackref )) { - JUMP_TO_LABEL(pop_2_error); + goto error; } /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); @@ -490,7 +601,7 @@ ((PyFloatObject *)right_o)->ob_fval; res_stackref = _PyFloat_FromDouble_ConsumeInputs(left, right, dres); if (PyStackRef_IsNull(res_stackref )) { - JUMP_TO_LABEL(pop_2_error); + goto error; } /* End of uop copied from bytecodes for constant evaluation */ res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); @@ -531,7 +642,7 @@ PyStackRef_CLOSE_SPECIALIZED(right, _PyUnicode_ExactDealloc); PyStackRef_CLOSE_SPECIALIZED(left, _PyUnicode_ExactDealloc); if (res_o == NULL) { - JUMP_TO_LABEL(pop_2_error); + goto error; } res_stackref = PyStackRef_FromPyObjectSteal(res_o); /* End of uop copied from bytecodes for constant evaluation */ diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 5784de32355d82..6aacb2457b2bb1 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -139,30 +139,6 @@ def __init__(self, out: CWriter, labels: dict[str, Label]): self.out = out self.labels = labels - def emit_to_with_replacement( - self, - out: CWriter, - tkn_iter: TokenIterator, - end: str, - uop: CodeSection, - storage: Storage, - inst: Instruction | None - ) -> Token: - parens = 0 - for tkn in tkn_iter: - if tkn.kind == end and parens == 0: - return tkn - if tkn.kind == "LPAREN": - parens += 1 - if tkn.kind == "RPAREN": - parens -= 1 - if tkn.text in self._replacers: - self._replacers[tkn.text](tkn, tkn_iter, uop, storage, inst) - else: - out.emit(tkn) - raise analysis_error(f"Expecting {end}. Reached end of file", tkn) - - def dispatch( self, tkn: Token, @@ -190,7 +166,7 @@ def deopt_if( lparen = next(tkn_iter) assert lparen.kind == "LPAREN" first_tkn = tkn_iter.peek() - self.emit_to_with_replacement(self.out, tkn_iter, "RPAREN", uop, storage, inst) + emit_to(self.out, tkn_iter, "RPAREN") self.emit(") {\n") next(tkn_iter) # Semi colon assert inst is not None @@ -230,7 +206,7 @@ def error_if( else: self.out.emit_at("if ", tkn) self.emit(lparen) - self.emit_to_with_replacement(self.out, tkn_iter, "RPAREN", uop, storage, inst) + emit_to(self.out, tkn_iter, "RPAREN") self.out.emit(") {\n") next(tkn_iter) # Semi colon storage.clear_inputs("at ERROR_IF") diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 6d1bfe41ec01c3..092951bdf7cb74 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -23,6 +23,7 @@ Emitter, TokenIterator, skip_to, + always_true, ) from cwriter import CWriter from typing import TextIO @@ -227,6 +228,29 @@ def __init__(self, out: CWriter, labels: dict[str, Label], original_uop: Uop, st } self._replacers = {**self._replacers, **overrides} + def emit_to_with_replacement( + self, + out: CWriter, + tkn_iter: TokenIterator, + end: str, + uop: CodeSection, + storage: Storage, + inst: Instruction | None + ) -> Token: + parens = 0 + for tkn in tkn_iter: + if tkn.kind == end and parens == 0: + return tkn + if tkn.kind == "LPAREN": + parens += 1 + if tkn.kind == "RPAREN": + parens -= 1 + if tkn.text in self._replacers: + self._replacers[tkn.text](tkn, tkn_iter, uop, storage, inst) + else: + out.emit(tkn) + raise analysis_error(f"Expecting {end}. Reached end of file", tkn) + def emit_stackref_override( self, tkn: Token, @@ -239,6 +263,60 @@ def emit_stackref_override( self.out.emit("_stackref ") return True + def deopt_if( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ) -> bool: + self.out.start_line() + self.out.emit("if (") + lparen = next(tkn_iter) + assert lparen.kind == "LPAREN" + first_tkn = tkn_iter.peek() + self.emit_to_with_replacement(self.out, tkn_iter, "RPAREN", uop, storage, inst) + self.emit(") {\n") + next(tkn_iter) # Semi colon + # We guarantee this will deopt in real-world code + # via constants analysis. So just bail. + self.emit("ctx->done = true;\n") + self.emit("break;\n") + self.emit("}\n") + return not always_true(first_tkn) + + exit_if = deopt_if + + def error_if( + self, + tkn: Token, + tkn_iter: TokenIterator, + uop: CodeSection, + storage: Storage, + inst: Instruction | None, + ) -> bool: + lparen = next(tkn_iter) + assert lparen.kind == "LPAREN" + first_tkn = tkn_iter.peek() + unconditional = always_true(first_tkn) + if unconditional: + next(tkn_iter) + next(tkn_iter) # RPAREN + self.out.start_line() + else: + self.out.emit_at("if ", tkn) + self.emit(lparen) + self.emit_to_with_replacement(self.out, tkn_iter, "RPAREN", uop, storage, inst) + self.out.emit(") {\n") + next(tkn_iter) # Semi colon + storage.clear_inputs("at ERROR_IF") + + self.out.emit("goto error;\n") + if not unconditional: + self.out.emit("}\n") + return not unconditional + def replace_opcode_if_evaluates_pure_identifiers(uop: Uop) -> list[str]: token = None From 738d20ea899875a25c057a5be80f41172e5923cc Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Mon, 16 Jun 2025 15:53:57 +0800 Subject: [PATCH 34/43] remove is_abstract --- Tools/cases_generator/generators_common.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 6aacb2457b2bb1..71921ef57ede3a 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -116,7 +116,6 @@ class Emitter: out: CWriter labels: dict[str, Label] _replacers: dict[str, ReplacementFunctionType] - is_abstract: bool def __init__(self, out: CWriter, labels: dict[str, Label]): self._replacers = { From 4c0c52c18c1ec7732737e2268d0136ba989491fe Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:36:51 +0800 Subject: [PATCH 35/43] fix tests --- Lib/test/test_generated_cases.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index 21688b61b7c207..a21a4be434e20b 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2240,13 +2240,13 @@ def test_pure_uop_body_copied_in(self): """ output = """ case OP: { - JitOptSymbol *foo; - JitOptSymbol *res; + JitOptRef foo; + JitOptRef res; foo = stack_pointer[-1]; if ( sym_is_safe_const(ctx, foo) ) { - JitOptSymbol *foo_sym = foo; + JitOptRef foo_sym = foo; _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); _PyStackRef res_stackref; /* Start of uop copied from bytecodes for constant evaluation */ @@ -2280,13 +2280,13 @@ def test_pure_uop_body_copied_in_deopt(self): """ output = """ case OP: { - JitOptSymbol *foo; - JitOptSymbol *res; + JitOptRef foo; + JitOptRef res; foo = stack_pointer[-1]; if ( sym_is_safe_const(ctx, foo) ) { - JitOptSymbol *foo_sym = foo; + JitOptRef foo_sym = foo; _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); _PyStackRef res_stackref; /* Start of uop copied from bytecodes for constant evaluation */ @@ -2324,13 +2324,13 @@ def test_pure_uop_body_copied_in_error_if(self): """ output = """ case OP: { - JitOptSymbol *foo; - JitOptSymbol *res; + JitOptRef foo; + JitOptRef res; foo = stack_pointer[-1]; if ( sym_is_safe_const(ctx, foo) ) { - JitOptSymbol *foo_sym = foo; + JitOptRef foo_sym = foo; _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); _PyStackRef res_stackref; /* Start of uop copied from bytecodes for constant evaluation */ @@ -2370,13 +2370,13 @@ def test_replace_opcode_uop_body_copied_in_complex(self): """ output = """ case OP: { - JitOptSymbol *foo; - JitOptSymbol *res; + JitOptRef foo; + JitOptRef res; foo = stack_pointer[-1]; if ( sym_is_safe_const(ctx, foo) ) { - JitOptSymbol *foo_sym = foo; + JitOptRef foo_sym = foo; _PyStackRef foo = sym_get_const_as_stackref(ctx, foo_sym); _PyStackRef res_stackref; /* Start of uop copied from bytecodes for constant evaluation */ From e24e9a3e85485696969155f43b3e1cf94dbf81de Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Wed, 18 Jun 2025 21:39:31 +0800 Subject: [PATCH 36/43] Fix test_opt --- Python/optimizer_bytecodes.c | 12 ++++++++++++ Python/optimizer_cases.c.h | 9 +++++++++ 2 files changed, 21 insertions(+) diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index 8e401dcef1828b..726ea69188989e 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -239,16 +239,28 @@ dummy_func(void) { op(_BINARY_OP_ADD_FLOAT, (left, right -- res)) { REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); + // TODO (gh-134584): Refactor this to use another uop + if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) { + REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0); + } } op(_BINARY_OP_SUBTRACT_FLOAT, (left, right -- res)) { REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); + // TODO (gh-134584): Refactor this to use another uop + if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) { + REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0); + } } op(_BINARY_OP_MULTIPLY_FLOAT, (left, right -- res)) { REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_type(ctx, &PyFloat_Type); + // TODO (gh-134584): Refactor this to use another uop + if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) { + REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0); + } } op(_BINARY_OP_ADD_UNICODE, (left, right -- res)) { diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index e9283e3cbc21b7..73e19fd994de67 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -527,6 +527,9 @@ break; } res = sym_new_type(ctx, &PyFloat_Type); + if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) { + REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0); + } stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -569,6 +572,9 @@ break; } res = sym_new_type(ctx, &PyFloat_Type); + if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) { + REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0); + } stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); @@ -611,6 +617,9 @@ break; } res = sym_new_type(ctx, &PyFloat_Type); + if (PyJitRef_IsBorrowed(left) && PyJitRef_IsBorrowed(right)) { + REPLACE_OP(this_instr, op_without_decref_inputs[opcode], oparg, 0); + } stack_pointer[-2] = res; stack_pointer += -1; assert(WITHIN_STACK_BOUNDS()); From d54a6f255565bd2ef9fecdcb3143906e5f2375a9 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 20 Jun 2025 16:53:00 +0800 Subject: [PATCH 37/43] Update optimizer_bytecodes.c --- Python/optimizer_bytecodes.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index ff55958fbe3871..b01bab3156b41b 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -235,17 +235,17 @@ dummy_func(void) { } op(_BINARY_OP_ADD_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_compact_int(ctx); } op(_BINARY_OP_SUBTRACT_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_compact_int(ctx); } op(_BINARY_OP_MULTIPLY_INT, (left, right -- res)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); + REPLACE_OPCODE_IF_EVALUATES_PURE(left, right); res = sym_new_compact_int(ctx); } From 2644cb98505e48f882aef3fe50f20f124caf5e2e Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 20 Jun 2025 17:28:35 +0800 Subject: [PATCH 38/43] Address review --- Tools/cases_generator/optimizer_generator.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index a5c7b566b43309..689608e893f2ed 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -179,7 +179,11 @@ def replace_opcode_if_evaluates_pure( emitter.emit("if (\n") assert isinstance(uop, Uop) input_identifiers = replace_opcode_if_evaluates_pure_identifiers(uop) - assert len(input_identifiers) > 0, "Pure operations must have at least 1 input" + if len(input_identifiers) == 0: + raise analysis_error( + "Pure operations must have at least 1 input", + self.original_uop.body.open + ) for inp in input_identifiers[:-1]: emitter.emit(f"sym_is_safe_const(ctx, {inp}) &&\n") emitter.emit(f"sym_is_safe_const(ctx, {input_identifiers[-1]})\n") @@ -194,7 +198,11 @@ def replace_opcode_if_evaluates_pure( emitter.emit(f"{stackref_type_name(inp)}{inp.name} = sym_get_const_as_stackref(ctx, {inp.name}_sym);\n") # Rename all output variables to stackref variant. for outp in self.original_uop.stack.outputs: - assert not outp.is_array(), "Array output StackRefs not supported for pure ops." + if outp.is_array(): + raise analysis_error( + "Array output StackRefs not supported for evaluating pure ops.", + self.original_uop.body.open + ) emitter.emit(f"_PyStackRef {outp.name}_stackref;\n") From 634ed26c8d6a1fe69a622d7ce48c65211b7046c3 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 20 Jun 2025 19:11:11 +0800 Subject: [PATCH 39/43] Add for _BINARY_OP --- Python/optimizer_bytecodes.c | 1 + Python/optimizer_cases.c.h | 25 ++++++++++++++++++++ Tools/cases_generator/optimizer_generator.py | 5 ---- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Python/optimizer_bytecodes.c b/Python/optimizer_bytecodes.c index b01bab3156b41b..8cbfe0917cc33c 100644 --- a/Python/optimizer_bytecodes.c +++ b/Python/optimizer_bytecodes.c @@ -181,6 +181,7 @@ dummy_func(void) { } op(_BINARY_OP, (lhs, rhs -- res)) { + REPLACE_OPCODE_IF_EVALUATES_PURE(lhs, rhs); bool lhs_int = sym_matches_type(lhs, &PyLong_Type); bool rhs_int = sym_matches_type(rhs, &PyLong_Type); bool lhs_float = sym_matches_type(lhs, &PyFloat_Type); diff --git a/Python/optimizer_cases.c.h b/Python/optimizer_cases.c.h index 30c94dbd34195e..0b8d26bef1e63c 100644 --- a/Python/optimizer_cases.c.h +++ b/Python/optimizer_cases.c.h @@ -2644,6 +2644,31 @@ JitOptRef res; rhs = stack_pointer[-1]; lhs = stack_pointer[-2]; + if ( + sym_is_safe_const(ctx, lhs) && + sym_is_safe_const(ctx, rhs) + ) { + JitOptRef lhs_sym = lhs; + JitOptRef rhs_sym = rhs; + _PyStackRef lhs = sym_get_const_as_stackref(ctx, lhs_sym); + _PyStackRef rhs = sym_get_const_as_stackref(ctx, rhs_sym); + _PyStackRef res_stackref; + /* Start of uop copied from bytecodes for constant evaluation */ + PyObject *lhs_o = PyStackRef_AsPyObjectBorrow(lhs); + PyObject *rhs_o = PyStackRef_AsPyObjectBorrow(rhs); + assert(_PyEval_BinaryOps[oparg]); + stack_pointer[-2] = res; + stack_pointer += -1; + assert(WITHIN_STACK_BOUNDS()); + PyObject *res_o = _PyEval_BinaryOps[oparg](lhs_o, rhs_o); + if (res_o == NULL) { + JUMP_TO_LABEL(error); + } + res_stackref = PyStackRef_FromPyObjectSteal(res_o); + /* End of uop copied from bytecodes for constant evaluation */ + res = sym_new_const_steal(ctx, PyStackRef_AsPyObjectSteal(res_stackref)); + break; + } bool lhs_int = sym_matches_type(lhs, &PyLong_Type); bool rhs_int = sym_matches_type(rhs, &PyLong_Type); bool lhs_float = sym_matches_type(lhs, &PyFloat_Type); diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 689608e893f2ed..1c5f2c40b68cc3 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -170,11 +170,6 @@ def replace_opcode_if_evaluates_pure( ) -> bool: skip_to(tkn_iter, "SEMI") - if self.original_uop.properties.escapes: - raise analysis_error( - f"REPLACE_OPCODE_IF_EVALUATES_PURE cannot be used with an escaping uop {self.original_uop.properties.escaping_calls}", - self.original_uop.body.open - ) emitter = OptimizerConstantEmitter(self.out, {}, self.original_uop, copy.deepcopy(self.stack)) emitter.emit("if (\n") assert isinstance(uop, Uop) From 0e9ce847822508864406db0602dfb2889b91adda Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:48:36 +0800 Subject: [PATCH 40/43] Address review --- Include/internal/pycore_uop_metadata.h | 2 +- Lib/test/test_generated_cases.py | 6 +- Python/bytecodes.c | 2 +- Tools/cases_generator/generators_common.py | 10 --- Tools/cases_generator/optimizer_generator.py | 65 ++++++++++---------- 5 files changed, 36 insertions(+), 49 deletions(-) diff --git a/Include/internal/pycore_uop_metadata.h b/Include/internal/pycore_uop_metadata.h index 11345a00785817..2d4c96af57011a 100644 --- a/Include/internal/pycore_uop_metadata.h +++ b/Include/internal/pycore_uop_metadata.h @@ -102,7 +102,7 @@ const uint16_t _PyUop_Flags[MAX_UOP_ID+1] = { [_BINARY_OP_ADD_UNICODE] = HAS_ERROR_FLAG | HAS_PURE_FLAG, [_BINARY_OP_INPLACE_ADD_UNICODE] = HAS_LOCAL_FLAG | HAS_DEOPT_FLAG | HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_GUARD_BINARY_OP_EXTEND] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, - [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG | HAS_PURE_FLAG, + [_BINARY_OP_EXTEND] = HAS_ESCAPES_FLAG, [_BINARY_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_STORE_SLICE] = HAS_ERROR_FLAG | HAS_ESCAPES_FLAG, [_BINARY_OP_SUBSCR_LIST_INT] = HAS_DEOPT_FLAG | HAS_ESCAPES_FLAG, diff --git a/Lib/test/test_generated_cases.py b/Lib/test/test_generated_cases.py index a21a4be434e20b..eb01328b6ea946 100644 --- a/Lib/test/test_generated_cases.py +++ b/Lib/test/test_generated_cases.py @@ -2411,14 +2411,14 @@ def test_replace_opocode_uop_reject_array_effects(self): """ input2 = """ op(OP, (foo[2] -- res)) { - REPLACE_OPCODE_IF_EVALUATES_PURE(foo[2]); + REPLACE_OPCODE_IF_EVALUATES_PURE(foo); res = sym_new_unknown(ctx); } """ output = """ """ - with self.assertRaisesRegex(AssertionError, - "Unsafe to convert a symbol to an array-like StackRef."): + with self.assertRaisesRegex(SyntaxError, + "Pure evaluation cannot take array-like inputs"): self.run_cases_test(input, input2, output) if __name__ == "__main__": diff --git a/Python/bytecodes.c b/Python/bytecodes.c index 4af0f58d8ecb7d..b89f703990de2a 100644 --- a/Python/bytecodes.c +++ b/Python/bytecodes.c @@ -829,7 +829,7 @@ dummy_func( DEOPT_IF(!res); } - pure op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) { + op(_BINARY_OP_EXTEND, (descr/4, left, right -- res)) { PyObject *left_o = PyStackRef_AsPyObjectBorrow(left); PyObject *right_o = PyStackRef_AsPyObjectBorrow(right); assert(INLINE_CACHE_ENTRIES_BINARY_OP == 5); diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 71921ef57ede3a..20b8764d073739 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -86,16 +86,6 @@ def emit_to(out: CWriter, tkn_iter: TokenIterator, end: str) -> Token: out.emit(tkn) raise analysis_error(f"Expecting {end}. Reached end of file", tkn) -def skip_to(tkn_iter: TokenIterator, end: str) -> Token: - parens = 0 - for tkn in tkn_iter: - if tkn.kind == end and parens == 0: - return tkn - if tkn.kind == "LPAREN": - parens += 1 - if tkn.kind == "RPAREN": - parens -= 1 - raise analysis_error(f"Expecting {end}. Reached end of file", tkn) ReplacementFunctionType = Callable[ [Token, TokenIterator, CodeSection, Storage, Instruction | None], bool diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 1c5f2c40b68cc3..0db099d25a0b21 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -4,7 +4,6 @@ """ import argparse -import copy from analyzer import ( Analysis, @@ -22,7 +21,6 @@ write_header, Emitter, TokenIterator, - skip_to, always_true, ) from cwriter import CWriter @@ -81,8 +79,7 @@ def type_name(var: StackItem) -> str: return "JitOptRef " def stackref_type_name(var: StackItem) -> str: - if var.is_array(): - assert False, "Unsafe to convert a symbol to an array-like StackRef." + assert not var.is_array(), "Unsafe to convert a symbol to an array-like StackRef." return "_PyStackRef " def declare_variables(uop: Uop, out: CWriter, skip_inputs: bool) -> None: @@ -168,27 +165,45 @@ def replace_opcode_if_evaluates_pure( storage: Storage, inst: Instruction | None, ) -> bool: - skip_to(tkn_iter, "SEMI") + input_identifiers = [] + for token in tkn_iter: + if token.kind == "IDENTIFIER": + input_identifiers.append(token) + if token.kind == "SEMI": + break - emitter = OptimizerConstantEmitter(self.out, {}, self.original_uop, copy.deepcopy(self.stack)) - emitter.emit("if (\n") - assert isinstance(uop, Uop) - input_identifiers = replace_opcode_if_evaluates_pure_identifiers(uop) if len(input_identifiers) == 0: raise analysis_error( - "Pure operations must have at least 1 input", + "To evaluate an operation as pure, it must have at least 1 input", self.original_uop.body.open ) - for inp in input_identifiers[:-1]: - emitter.emit(f"sym_is_safe_const(ctx, {inp}) &&\n") - emitter.emit(f"sym_is_safe_const(ctx, {input_identifiers[-1]})\n") + # Check that the input identifiers belong to the uop's + # input stack effect + uop_stack_effect_input_identifers = {inp.name for inp in uop.stack.inputs} + for input_tkn in input_identifiers: + if input_tkn.text not in uop_stack_effect_input_identifers: + raise analysis_error(f"{input_tkn.text} referenced in " + f"REPLACE_OPCODE_IF_EVALUATES_PURE but does not " + f"exist in the base uop's input stack effects", + input_tkn) + input_identifiers_as_str = {tkn.text for tkn in input_identifiers} + used_stack_inputs = [inp for inp in uop.stack.inputs if inp.name in input_identifiers_as_str] + assert len(used_stack_inputs) > 0 + emitter = OptimizerConstantEmitter(self.out, {}, self.original_uop, self.stack.copy()) + emitter.emit("if (\n") + assert isinstance(uop, Uop) + for inp in used_stack_inputs[:-1]: + emitter.emit(f"sym_is_safe_const(ctx, {inp.name}) &&\n") + emitter.emit(f"sym_is_safe_const(ctx, {used_stack_inputs[-1].name})\n") emitter.emit(') {\n') # Declare variables, before they are shadowed. - for inp in self.original_uop.stack.inputs: + for inp in used_stack_inputs: if inp.used: emitter.emit(f"{type_name(inp)}{inp.name}_sym = {inp.name};\n") # Shadow the symbolic variables with stackrefs. - for inp in self.original_uop.stack.inputs: + for inp in used_stack_inputs: + if inp.is_array(): + raise analysis_error("Pure evaluation cannot take array-like inputs.", tkn) if inp.used: emitter.emit(f"{stackref_type_name(inp)}{inp.name} = sym_get_const_as_stackref(ctx, {inp.name}_sym);\n") # Rename all output variables to stackref variant. @@ -321,24 +336,6 @@ def error_if( return not unconditional -def replace_opcode_if_evaluates_pure_identifiers(uop: Uop) -> list[str]: - token = None - iterator = uop.body.tokens() - for token in iterator: - if token.kind == "IDENTIFIER" and token.text == "REPLACE_OPCODE_IF_EVALUATES_PURE": - break - assert token is not None - assert token.kind == "IDENTIFIER" and token.text == "REPLACE_OPCODE_IF_EVALUATES_PURE", uop.name - assert next(iterator).kind == "LPAREN" - idents = [] - for token in iterator: - if token.kind == "RPAREN": - break - if token.kind == "IDENTIFIER": - idents.append(token.text) - return idents - - def write_uop( override: Uop | None, uop: Uop, @@ -369,7 +366,7 @@ def write_uop( cast = f"uint{cache.size*16}_t" out.emit(f"{type}{cache.name} = ({cast})this_instr->operand0;\n") if override: - emitter = OptimizerEmitter(out, {}, uop, copy.deepcopy(stack)) + emitter = OptimizerEmitter(out, {}, uop, stack.copy()) # No reference management of inputs needed. for var in storage.inputs: # type: ignore[possibly-undefined] var.in_local = False From f2fc3fcc58f2eaf07bf4f9de853f23e6c10e14b5 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:49:37 +0800 Subject: [PATCH 41/43] reduce diff --- Tools/cases_generator/generators_common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/generators_common.py b/Tools/cases_generator/generators_common.py index 20b8764d073739..47de205c0e9120 100644 --- a/Tools/cases_generator/generators_common.py +++ b/Tools/cases_generator/generators_common.py @@ -653,7 +653,7 @@ def emit_tokens( code: CodeSection, storage: Storage, inst: Instruction | None, - emit_braces: bool = True, + emit_braces: bool = True ) -> tuple[bool, Storage]: self.out.start_line() reachable, tkn, storage = self.emit_BlockStmt(code.body, code, storage, inst, emit_braces) From 2408fb020850283384767fa50e8b63bd2bd87d49 Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 27 Jun 2025 17:51:00 +0800 Subject: [PATCH 42/43] fix mypy --- Tools/cases_generator/optimizer_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 0db099d25a0b21..41e533d1aed98a 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -165,6 +165,7 @@ def replace_opcode_if_evaluates_pure( storage: Storage, inst: Instruction | None, ) -> bool: + assert isinstance(uop, Uop) input_identifiers = [] for token in tkn_iter: if token.kind == "IDENTIFIER": @@ -191,7 +192,6 @@ def replace_opcode_if_evaluates_pure( assert len(used_stack_inputs) > 0 emitter = OptimizerConstantEmitter(self.out, {}, self.original_uop, self.stack.copy()) emitter.emit("if (\n") - assert isinstance(uop, Uop) for inp in used_stack_inputs[:-1]: emitter.emit(f"sym_is_safe_const(ctx, {inp.name}) &&\n") emitter.emit(f"sym_is_safe_const(ctx, {used_stack_inputs[-1].name})\n") From e40cb06992c22b1816884c633b29ea828675b70a Mon Sep 17 00:00:00 2001 From: Ken Jin <28750310+Fidget-Spinner@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:40:49 +0800 Subject: [PATCH 43/43] fix wrong error message --- Tools/cases_generator/optimizer_generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tools/cases_generator/optimizer_generator.py b/Tools/cases_generator/optimizer_generator.py index 41e533d1aed98a..4556b6d5a74f37 100644 --- a/Tools/cases_generator/optimizer_generator.py +++ b/Tools/cases_generator/optimizer_generator.py @@ -176,7 +176,7 @@ def replace_opcode_if_evaluates_pure( if len(input_identifiers) == 0: raise analysis_error( "To evaluate an operation as pure, it must have at least 1 input", - self.original_uop.body.open + tkn ) # Check that the input identifiers belong to the uop's # input stack effect 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