| #include "Python.h" | |
| #include "opcode.h" | |
| #include "pycore_c_array.h" // _Py_CArray_EnsureCapacity | |
| #include "pycore_flowgraph.h" | |
| #include "pycore_compile.h" | |
| #include "pycore_intrinsics.h" | |
| #include "pycore_pymem.h" // _PyMem_IsPtrFreed() | |
| #include "pycore_long.h" // _PY_IS_SMALL_INT() | |
| #include "pycore_hashtable.h" // _Py_hashtable_t | |
| #include "pycore_opcode_utils.h" | |
| #include "pycore_opcode_metadata.h" // OPCODE_HAS_ARG, etc | |
| #include "pycore_pystate.h" // _PyInterpreterState_GET() | |
| #include "pycore_stackref.h" // PyStackRef_AsPyObjectBorrow() | |
| #include <stdbool.h> | |
| #undef SUCCESS | |
| #undef ERROR | |
| #define SUCCESS 0 | |
| #define ERROR -1 | |
| #define RETURN_IF_ERROR(X) \ | |
| if ((X) == -1) { \ | |
| return ERROR; \ | |
| } | |
| #define DEFAULT_BLOCK_SIZE 16 | |
| typedef _Py_SourceLocation location; | |
| typedef _PyJumpTargetLabel jump_target_label; | |
| typedef struct _PyCfgInstruction { | |
| int i_opcode; | |
| int i_oparg; | |
| _Py_SourceLocation i_loc; | |
| struct _PyCfgBasicblock *i_target; /* target block (if jump instruction) */ | |
| struct _PyCfgBasicblock *i_except; /* target block when exception is raised */ | |
| } cfg_instr; | |
| typedef struct _PyCfgBasicblock { | |
| /* Each basicblock in a compilation unit is linked via b_list in the | |
| reverse order that blocks are allocated. b_list points to the | |
| previously allocated block, not to be confused with b_next, which is | |
| next by control flow. */ | |
| struct _PyCfgBasicblock *b_list; | |
| /* The label of this block if it is a jump target, -1 otherwise */ | |
| _PyJumpTargetLabel b_label; | |
| /* Exception stack at start of block, used by assembler to create the exception handling table */ | |
| struct _PyCfgExceptStack *b_exceptstack; | |
| /* pointer to an array of instructions, initially NULL */ | |
| cfg_instr *b_instr; | |
| /* If b_next is non-NULL, it is a pointer to the next | |
| block reached by normal control flow. */ | |
| struct _PyCfgBasicblock *b_next; | |
| /* number of instructions used */ | |
| int b_iused; | |
| /* length of instruction array (b_instr) */ | |
| int b_ialloc; | |
| /* Used by add_checks_for_loads_of_unknown_variables */ | |
| uint64_t b_unsafe_locals_mask; | |
| /* Number of predecessors that a block has. */ | |
| int b_predecessors; | |
| /* depth of stack upon entry of block, computed by stackdepth() */ | |
| int b_startdepth; | |
| /* Basic block is an exception handler that preserves lasti */ | |
| unsigned b_preserve_lasti : 1; | |
| /* Used by compiler passes to mark whether they have visited a basic block. */ | |
| unsigned b_visited : 1; | |
| /* b_except_handler is used by the cold-detection algorithm to mark exception targets */ | |
| unsigned b_except_handler : 1; | |
| /* b_cold is true if this block is not perf critical (like an exception handler) */ | |
| unsigned b_cold : 1; | |
| /* b_warm is used by the cold-detection algorithm to mark blocks which are definitely not cold */ | |
| unsigned b_warm : 1; | |
| } basicblock; | |
| struct _PyCfgBuilder { | |
| /* The entryblock, at which control flow begins. All blocks of the | |
| CFG are reachable through the b_next links */ | |
| struct _PyCfgBasicblock *g_entryblock; | |
| /* Pointer to the most recently allocated block. By following | |
| b_list links, you can reach all allocated blocks. */ | |
| struct _PyCfgBasicblock *g_block_list; | |
| /* pointer to the block currently being constructed */ | |
| struct _PyCfgBasicblock *g_curblock; | |
| /* label for the next instruction to be placed */ | |
| _PyJumpTargetLabel g_current_label; | |
| }; | |
| typedef struct _PyCfgBuilder cfg_builder; | |
| #define SAME_LABEL(L1, L2) ((L1).id == (L2).id) | |
| #define IS_LABEL(L) (!SAME_LABEL((L), (NO_LABEL))) | |
| #define LOCATION(LNO, END_LNO, COL, END_COL) \ | |
| ((const _Py_SourceLocation){(LNO), (END_LNO), (COL), (END_COL)}) | |
| static inline int | |
| is_block_push(cfg_instr *i) | |
| { | |
| assert(OPCODE_HAS_ARG(i->i_opcode) || !IS_BLOCK_PUSH_OPCODE(i->i_opcode)); | |
| return IS_BLOCK_PUSH_OPCODE(i->i_opcode); | |
| } | |
| static inline int | |
| is_jump(cfg_instr *i) | |
| { | |
| return OPCODE_HAS_JUMP(i->i_opcode); | |
| } | |
| /* One arg*/ | |
| #define INSTR_SET_OP1(I, OP, ARG) \ | |
| do { \ | |
| assert(OPCODE_HAS_ARG(OP)); \ | |
| cfg_instr *_instr__ptr_ = (I); \ | |
| _instr__ptr_->i_opcode = (OP); \ | |
| _instr__ptr_->i_oparg = (ARG); \ | |
| } while (0); | |
| /* No args*/ | |
| #define INSTR_SET_OP0(I, OP) \ | |
| do { \ | |
| assert(!OPCODE_HAS_ARG(OP)); \ | |
| cfg_instr *_instr__ptr_ = (I); \ | |
| _instr__ptr_->i_opcode = (OP); \ | |
| _instr__ptr_->i_oparg = 0; \ | |
| } while (0); | |
| #define INSTR_SET_LOC(I, LOC) \ | |
| do { \ | |
| cfg_instr *_instr__ptr_ = (I); \ | |
| _instr__ptr_->i_loc = (LOC); \ | |
| } while (0); | |
| /***** Blocks *****/ | |
| /* Returns the offset of the next instruction in the current block's | |
| b_instr array. Resizes the b_instr as necessary. | |
| Returns -1 on failure. | |
| */ | |
| static int | |
| basicblock_next_instr(basicblock *b) | |
| { | |
| assert(b != NULL); | |
| _Py_c_array_t array = { | |
| .array = (void*)b->b_instr, | |
| .allocated_entries = b->b_ialloc, | |
| .item_size = sizeof(cfg_instr), | |
| .initial_num_entries = DEFAULT_BLOCK_SIZE, | |
| }; | |
| RETURN_IF_ERROR(_Py_CArray_EnsureCapacity(&array, b->b_iused + 1)); | |
| b->b_instr = array.array; | |
| b->b_ialloc = array.allocated_entries; | |
| return b->b_iused++; | |
| } | |
| static cfg_instr * | |
| basicblock_last_instr(const basicblock *b) { | |
| assert(b->b_iused >= 0); | |
| if (b->b_iused > 0) { | |
| assert(b->b_instr != NULL); | |
| return &b->b_instr[b->b_iused - 1]; | |
| } | |
| return NULL; | |
| } | |
| /* Allocate a new block and return a pointer to it. | |
| Returns NULL on error. | |
| */ | |
| static basicblock * | |
| cfg_builder_new_block(cfg_builder *g) | |
| { | |
| basicblock *b = (basicblock *)PyMem_Calloc(1, sizeof(basicblock)); | |
| if (b == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| /* Extend the singly linked list of blocks with new block. */ | |
| b->b_list = g->g_block_list; | |
| g->g_block_list = b; | |
| b->b_label = NO_LABEL; | |
| return b; | |
| } | |
| static int | |
| basicblock_addop(basicblock *b, int opcode, int oparg, location loc, | |
| basicblock *except) | |
| { | |
| assert(IS_WITHIN_OPCODE_RANGE(opcode)); | |
| assert(!IS_ASSEMBLER_OPCODE(opcode)); | |
| assert(OPCODE_HAS_ARG(opcode) || HAS_TARGET(opcode) || oparg == 0); | |
| assert(0 <= oparg && oparg < (1 << 30)); | |
| int off = basicblock_next_instr(b); | |
| if (off < 0) { | |
| return ERROR; | |
| } | |
| cfg_instr *i = &b->b_instr[off]; | |
| i->i_opcode = opcode; | |
| i->i_oparg = oparg; | |
| i->i_loc = loc; | |
| i->i_except = except; | |
| // memory is already zero initialized | |
| assert(i->i_target == NULL); | |
| return SUCCESS; | |
| } | |
| static int | |
| basicblock_add_jump(basicblock *b, int opcode, basicblock *target, location loc, | |
| basicblock *except) | |
| { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| if (last && is_jump(last)) { | |
| return ERROR; | |
| } | |
| RETURN_IF_ERROR( | |
| basicblock_addop(b, opcode, target->b_label.id, loc, except)); | |
| last = basicblock_last_instr(b); | |
| assert(last && last->i_opcode == opcode); | |
| last->i_target = target; | |
| return SUCCESS; | |
| } | |
| static inline int | |
| basicblock_append_instructions(basicblock *to, basicblock *from) | |
| { | |
| for (int i = 0; i < from->b_iused; i++) { | |
| int n = basicblock_next_instr(to); | |
| if (n < 0) { | |
| return ERROR; | |
| } | |
| to->b_instr[n] = from->b_instr[i]; | |
| } | |
| return SUCCESS; | |
| } | |
| static inline int | |
| basicblock_nofallthrough(const basicblock *b) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| return (last && | |
| (IS_SCOPE_EXIT_OPCODE(last->i_opcode) || | |
| IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode))); | |
| } | |
| #define BB_NO_FALLTHROUGH(B) (basicblock_nofallthrough(B)) | |
| #define BB_HAS_FALLTHROUGH(B) (!basicblock_nofallthrough(B)) | |
| static basicblock * | |
| copy_basicblock(cfg_builder *g, basicblock *block) | |
| { | |
| /* Cannot copy a block if it has a fallthrough, since | |
| * a block can only have one fallthrough predecessor. | |
| */ | |
| assert(BB_NO_FALLTHROUGH(block)); | |
| basicblock *result = cfg_builder_new_block(g); | |
| if (result == NULL) { | |
| return NULL; | |
| } | |
| if (basicblock_append_instructions(result, block) < 0) { | |
| return NULL; | |
| } | |
| return result; | |
| } | |
| static int | |
| basicblock_insert_instruction(basicblock *block, int pos, cfg_instr *instr) { | |
| RETURN_IF_ERROR(basicblock_next_instr(block)); | |
| for (int i = block->b_iused - 1; i > pos; i--) { | |
| block->b_instr[i] = block->b_instr[i-1]; | |
| } | |
| block->b_instr[pos] = *instr; | |
| return SUCCESS; | |
| } | |
| /* For debugging purposes only */ | |
| #if 0 | |
| static void | |
| dump_instr(cfg_instr *i) | |
| { | |
| const char *jump = is_jump(i) ? "jump " : ""; | |
| char arg[128]; | |
| *arg = '\0'; | |
| if (OPCODE_HAS_ARG(i->i_opcode)) { | |
| sprintf(arg, "arg: %d ", i->i_oparg); | |
| } | |
| if (HAS_TARGET(i->i_opcode)) { | |
| sprintf(arg, "target: %p [%d] ", i->i_target, i->i_oparg); | |
| } | |
| fprintf(stderr, "line: %d, %s (%d) %s%s\n", | |
| i->i_loc.lineno, _PyOpcode_OpName[i->i_opcode], i->i_opcode, arg, jump); | |
| } | |
| static inline int | |
| basicblock_returns(const basicblock *b) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| return last && IS_RETURN_OPCODE(last->i_opcode); | |
| } | |
| static void | |
| dump_basicblock(const basicblock *b, bool highlight) | |
| { | |
| const char *b_return = basicblock_returns(b) ? "return " : ""; | |
| if (highlight) { | |
| fprintf(stderr, ">>> "); | |
| } | |
| fprintf(stderr, "%d: [EH=%d CLD=%d WRM=%d NO_FT=%d %p] used: %d, depth: %d, preds: %d %s\n", | |
| b->b_label.id, b->b_except_handler, b->b_cold, b->b_warm, BB_NO_FALLTHROUGH(b), b, b->b_iused, | |
| b->b_startdepth, b->b_predecessors, b_return); | |
| int depth = b->b_startdepth; | |
| if (b->b_instr) { | |
| int i; | |
| for (i = 0; i < b->b_iused; i++) { | |
| fprintf(stderr, " [%02d] depth: %d ", i, depth); | |
| dump_instr(b->b_instr + i); | |
| int popped = _PyOpcode_num_popped(b->b_instr[i].i_opcode, b->b_instr[i].i_oparg); | |
| int pushed = _PyOpcode_num_pushed(b->b_instr[i].i_opcode, b->b_instr[i].i_oparg); | |
| depth += (pushed - popped); | |
| } | |
| } | |
| } | |
| void | |
| _PyCfgBuilder_DumpGraph(const basicblock *entryblock, const basicblock *mark) | |
| { | |
| for (const basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| dump_basicblock(b, b == mark); | |
| } | |
| } | |
| #endif | |
| /***** CFG construction and modification *****/ | |
| static basicblock * | |
| cfg_builder_use_next_block(cfg_builder *g, basicblock *block) | |
| { | |
| assert(block != NULL); | |
| g->g_curblock->b_next = block; | |
| g->g_curblock = block; | |
| return block; | |
| } | |
| static inline int | |
| basicblock_exits_scope(const basicblock *b) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| return last && IS_SCOPE_EXIT_OPCODE(last->i_opcode); | |
| } | |
| static inline int | |
| basicblock_has_eval_break(const basicblock *b) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| if (OPCODE_HAS_EVAL_BREAK(b->b_instr[i].i_opcode)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| static bool | |
| cfg_builder_current_block_is_terminated(cfg_builder *g) | |
| { | |
| cfg_instr *last = basicblock_last_instr(g->g_curblock); | |
| if (last && IS_TERMINATOR_OPCODE(last->i_opcode)) { | |
| return true; | |
| } | |
| if (IS_LABEL(g->g_current_label)) { | |
| if (last || IS_LABEL(g->g_curblock->b_label)) { | |
| return true; | |
| } | |
| else { | |
| /* current block is empty, label it */ | |
| g->g_curblock->b_label = g->g_current_label; | |
| g->g_current_label = NO_LABEL; | |
| } | |
| } | |
| return false; | |
| } | |
| static int | |
| cfg_builder_maybe_start_new_block(cfg_builder *g) | |
| { | |
| if (cfg_builder_current_block_is_terminated(g)) { | |
| basicblock *b = cfg_builder_new_block(g); | |
| if (b == NULL) { | |
| return ERROR; | |
| } | |
| b->b_label = g->g_current_label; | |
| g->g_current_label = NO_LABEL; | |
| cfg_builder_use_next_block(g, b); | |
| } | |
| return SUCCESS; | |
| } | |
| #ifndef NDEBUG | |
| static bool | |
| cfg_builder_check(cfg_builder *g) | |
| { | |
| for (basicblock *block = g->g_block_list; block != NULL; block = block->b_list) { | |
| assert(!_PyMem_IsPtrFreed(block)); | |
| if (block->b_instr != NULL) { | |
| assert(block->b_ialloc > 0); | |
| assert(block->b_iused >= 0); | |
| assert(block->b_ialloc >= block->b_iused); | |
| } | |
| else { | |
| assert (block->b_iused == 0); | |
| assert (block->b_ialloc == 0); | |
| } | |
| } | |
| return true; | |
| } | |
| #endif | |
| static int | |
| init_cfg_builder(cfg_builder *g) | |
| { | |
| g->g_block_list = NULL; | |
| basicblock *block = cfg_builder_new_block(g); | |
| if (block == NULL) { | |
| return ERROR; | |
| } | |
| g->g_curblock = g->g_entryblock = block; | |
| g->g_current_label = NO_LABEL; | |
| return SUCCESS; | |
| } | |
| cfg_builder * | |
| _PyCfgBuilder_New(void) | |
| { | |
| cfg_builder *g = PyMem_Malloc(sizeof(cfg_builder)); | |
| if (g == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| memset(g, 0, sizeof(cfg_builder)); | |
| if (init_cfg_builder(g) < 0) { | |
| PyMem_Free(g); | |
| return NULL; | |
| } | |
| return g; | |
| } | |
| void | |
| _PyCfgBuilder_Free(cfg_builder *g) | |
| { | |
| if (g == NULL) { | |
| return; | |
| } | |
| assert(cfg_builder_check(g)); | |
| basicblock *b = g->g_block_list; | |
| while (b != NULL) { | |
| if (b->b_instr) { | |
| PyMem_Free((void *)b->b_instr); | |
| } | |
| basicblock *next = b->b_list; | |
| PyMem_Free((void *)b); | |
| b = next; | |
| } | |
| PyMem_Free(g); | |
| } | |
| int | |
| _PyCfgBuilder_CheckSize(cfg_builder *g) | |
| { | |
| int nblocks = 0; | |
| for (basicblock *b = g->g_block_list; b != NULL; b = b->b_list) { | |
| nblocks++; | |
| } | |
| if ((size_t)nblocks > SIZE_MAX / sizeof(basicblock *)) { | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| return SUCCESS; | |
| } | |
| int | |
| _PyCfgBuilder_UseLabel(cfg_builder *g, jump_target_label lbl) | |
| { | |
| g->g_current_label = lbl; | |
| return cfg_builder_maybe_start_new_block(g); | |
| } | |
| int | |
| _PyCfgBuilder_Addop(cfg_builder *g, int opcode, int oparg, location loc) | |
| { | |
| RETURN_IF_ERROR(cfg_builder_maybe_start_new_block(g)); | |
| return basicblock_addop(g->g_curblock, opcode, oparg, loc, NULL); | |
| } | |
| static basicblock * | |
| next_nonempty_block(basicblock *b) | |
| { | |
| while (b && b->b_iused == 0) { | |
| b = b->b_next; | |
| } | |
| return b; | |
| } | |
| /***** debugging helpers *****/ | |
| #ifndef NDEBUG | |
| static int remove_redundant_nops(cfg_builder *g); | |
| static bool | |
| no_redundant_nops(cfg_builder *g) { | |
| if (remove_redundant_nops(g) != 0) { | |
| return false; | |
| } | |
| return true; | |
| } | |
| static bool | |
| no_redundant_jumps(cfg_builder *g) { | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| if (last != NULL) { | |
| if (IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode)) { | |
| basicblock *next = next_nonempty_block(b->b_next); | |
| basicblock *jump_target = next_nonempty_block(last->i_target); | |
| if (jump_target == next) { | |
| assert(next); | |
| if (last->i_loc.lineno == next->b_instr[0].i_loc.lineno) { | |
| assert(0); | |
| return false; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| #endif | |
| /***** CFG preprocessing (jump targets and exceptions) *****/ | |
| static int | |
| normalize_jumps_in_block(cfg_builder *g, basicblock *b) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| if (last == NULL || !IS_CONDITIONAL_JUMP_OPCODE(last->i_opcode)) { | |
| return SUCCESS; | |
| } | |
| assert(!IS_ASSEMBLER_OPCODE(last->i_opcode)); | |
| bool is_forward = last->i_target->b_visited == 0; | |
| if (is_forward) { | |
| RETURN_IF_ERROR( | |
| basicblock_addop(b, NOT_TAKEN, 0, last->i_loc, last->i_except)); | |
| return SUCCESS; | |
| } | |
| int reversed_opcode = 0; | |
| switch(last->i_opcode) { | |
| case POP_JUMP_IF_NOT_NONE: | |
| reversed_opcode = POP_JUMP_IF_NONE; | |
| break; | |
| case POP_JUMP_IF_NONE: | |
| reversed_opcode = POP_JUMP_IF_NOT_NONE; | |
| break; | |
| case POP_JUMP_IF_FALSE: | |
| reversed_opcode = POP_JUMP_IF_TRUE; | |
| break; | |
| case POP_JUMP_IF_TRUE: | |
| reversed_opcode = POP_JUMP_IF_FALSE; | |
| break; | |
| } | |
| /* transform 'conditional jump T' to | |
| * 'reversed_jump b_next' followed by 'jump_backwards T' | |
| */ | |
| basicblock *target = last->i_target; | |
| basicblock *backwards_jump = cfg_builder_new_block(g); | |
| if (backwards_jump == NULL) { | |
| return ERROR; | |
| } | |
| RETURN_IF_ERROR( | |
| basicblock_addop(backwards_jump, NOT_TAKEN, 0, last->i_loc, | |
| last->i_except)); | |
| RETURN_IF_ERROR( | |
| basicblock_add_jump(backwards_jump, JUMP, target, last->i_loc, | |
| last->i_except)); | |
| backwards_jump->b_startdepth = target->b_startdepth; | |
| last->i_opcode = reversed_opcode; | |
| last->i_target = b->b_next; | |
| backwards_jump->b_cold = b->b_cold; | |
| backwards_jump->b_next = b->b_next; | |
| b->b_next = backwards_jump; | |
| return SUCCESS; | |
| } | |
| static int | |
| normalize_jumps(cfg_builder *g) | |
| { | |
| basicblock *entryblock = g->g_entryblock; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| b->b_visited = 0; | |
| } | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| b->b_visited = 1; | |
| RETURN_IF_ERROR(normalize_jumps_in_block(g, b)); | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| check_cfg(cfg_builder *g) { | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| /* Raise SystemError if jump or exit is not last instruction in the block. */ | |
| for (int i = 0; i < b->b_iused; i++) { | |
| int opcode = b->b_instr[i].i_opcode; | |
| assert(!IS_ASSEMBLER_OPCODE(opcode)); | |
| if (IS_TERMINATOR_OPCODE(opcode)) { | |
| if (i != b->b_iused - 1) { | |
| PyErr_SetString(PyExc_SystemError, "malformed control flow graph."); | |
| return ERROR; | |
| } | |
| } | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| get_max_label(basicblock *entryblock) | |
| { | |
| int lbl = -1; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| if (b->b_label.id > lbl) { | |
| lbl = b->b_label.id; | |
| } | |
| } | |
| return lbl; | |
| } | |
| /* Calculate the actual jump target from the target_label */ | |
| static int | |
| translate_jump_labels_to_targets(basicblock *entryblock) | |
| { | |
| int max_label = get_max_label(entryblock); | |
| size_t mapsize = sizeof(basicblock *) * (max_label + 1); | |
| basicblock **label2block = (basicblock **)PyMem_Malloc(mapsize); | |
| if (!label2block) { | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| memset(label2block, 0, mapsize); | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| if (b->b_label.id >= 0) { | |
| label2block[b->b_label.id] = b; | |
| } | |
| } | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| assert(instr->i_target == NULL); | |
| if (HAS_TARGET(instr->i_opcode)) { | |
| int lbl = instr->i_oparg; | |
| assert(lbl >= 0 && lbl <= max_label); | |
| instr->i_target = label2block[lbl]; | |
| assert(instr->i_target != NULL); | |
| assert(instr->i_target->b_label.id == lbl); | |
| } | |
| } | |
| } | |
| PyMem_Free(label2block); | |
| return SUCCESS; | |
| } | |
| static int | |
| mark_except_handlers(basicblock *entryblock) { | |
| #ifndef NDEBUG | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| assert(!b->b_except_handler); | |
| } | |
| #endif | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| for (int i=0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| if (is_block_push(instr)) { | |
| instr->i_target->b_except_handler = 1; | |
| } | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| struct _PyCfgExceptStack { | |
| basicblock *handlers[CO_MAXBLOCKS+2]; | |
| int depth; | |
| }; | |
| static basicblock * | |
| push_except_block(struct _PyCfgExceptStack *stack, cfg_instr *setup) { | |
| assert(is_block_push(setup)); | |
| int opcode = setup->i_opcode; | |
| basicblock * target = setup->i_target; | |
| if (opcode == SETUP_WITH || opcode == SETUP_CLEANUP) { | |
| target->b_preserve_lasti = 1; | |
| } | |
| assert(stack->depth <= CO_MAXBLOCKS); | |
| stack->handlers[++stack->depth] = target; | |
| return target; | |
| } | |
| static basicblock * | |
| pop_except_block(struct _PyCfgExceptStack *stack) { | |
| assert(stack->depth > 0); | |
| return stack->handlers[--stack->depth]; | |
| } | |
| static basicblock * | |
| except_stack_top(struct _PyCfgExceptStack *stack) { | |
| return stack->handlers[stack->depth]; | |
| } | |
| static struct _PyCfgExceptStack * | |
| make_except_stack(void) { | |
| struct _PyCfgExceptStack *new = PyMem_Malloc(sizeof(struct _PyCfgExceptStack)); | |
| if (new == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| new->depth = 0; | |
| new->handlers[0] = NULL; | |
| return new; | |
| } | |
| static struct _PyCfgExceptStack * | |
| copy_except_stack(struct _PyCfgExceptStack *stack) { | |
| struct _PyCfgExceptStack *copy = PyMem_Malloc(sizeof(struct _PyCfgExceptStack)); | |
| if (copy == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| memcpy(copy, stack, sizeof(struct _PyCfgExceptStack)); | |
| return copy; | |
| } | |
| static basicblock** | |
| make_cfg_traversal_stack(basicblock *entryblock) { | |
| int nblocks = 0; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| b->b_visited = 0; | |
| nblocks++; | |
| } | |
| basicblock **stack = (basicblock **)PyMem_Malloc(sizeof(basicblock *) * nblocks); | |
| if (!stack) { | |
| PyErr_NoMemory(); | |
| } | |
| return stack; | |
| } | |
| /* Compute the stack effects of opcode with argument oparg. | |
| Some opcodes have different stack effect when jump to the target and | |
| when not jump. The 'jump' parameter specifies the case: | |
| * 0 -- when not jump | |
| * 1 -- when jump | |
| * -1 -- maximal | |
| */ | |
| typedef struct { | |
| /* The stack effect of the instruction. */ | |
| int net; | |
| } stack_effects; | |
| Py_LOCAL(int) | |
| get_stack_effects(int opcode, int oparg, int jump, stack_effects *effects) | |
| { | |
| if (opcode < 0) { | |
| return -1; | |
| } | |
| if ((opcode <= MAX_REAL_OPCODE) && (_PyOpcode_Deopt[opcode] != opcode)) { | |
| // Specialized instructions are not supported. | |
| return -1; | |
| } | |
| int popped = _PyOpcode_num_popped(opcode, oparg); | |
| int pushed = _PyOpcode_num_pushed(opcode, oparg); | |
| if (popped < 0 || pushed < 0) { | |
| return -1; | |
| } | |
| if (IS_BLOCK_PUSH_OPCODE(opcode) && !jump) { | |
| effects->net = 0; | |
| return 0; | |
| } | |
| effects->net = pushed - popped; | |
| return 0; | |
| } | |
| Py_LOCAL_INLINE(int) | |
| stackdepth_push(basicblock ***sp, basicblock *b, int depth) | |
| { | |
| if (!(b->b_startdepth < 0 || b->b_startdepth == depth)) { | |
| PyErr_Format(PyExc_ValueError, "Invalid CFG, inconsistent stackdepth"); | |
| return ERROR; | |
| } | |
| if (b->b_startdepth < depth && b->b_startdepth < 100) { | |
| assert(b->b_startdepth < 0); | |
| b->b_startdepth = depth; | |
| *(*sp)++ = b; | |
| } | |
| return SUCCESS; | |
| } | |
| /* Find the flow path that needs the largest stack. We assume that | |
| * cycles in the flow graph have no net effect on the stack depth. | |
| */ | |
| static int | |
| calculate_stackdepth(cfg_builder *g) | |
| { | |
| basicblock *entryblock = g->g_entryblock; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| b->b_startdepth = INT_MIN; | |
| } | |
| basicblock **stack = make_cfg_traversal_stack(entryblock); | |
| if (!stack) { | |
| return ERROR; | |
| } | |
| int stackdepth = -1; | |
| int maxdepth = 0; | |
| basicblock **sp = stack; | |
| if (stackdepth_push(&sp, entryblock, 0) < 0) { | |
| goto error; | |
| } | |
| while (sp != stack) { | |
| basicblock *b = *--sp; | |
| int depth = b->b_startdepth; | |
| assert(depth >= 0); | |
| basicblock *next = b->b_next; | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| stack_effects effects; | |
| if (get_stack_effects(instr->i_opcode, instr->i_oparg, 0, &effects) < 0) { | |
| PyErr_Format(PyExc_SystemError, | |
| "Invalid stack effect for opcode=%d, arg=%i", | |
| instr->i_opcode, instr->i_oparg); | |
| goto error; | |
| } | |
| int new_depth = depth + effects.net; | |
| if (new_depth < 0) { | |
| PyErr_Format(PyExc_ValueError, | |
| "Invalid CFG, stack underflow at line %d", instr->i_loc.lineno); | |
| goto error; | |
| } | |
| maxdepth = Py_MAX(maxdepth, depth); | |
| if (HAS_TARGET(instr->i_opcode) && instr->i_opcode != END_ASYNC_FOR) { | |
| if (get_stack_effects(instr->i_opcode, instr->i_oparg, 1, &effects) < 0) { | |
| PyErr_Format(PyExc_SystemError, | |
| "Invalid stack effect for opcode=%d, arg=%i", | |
| instr->i_opcode, instr->i_oparg); | |
| goto error; | |
| } | |
| int target_depth = depth + effects.net; | |
| assert(target_depth >= 0); /* invalid code or bug in stackdepth() */ | |
| maxdepth = Py_MAX(maxdepth, depth); | |
| if (stackdepth_push(&sp, instr->i_target, target_depth) < 0) { | |
| goto error; | |
| } | |
| } | |
| depth = new_depth; | |
| assert(!IS_ASSEMBLER_OPCODE(instr->i_opcode)); | |
| if (IS_UNCONDITIONAL_JUMP_OPCODE(instr->i_opcode) || | |
| IS_SCOPE_EXIT_OPCODE(instr->i_opcode)) | |
| { | |
| /* remaining code is dead */ | |
| next = NULL; | |
| break; | |
| } | |
| } | |
| if (next != NULL) { | |
| assert(BB_HAS_FALLTHROUGH(b)); | |
| if (stackdepth_push(&sp, next, depth) < 0) { | |
| goto error; | |
| } | |
| } | |
| } | |
| stackdepth = maxdepth; | |
| error: | |
| PyMem_Free(stack); | |
| return stackdepth; | |
| } | |
| static int | |
| label_exception_targets(basicblock *entryblock) { | |
| basicblock **todo_stack = make_cfg_traversal_stack(entryblock); | |
| if (todo_stack == NULL) { | |
| return ERROR; | |
| } | |
| struct _PyCfgExceptStack *except_stack = make_except_stack(); | |
| if (except_stack == NULL) { | |
| PyMem_Free(todo_stack); | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| except_stack->depth = 0; | |
| todo_stack[0] = entryblock; | |
| entryblock->b_visited = 1; | |
| entryblock->b_exceptstack = except_stack; | |
| basicblock **todo = &todo_stack[1]; | |
| basicblock *handler = NULL; | |
| while (todo > todo_stack) { | |
| todo--; | |
| basicblock *b = todo[0]; | |
| assert(b->b_visited == 1); | |
| except_stack = b->b_exceptstack; | |
| assert(except_stack != NULL); | |
| b->b_exceptstack = NULL; | |
| handler = except_stack_top(except_stack); | |
| int last_yield_except_depth = -1; | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| if (is_block_push(instr)) { | |
| if (!instr->i_target->b_visited) { | |
| struct _PyCfgExceptStack *copy = copy_except_stack(except_stack); | |
| if (copy == NULL) { | |
| goto error; | |
| } | |
| instr->i_target->b_exceptstack = copy; | |
| todo[0] = instr->i_target; | |
| instr->i_target->b_visited = 1; | |
| todo++; | |
| } | |
| handler = push_except_block(except_stack, instr); | |
| } | |
| else if (instr->i_opcode == POP_BLOCK) { | |
| handler = pop_except_block(except_stack); | |
| INSTR_SET_OP0(instr, NOP); | |
| } | |
| else if (is_jump(instr)) { | |
| instr->i_except = handler; | |
| assert(i == b->b_iused -1); | |
| if (!instr->i_target->b_visited) { | |
| if (BB_HAS_FALLTHROUGH(b)) { | |
| struct _PyCfgExceptStack *copy = copy_except_stack(except_stack); | |
| if (copy == NULL) { | |
| goto error; | |
| } | |
| instr->i_target->b_exceptstack = copy; | |
| } | |
| else { | |
| instr->i_target->b_exceptstack = except_stack; | |
| except_stack = NULL; | |
| } | |
| todo[0] = instr->i_target; | |
| instr->i_target->b_visited = 1; | |
| todo++; | |
| } | |
| } | |
| else if (instr->i_opcode == YIELD_VALUE) { | |
| instr->i_except = handler; | |
| last_yield_except_depth = except_stack->depth; | |
| } | |
| else if (instr->i_opcode == RESUME) { | |
| instr->i_except = handler; | |
| if (instr->i_oparg != RESUME_AT_FUNC_START && instr->i_oparg != RESUME_AT_GEN_EXPR_START) { | |
| assert(last_yield_except_depth >= 0); | |
| if (last_yield_except_depth == 1) { | |
| instr->i_oparg |= RESUME_OPARG_DEPTH1_MASK; | |
| } | |
| last_yield_except_depth = -1; | |
| } | |
| } | |
| else if (instr->i_opcode == RETURN_GENERATOR) { | |
| instr->i_except = NULL; | |
| } | |
| else { | |
| instr->i_except = handler; | |
| } | |
| } | |
| if (BB_HAS_FALLTHROUGH(b) && !b->b_next->b_visited) { | |
| assert(except_stack != NULL); | |
| b->b_next->b_exceptstack = except_stack; | |
| todo[0] = b->b_next; | |
| b->b_next->b_visited = 1; | |
| todo++; | |
| } | |
| else if (except_stack != NULL) { | |
| PyMem_Free(except_stack); | |
| } | |
| } | |
| #ifdef Py_DEBUG | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| assert(b->b_exceptstack == NULL); | |
| } | |
| #endif | |
| PyMem_Free(todo_stack); | |
| return SUCCESS; | |
| error: | |
| PyMem_Free(todo_stack); | |
| PyMem_Free(except_stack); | |
| return ERROR; | |
| } | |
| /***** CFG optimizations *****/ | |
| static int | |
| remove_unreachable(basicblock *entryblock) { | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| b->b_predecessors = 0; | |
| } | |
| basicblock **stack = make_cfg_traversal_stack(entryblock); | |
| if (stack == NULL) { | |
| return ERROR; | |
| } | |
| basicblock **sp = stack; | |
| entryblock->b_predecessors = 1; | |
| *sp++ = entryblock; | |
| entryblock->b_visited = 1; | |
| while (sp > stack) { | |
| basicblock *b = *(--sp); | |
| if (b->b_next && BB_HAS_FALLTHROUGH(b)) { | |
| if (!b->b_next->b_visited) { | |
| assert(b->b_next->b_predecessors == 0); | |
| *sp++ = b->b_next; | |
| b->b_next->b_visited = 1; | |
| } | |
| b->b_next->b_predecessors++; | |
| } | |
| for (int i = 0; i < b->b_iused; i++) { | |
| basicblock *target; | |
| cfg_instr *instr = &b->b_instr[i]; | |
| if (is_jump(instr) || is_block_push(instr)) { | |
| target = instr->i_target; | |
| if (!target->b_visited) { | |
| *sp++ = target; | |
| target->b_visited = 1; | |
| } | |
| target->b_predecessors++; | |
| } | |
| } | |
| } | |
| PyMem_Free(stack); | |
| /* Delete unreachable instructions */ | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| if (b->b_predecessors == 0) { | |
| b->b_iused = 0; | |
| b->b_except_handler = 0; | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| basicblock_remove_redundant_nops(basicblock *bb) { | |
| /* Remove NOPs when legal to do so. */ | |
| int dest = 0; | |
| int prev_lineno = -1; | |
| for (int src = 0; src < bb->b_iused; src++) { | |
| int lineno = bb->b_instr[src].i_loc.lineno; | |
| if (bb->b_instr[src].i_opcode == NOP) { | |
| /* Eliminate no-op if it doesn't have a line number */ | |
| if (lineno < 0) { | |
| continue; | |
| } | |
| /* or, if the previous instruction had the same line number. */ | |
| if (prev_lineno == lineno) { | |
| continue; | |
| } | |
| /* or, if the next instruction has same line number or no line number */ | |
| if (src < bb->b_iused - 1) { | |
| int next_lineno = bb->b_instr[src+1].i_loc.lineno; | |
| if (next_lineno == lineno) { | |
| continue; | |
| } | |
| if (next_lineno < 0) { | |
| bb->b_instr[src+1].i_loc = bb->b_instr[src].i_loc; | |
| continue; | |
| } | |
| } | |
| else { | |
| basicblock *next = next_nonempty_block(bb->b_next); | |
| /* or if last instruction in BB and next BB has same line number */ | |
| if (next) { | |
| location next_loc = NO_LOCATION; | |
| for (int next_i=0; next_i < next->b_iused; next_i++) { | |
| cfg_instr *instr = &next->b_instr[next_i]; | |
| if (instr->i_opcode == NOP && instr->i_loc.lineno < 0) { | |
| /* Skip over NOPs without a location, they will be removed */ | |
| continue; | |
| } | |
| next_loc = instr->i_loc; | |
| break; | |
| } | |
| if (lineno == next_loc.lineno) { | |
| continue; | |
| } | |
| } | |
| } | |
| } | |
| if (dest != src) { | |
| bb->b_instr[dest] = bb->b_instr[src]; | |
| } | |
| dest++; | |
| prev_lineno = lineno; | |
| } | |
| assert(dest <= bb->b_iused); | |
| int num_removed = bb->b_iused - dest; | |
| bb->b_iused = dest; | |
| memset(&bb->b_instr[dest], 0, sizeof(cfg_instr) * num_removed); | |
| return num_removed; | |
| } | |
| static int | |
| remove_redundant_nops(cfg_builder *g) { | |
| int changes = 0; | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| int change = basicblock_remove_redundant_nops(b); | |
| RETURN_IF_ERROR(change); | |
| changes += change; | |
| } | |
| return changes; | |
| } | |
| static int loads_const(int opcode); | |
| static int | |
| remove_redundant_nops_and_pairs(basicblock *entryblock) | |
| { | |
| bool done = false; | |
| while (! done) { | |
| done = true; | |
| cfg_instr *prev_instr = NULL; | |
| cfg_instr *instr = NULL; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| RETURN_IF_ERROR(basicblock_remove_redundant_nops(b)); | |
| if (IS_LABEL(b->b_label)) { | |
| /* this block is a jump target, forget instr */ | |
| instr = NULL; | |
| } | |
| for (int i = 0; i < b->b_iused; i++) { | |
| prev_instr = instr; | |
| instr = &b->b_instr[i]; | |
| int prev_opcode = prev_instr ? prev_instr->i_opcode : 0; | |
| int prev_oparg = prev_instr ? prev_instr->i_oparg : 0; | |
| int opcode = instr->i_opcode; | |
| bool is_redundant_pair = false; | |
| if (opcode == POP_TOP) { | |
| if (loads_const(prev_opcode)) { | |
| is_redundant_pair = true; | |
| } | |
| else if (prev_opcode == COPY && prev_oparg == 1) { | |
| is_redundant_pair = true; | |
| } | |
| } | |
| if (is_redundant_pair) { | |
| INSTR_SET_OP0(prev_instr, NOP); | |
| INSTR_SET_OP0(instr, NOP); | |
| done = false; | |
| } | |
| } | |
| if ((instr && is_jump(instr)) || !BB_HAS_FALLTHROUGH(b)) { | |
| instr = NULL; | |
| } | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| remove_redundant_jumps(cfg_builder *g) { | |
| /* If a non-empty block ends with a jump instruction, check if the next | |
| * non-empty block reached through normal flow control is the target | |
| * of that jump. If it is, then the jump instruction is redundant and | |
| * can be deleted. | |
| * | |
| * Return the number of changes applied, or -1 on error. | |
| */ | |
| int changes = 0; | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| if (last == NULL) { | |
| continue; | |
| } | |
| assert(!IS_ASSEMBLER_OPCODE(last->i_opcode)); | |
| if (IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode)) { | |
| basicblock* jump_target = next_nonempty_block(last->i_target); | |
| if (jump_target == NULL) { | |
| PyErr_SetString(PyExc_SystemError, "jump with NULL target"); | |
| return ERROR; | |
| } | |
| basicblock *next = next_nonempty_block(b->b_next); | |
| if (jump_target == next) { | |
| changes++; | |
| INSTR_SET_OP0(last, NOP); | |
| } | |
| } | |
| } | |
| return changes; | |
| } | |
| static inline bool | |
| basicblock_has_no_lineno(basicblock *b) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| if (b->b_instr[i].i_loc.lineno >= 0) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| } | |
| /* Maximum size of basic block that should be copied in optimizer */ | |
| #define MAX_COPY_SIZE 4 | |
| /* If this block ends with an unconditional jump to a small exit block or | |
| * a block that has no line numbers (and no fallthrough), then | |
| * remove the jump and extend this block with the target. | |
| * Returns 1 if extended, 0 if no change, and -1 on error. | |
| */ | |
| static int | |
| basicblock_inline_small_or_no_lineno_blocks(basicblock *bb) { | |
| cfg_instr *last = basicblock_last_instr(bb); | |
| if (last == NULL) { | |
| return 0; | |
| } | |
| if (!IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode)) { | |
| return 0; | |
| } | |
| basicblock *target = last->i_target; | |
| bool small_exit_block = (basicblock_exits_scope(target) && | |
| target->b_iused <= MAX_COPY_SIZE); | |
| bool no_lineno_no_fallthrough = (basicblock_has_no_lineno(target) && | |
| !BB_HAS_FALLTHROUGH(target)); | |
| if (small_exit_block || no_lineno_no_fallthrough) { | |
| assert(is_jump(last)); | |
| int removed_jump_opcode = last->i_opcode; | |
| INSTR_SET_OP0(last, NOP); | |
| RETURN_IF_ERROR(basicblock_append_instructions(bb, target)); | |
| if (no_lineno_no_fallthrough) { | |
| last = basicblock_last_instr(bb); | |
| if (IS_UNCONDITIONAL_JUMP_OPCODE(last->i_opcode) && | |
| removed_jump_opcode == JUMP) | |
| { | |
| /* Make sure we don't lose eval breaker checks */ | |
| last->i_opcode = JUMP; | |
| } | |
| } | |
| target->b_predecessors--; | |
| return 1; | |
| } | |
| return 0; | |
| } | |
| static int | |
| inline_small_or_no_lineno_blocks(basicblock *entryblock) { | |
| bool changes; | |
| do { | |
| changes = false; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| int res = basicblock_inline_small_or_no_lineno_blocks(b); | |
| RETURN_IF_ERROR(res); | |
| if (res) { | |
| changes = true; | |
| } | |
| } | |
| } while(changes); /* every change removes a jump, ensuring convergence */ | |
| return changes; | |
| } | |
| // Attempt to eliminate jumps to jumps by updating inst to jump to | |
| // target->i_target using the provided opcode. Return whether or not the | |
| // optimization was successful. | |
| static bool | |
| jump_thread(basicblock *bb, cfg_instr *inst, cfg_instr *target, int opcode) | |
| { | |
| assert(is_jump(inst)); | |
| assert(is_jump(target)); | |
| assert(inst == basicblock_last_instr(bb)); | |
| // bpo-45773: If inst->i_target == target->i_target, then nothing actually | |
| // changes (and we fall into an infinite loop): | |
| if (inst->i_target != target->i_target) { | |
| /* Change inst to NOP and append a jump to target->i_target. The | |
| * NOP will be removed later if it's not needed for the lineno. | |
| */ | |
| INSTR_SET_OP0(inst, NOP); | |
| RETURN_IF_ERROR( | |
| basicblock_add_jump( | |
| bb, opcode, target->i_target, target->i_loc, | |
| target->i_except)); | |
| return true; | |
| } | |
| return false; | |
| } | |
| static int | |
| loads_const(int opcode) | |
| { | |
| return OPCODE_HAS_CONST(opcode) | |
| || opcode == LOAD_SMALL_INT | |
| || opcode == LOAD_COMMON_CONSTANT; | |
| } | |
| /* Returns new reference */ | |
| static PyObject* | |
| get_const_value(int opcode, int oparg, PyObject *co_consts) | |
| { | |
| PyObject *constant = NULL; | |
| assert(loads_const(opcode)); | |
| if (opcode == LOAD_CONST) { | |
| assert(PyList_Check(co_consts)); | |
| Py_ssize_t n = PyList_GET_SIZE(co_consts); | |
| if (oparg < 0 || oparg >= n) { | |
| PyErr_Format(PyExc_ValueError, | |
| "LOAD_CONST index %d is out of range for consts (len=%zd)", | |
| oparg, n); | |
| return NULL; | |
| } | |
| constant = PyList_GET_ITEM(co_consts, oparg); | |
| } | |
| if (opcode == LOAD_SMALL_INT) { | |
| return PyLong_FromLong(oparg); | |
| } | |
| if (opcode == LOAD_COMMON_CONSTANT) { | |
| assert(oparg < NUM_COMMON_CONSTANTS); | |
| return PyStackRef_AsPyObjectBorrow( | |
| _PyInterpreterState_GET()->common_consts[oparg]); | |
| } | |
| if (constant == NULL) { | |
| PyErr_SetString(PyExc_SystemError, | |
| "Internal error: failed to get value of a constant"); | |
| return NULL; | |
| } | |
| return Py_NewRef(constant); | |
| } | |
| // Steals a reference to newconst. | |
| static int | |
| add_const(PyObject *newconst, PyObject *consts, PyObject *const_cache, | |
| _Py_hashtable_t *consts_index) | |
| { | |
| if (_PyCompile_ConstCacheMergeOne(const_cache, &newconst) < 0) { | |
| Py_DECREF(newconst); | |
| return -1; | |
| } | |
| _Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(consts_index, (void *)newconst); | |
| if (entry != NULL) { | |
| Py_DECREF(newconst); | |
| return (int)(uintptr_t)entry->value; | |
| } | |
| Py_ssize_t index = PyList_GET_SIZE(consts); | |
| if ((size_t)index >= (size_t)INT_MAX - 1) { | |
| PyErr_SetString(PyExc_OverflowError, "too many constants"); | |
| Py_DECREF(newconst); | |
| return -1; | |
| } | |
| if (PyList_Append(consts, newconst)) { | |
| Py_DECREF(newconst); | |
| return -1; | |
| } | |
| if (_Py_hashtable_set(consts_index, (void *)newconst, (void *)(uintptr_t)index) < 0) { | |
| PyList_SetSlice(consts, index, index + 1, NULL); | |
| Py_DECREF(newconst); | |
| PyErr_NoMemory(); | |
| return -1; | |
| } | |
| Py_DECREF(newconst); | |
| return (int)index; | |
| } | |
| /* | |
| Traverse the instructions of the basic block backwards from index "start", skipping over NOPs. | |
| Try to collect "size" number of consecutive instructions that load constants into the array "instrs". | |
| Caller must make sure that length of "instrs" is sufficient to fit in at least "size" instructions. | |
| Return boolean indicating whether "size" such instructions were found. | |
| */ | |
| static bool | |
| get_const_loading_instrs(basicblock *bb, int start, cfg_instr **instrs, int size) | |
| { | |
| assert(start < bb->b_iused); | |
| assert(size >= 0); | |
| assert(size <= _PY_STACK_USE_GUIDELINE); | |
| for (; start >= 0 && size > 0; start--) { | |
| cfg_instr *instr = &bb->b_instr[start]; | |
| if (instr->i_opcode == NOP) { | |
| continue; | |
| } | |
| if (!loads_const(instr->i_opcode)) { | |
| return false; | |
| } | |
| instrs[--size] = instr; | |
| } | |
| return size == 0; | |
| } | |
| /* | |
| Change every instruction in "instrs" NOP and set its location to NO_LOCATION. | |
| Caller must make sure "instrs" has at least "size" elements. | |
| */ | |
| static void | |
| nop_out(cfg_instr **instrs, int size) | |
| { | |
| for (int i = 0; i < size; i++) { | |
| cfg_instr *instr = instrs[i]; | |
| assert(instr->i_opcode != NOP); | |
| INSTR_SET_OP0(instr, NOP); | |
| INSTR_SET_LOC(instr, NO_LOCATION); | |
| } | |
| } | |
| /* Does not steal reference to "newconst". | |
| Return 1 if changed instruction to LOAD_SMALL_INT. | |
| Return 0 if could not change instruction to LOAD_SMALL_INT. | |
| Return -1 on error. | |
| */ | |
| static int | |
| maybe_instr_make_load_smallint(cfg_instr *instr, PyObject *newconst, | |
| PyObject *consts, PyObject *const_cache) | |
| { | |
| if (PyLong_CheckExact(newconst)) { | |
| int overflow; | |
| long val = PyLong_AsLongAndOverflow(newconst, &overflow); | |
| if (val == -1 && PyErr_Occurred()) { | |
| return -1; | |
| } | |
| if (!overflow && _PY_IS_SMALL_INT(val) && 0 <= val && val <= 255) { | |
| assert(_Py_IsImmortal(newconst)); | |
| INSTR_SET_OP1(instr, LOAD_SMALL_INT, (int)val); | |
| return 1; | |
| } | |
| } | |
| return 0; | |
| } | |
| /* Does not steal reference to "newconst". | |
| Return 1 if changed instruction to LOAD_COMMON_CONSTANT. | |
| Return 0 if could not change instruction to LOAD_COMMON_CONSTANT. | |
| Return -1 on error. | |
| */ | |
| static int | |
| maybe_instr_make_load_common_const(cfg_instr *instr, PyObject *newconst) | |
| { | |
| int oparg; | |
| if (newconst == Py_None) { | |
| oparg = CONSTANT_NONE; | |
| } | |
| else if (newconst == Py_True) { | |
| oparg = CONSTANT_TRUE; | |
| } | |
| else if (newconst == Py_False) { | |
| oparg = CONSTANT_FALSE; | |
| } | |
| else if (PyUnicode_CheckExact(newconst) | |
| && PyUnicode_GET_LENGTH(newconst) == 0) { | |
| oparg = CONSTANT_EMPTY_STR; | |
| } | |
| else if (PyTuple_CheckExact(newconst) | |
| && PyTuple_GET_SIZE(newconst) == 0) { | |
| oparg = CONSTANT_EMPTY_TUPLE; | |
| } | |
| else if (PyLong_CheckExact(newconst)) { | |
| int overflow; | |
| long val = PyLong_AsLongAndOverflow(newconst, &overflow); | |
| if (val == -1 && PyErr_Occurred()) { | |
| return -1; | |
| } | |
| if (overflow || val != -1) { | |
| return 0; | |
| } | |
| oparg = CONSTANT_MINUS_ONE; | |
| } | |
| else { | |
| return 0; | |
| } | |
| assert(_Py_IsImmortal(newconst)); | |
| INSTR_SET_OP1(instr, LOAD_COMMON_CONSTANT, oparg); | |
| return 1; | |
| } | |
| /* Steals reference to "newconst" */ | |
| static int | |
| instr_make_load_const(cfg_instr *instr, PyObject *newconst, | |
| PyObject *consts, PyObject *const_cache, | |
| _Py_hashtable_t *consts_index) | |
| { | |
| int res = maybe_instr_make_load_smallint(instr, newconst, consts, const_cache); | |
| if (res < 0) { | |
| Py_DECREF(newconst); | |
| return ERROR; | |
| } | |
| if (res > 0) { | |
| return SUCCESS; | |
| } | |
| res = maybe_instr_make_load_common_const(instr, newconst); | |
| if (res < 0) { | |
| Py_DECREF(newconst); | |
| return ERROR; | |
| } | |
| if (res > 0) { | |
| return SUCCESS; | |
| } | |
| int oparg = add_const(newconst, consts, const_cache, consts_index); | |
| RETURN_IF_ERROR(oparg); | |
| INSTR_SET_OP1(instr, LOAD_CONST, oparg); | |
| return SUCCESS; | |
| } | |
| /* Replace LOAD_CONST c1, LOAD_CONST c2 ... LOAD_CONST cn, BUILD_TUPLE n | |
| with LOAD_CONST (c1, c2, ... cn). | |
| The consts table must still be in list form so that the | |
| new constant (c1, c2, ... cn) can be appended. | |
| Called with codestr pointing to the first LOAD_CONST. | |
| */ | |
| static int | |
| fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, | |
| PyObject *const_cache, _Py_hashtable_t *consts_index) | |
| { | |
| /* Pre-conditions */ | |
| assert(PyDict_CheckExact(const_cache)); | |
| assert(PyList_CheckExact(consts)); | |
| cfg_instr *instr = &bb->b_instr[i]; | |
| assert(instr->i_opcode == BUILD_TUPLE); | |
| int seq_size = instr->i_oparg; | |
| if (seq_size > _PY_STACK_USE_GUIDELINE) { | |
| return SUCCESS; | |
| } | |
| cfg_instr *const_instrs[_PY_STACK_USE_GUIDELINE]; | |
| if (!get_const_loading_instrs(bb, i-1, const_instrs, seq_size)) { | |
| /* not a const sequence */ | |
| return SUCCESS; | |
| } | |
| PyObject *const_tuple = PyTuple_New((Py_ssize_t)seq_size); | |
| if (const_tuple == NULL) { | |
| return ERROR; | |
| } | |
| for (int i = 0; i < seq_size; i++) { | |
| cfg_instr *inst = const_instrs[i]; | |
| assert(loads_const(inst->i_opcode)); | |
| PyObject *element = get_const_value(inst->i_opcode, inst->i_oparg, consts); | |
| if (element == NULL) { | |
| Py_DECREF(const_tuple); | |
| return ERROR; | |
| } | |
| PyTuple_SET_ITEM(const_tuple, i, element); | |
| } | |
| nop_out(const_instrs, seq_size); | |
| return instr_make_load_const(instr, const_tuple, consts, const_cache, consts_index); | |
| } | |
| /* Replace: | |
| BUILD_LIST/BUILD_SET 0 | |
| LOAD_CONST c1 | |
| LIST_APPEND/SET_ADD 1 | |
| LOAD_CONST c2 | |
| LIST_APPEND/SET_ADD 1 | |
| ... | |
| LOAD_CONST cN | |
| LIST_APPEND/SET_ADD 1 | |
| [CALL_INTRINSIC_1 INTRINSIC_LIST_TO_TUPLE] <-- optional | |
| with: | |
| LOAD_CONST (c1, c2, ... cN) | |
| The instruction at `i` is either the LIST_TO_TUPLE intrinsic (so the | |
| immediately preceding non-NOP instruction is expected to be a | |
| LIST_APPEND, and only the BUILD_LIST/LIST_APPEND form is considered), | |
| or the trailing LIST_APPEND or SET_ADD itself, in which case the | |
| matching BUILD_LIST/BUILD_SET start is selected from its opcode, and | |
| for sets the result is wrapped in a frozenset. | |
| */ | |
| static int | |
| fold_constant_seq_into_load_const(basicblock *bb, int i, | |
| PyObject *consts, PyObject *const_cache, | |
| _Py_hashtable_t *consts_index) | |
| { | |
| assert(PyDict_CheckExact(const_cache)); | |
| assert(PyList_CheckExact(consts)); | |
| assert(i >= 0); | |
| assert(i < bb->b_iused); | |
| cfg_instr *target = &bb->b_instr[i]; | |
| assert(target->i_opcode == LIST_APPEND || target->i_opcode == SET_ADD || | |
| (target->i_opcode == CALL_INTRINSIC_1 && | |
| target->i_oparg == INTRINSIC_LIST_TO_TUPLE)); | |
| bool expected_append = target->i_opcode == CALL_INTRINSIC_1; | |
| int append_op = expected_append ? LIST_APPEND : target->i_opcode; | |
| assert(append_op == LIST_APPEND || append_op == SET_ADD); | |
| int build_op = append_op == LIST_APPEND ? BUILD_LIST : BUILD_SET; | |
| int consts_found = 0; | |
| /* Walking backward from `i`, we expect LIST_APPEND/SET_ADD and | |
| LOAD_CONST to alternate. If `i` is the trailing LIST_TO_TUPLE | |
| intrinsic, the next instruction back is an APPEND. If `i` is the | |
| trailing APPEND itself, the next instruction back is a LOAD_CONST. */ | |
| bool expect_append = expected_append; | |
| for (int pos = i - 1; pos >= 0; pos--) { | |
| cfg_instr *instr = &bb->b_instr[pos]; | |
| int opcode = instr->i_opcode; | |
| int oparg = instr->i_oparg; | |
| if (opcode == NOP) { | |
| continue; | |
| } | |
| if (opcode == build_op && oparg == 0) { | |
| if (!expect_append) { | |
| /* Not a sequence start. */ | |
| return SUCCESS; | |
| } | |
| /* Sequence start, we are done. */ | |
| PyObject *newconst = PyTuple_New((Py_ssize_t)consts_found); | |
| if (newconst == NULL) { | |
| return ERROR; | |
| } | |
| int newpos_start = expected_append ? i - 1 : i; | |
| for (int newpos = newpos_start; newpos >= pos; newpos--) { | |
| instr = &bb->b_instr[newpos]; | |
| if (instr->i_opcode == NOP) { | |
| continue; | |
| } | |
| if (loads_const(instr->i_opcode)) { | |
| PyObject *constant = get_const_value(instr->i_opcode, instr->i_oparg, consts); | |
| if (constant == NULL) { | |
| Py_DECREF(newconst); | |
| return ERROR; | |
| } | |
| assert(consts_found > 0); | |
| PyTuple_SET_ITEM(newconst, --consts_found, constant); | |
| } | |
| nop_out(&instr, 1); | |
| } | |
| assert(consts_found == 0); | |
| if (build_op == BUILD_SET) { | |
| PyObject *frozen = PyFrozenSet_New(newconst); | |
| Py_DECREF(newconst); | |
| if (frozen == NULL) { | |
| return ERROR; | |
| } | |
| newconst = frozen; | |
| } | |
| return instr_make_load_const(target, newconst, consts, const_cache, consts_index); | |
| } | |
| if (expect_append) { | |
| if (opcode != append_op || oparg != 1) { | |
| return SUCCESS; | |
| } | |
| } | |
| else { | |
| if (!loads_const(opcode)) { | |
| return SUCCESS; | |
| } | |
| consts_found++; | |
| } | |
| expect_append = !expect_append; | |
| } | |
| /* Did not find sequence start. */ | |
| return SUCCESS; | |
| } | |
| #define MIN_CONST_SEQUENCE_SIZE 3 | |
| /* | |
| Optimize lists and sets for: | |
| 1. "for" loop, comprehension or "in"/"not in" tests: | |
| Change literal list or set of constants into constant | |
| tuple or frozenset respectively. Change list of | |
| non-constants into tuple. | |
| 2. Constant literal lists/set with length >= MIN_CONST_SEQUENCE_SIZE: | |
| Replace LOAD_CONST c1, LOAD_CONST c2 ... LOAD_CONST cN, BUILD_LIST N | |
| with BUILD_LIST 0, LOAD_CONST (c1, c2, ... cN), LIST_EXTEND 1, | |
| or BUILD_SET & SET_UPDATE respectively. | |
| */ | |
| static int | |
| optimize_lists_and_sets(basicblock *bb, int i, int nextop, | |
| PyObject *consts, PyObject *const_cache, | |
| _Py_hashtable_t *consts_index) | |
| { | |
| assert(PyDict_CheckExact(const_cache)); | |
| assert(PyList_CheckExact(consts)); | |
| cfg_instr *instr = &bb->b_instr[i]; | |
| assert(instr->i_opcode == BUILD_LIST || instr->i_opcode == BUILD_SET); | |
| bool contains_or_iter = nextop == GET_ITER || nextop == CONTAINS_OP; | |
| int seq_size = instr->i_oparg; | |
| if (seq_size > _PY_STACK_USE_GUIDELINE || | |
| (seq_size < MIN_CONST_SEQUENCE_SIZE && !contains_or_iter)) | |
| { | |
| return SUCCESS; | |
| } | |
| cfg_instr *const_instrs[_PY_STACK_USE_GUIDELINE]; | |
| if (!get_const_loading_instrs(bb, i-1, const_instrs, seq_size)) { /* not a const sequence */ | |
| if (contains_or_iter && instr->i_opcode == BUILD_LIST) { | |
| /* iterate over a tuple instead of list */ | |
| INSTR_SET_OP1(instr, BUILD_TUPLE, instr->i_oparg); | |
| } | |
| return SUCCESS; | |
| } | |
| PyObject *const_result = PyTuple_New((Py_ssize_t)seq_size); | |
| if (const_result == NULL) { | |
| return ERROR; | |
| } | |
| for (int i = 0; i < seq_size; i++) { | |
| cfg_instr *inst = const_instrs[i]; | |
| assert(loads_const(inst->i_opcode)); | |
| PyObject *element = get_const_value(inst->i_opcode, inst->i_oparg, consts); | |
| if (element == NULL) { | |
| Py_DECREF(const_result); | |
| return ERROR; | |
| } | |
| PyTuple_SET_ITEM(const_result, i, element); | |
| } | |
| if (instr->i_opcode == BUILD_SET) { | |
| PyObject *frozenset = PyFrozenSet_New(const_result); | |
| if (frozenset == NULL) { | |
| Py_DECREF(const_result); | |
| return ERROR; | |
| } | |
| Py_SETREF(const_result, frozenset); | |
| } | |
| int index = add_const(const_result, consts, const_cache, consts_index); | |
| RETURN_IF_ERROR(index); | |
| nop_out(const_instrs, seq_size); | |
| if (contains_or_iter) { | |
| INSTR_SET_OP1(instr, LOAD_CONST, index); | |
| } | |
| else { | |
| assert(i >= 2); | |
| assert(instr->i_opcode == BUILD_LIST || instr->i_opcode == BUILD_SET); | |
| INSTR_SET_LOC(&bb->b_instr[i-2], instr->i_loc); | |
| INSTR_SET_OP1(&bb->b_instr[i-2], instr->i_opcode, 0); | |
| INSTR_SET_OP1(&bb->b_instr[i-1], LOAD_CONST, index); | |
| INSTR_SET_OP1(&bb->b_instr[i], instr->i_opcode == BUILD_LIST ? LIST_EXTEND : SET_UPDATE, 1); | |
| } | |
| return SUCCESS; | |
| } | |
| /* Check whether the total number of items in the (possibly nested) collection obj exceeds | |
| * limit. Return a negative number if it does, and a non-negative number otherwise. | |
| * Used to avoid creating constants which are slow to hash. | |
| */ | |
| static Py_ssize_t | |
| const_folding_check_complexity(PyObject *obj, Py_ssize_t limit) | |
| { | |
| if (PyTuple_Check(obj)) { | |
| Py_ssize_t i; | |
| limit -= PyTuple_GET_SIZE(obj); | |
| for (i = 0; limit >= 0 && i < PyTuple_GET_SIZE(obj); i++) { | |
| limit = const_folding_check_complexity(PyTuple_GET_ITEM(obj, i), limit); | |
| if (limit < 0) { | |
| return limit; | |
| } | |
| } | |
| } | |
| return limit; | |
| } | |
| #define MAX_INT_SIZE 128 /* bits */ | |
| #define MAX_COLLECTION_SIZE 256 /* items */ | |
| #define MAX_STR_SIZE 4096 /* characters */ | |
| #define MAX_TOTAL_ITEMS 1024 /* including nested collections */ | |
| static PyObject * | |
| const_folding_safe_multiply(PyObject *v, PyObject *w) | |
| { | |
| if (PyLong_Check(v) && PyLong_Check(w) && | |
| !_PyLong_IsZero((PyLongObject *)v) && !_PyLong_IsZero((PyLongObject *)w) | |
| ) { | |
| int64_t vbits = _PyLong_NumBits(v); | |
| int64_t wbits = _PyLong_NumBits(w); | |
| assert(vbits >= 0); | |
| assert(wbits >= 0); | |
| if (vbits + wbits > MAX_INT_SIZE) { | |
| return NULL; | |
| } | |
| } | |
| else if (PyLong_Check(v) && PyTuple_Check(w)) { | |
| Py_ssize_t size = PyTuple_GET_SIZE(w); | |
| if (size) { | |
| long n = PyLong_AsLong(v); | |
| if (n < 0 || n > MAX_COLLECTION_SIZE / size) { | |
| return NULL; | |
| } | |
| if (n && const_folding_check_complexity(w, MAX_TOTAL_ITEMS / n) < 0) { | |
| return NULL; | |
| } | |
| } | |
| } | |
| else if (PyLong_Check(v) && (PyUnicode_Check(w) || PyBytes_Check(w))) { | |
| Py_ssize_t size = PyUnicode_Check(w) ? PyUnicode_GET_LENGTH(w) : | |
| PyBytes_GET_SIZE(w); | |
| if (size) { | |
| long n = PyLong_AsLong(v); | |
| if (n < 0 || n > MAX_STR_SIZE / size) { | |
| return NULL; | |
| } | |
| } | |
| } | |
| else if (PyLong_Check(w) && | |
| (PyTuple_Check(v) || PyUnicode_Check(v) || PyBytes_Check(v))) | |
| { | |
| return const_folding_safe_multiply(w, v); | |
| } | |
| return PyNumber_Multiply(v, w); | |
| } | |
| static PyObject * | |
| const_folding_safe_power(PyObject *v, PyObject *w) | |
| { | |
| if (PyLong_Check(v) && PyLong_Check(w) && | |
| !_PyLong_IsZero((PyLongObject *)v) && _PyLong_IsPositive((PyLongObject *)w) | |
| ) { | |
| int64_t vbits = _PyLong_NumBits(v); | |
| size_t wbits = PyLong_AsSize_t(w); | |
| assert(vbits >= 0); | |
| if (wbits == (size_t)-1) { | |
| return NULL; | |
| } | |
| if ((uint64_t)vbits > MAX_INT_SIZE / wbits) { | |
| return NULL; | |
| } | |
| } | |
| return PyNumber_Power(v, w, Py_None); | |
| } | |
| static PyObject * | |
| const_folding_safe_lshift(PyObject *v, PyObject *w) | |
| { | |
| if (PyLong_Check(v) && PyLong_Check(w) && | |
| !_PyLong_IsZero((PyLongObject *)v) && !_PyLong_IsZero((PyLongObject *)w) | |
| ) { | |
| int64_t vbits = _PyLong_NumBits(v); | |
| size_t wbits = PyLong_AsSize_t(w); | |
| assert(vbits >= 0); | |
| if (wbits == (size_t)-1) { | |
| return NULL; | |
| } | |
| if (wbits > MAX_INT_SIZE || (uint64_t)vbits > MAX_INT_SIZE - wbits) { | |
| return NULL; | |
| } | |
| } | |
| return PyNumber_Lshift(v, w); | |
| } | |
| static PyObject * | |
| const_folding_safe_mod(PyObject *v, PyObject *w) | |
| { | |
| if (PyUnicode_Check(v) || PyBytes_Check(v)) { | |
| return NULL; | |
| } | |
| return PyNumber_Remainder(v, w); | |
| } | |
| static PyObject * | |
| eval_const_binop(PyObject *left, int op, PyObject *right) | |
| { | |
| assert(left != NULL && right != NULL); | |
| assert(op >= 0 && op <= NB_OPARG_LAST); | |
| PyObject *result = NULL; | |
| switch (op) { | |
| case NB_ADD: | |
| result = PyNumber_Add(left, right); | |
| break; | |
| case NB_SUBTRACT: | |
| result = PyNumber_Subtract(left, right); | |
| break; | |
| case NB_MULTIPLY: | |
| result = const_folding_safe_multiply(left, right); | |
| break; | |
| case NB_TRUE_DIVIDE: | |
| result = PyNumber_TrueDivide(left, right); | |
| break; | |
| case NB_FLOOR_DIVIDE: | |
| result = PyNumber_FloorDivide(left, right); | |
| break; | |
| case NB_REMAINDER: | |
| result = const_folding_safe_mod(left, right); | |
| break; | |
| case NB_POWER: | |
| result = const_folding_safe_power(left, right); | |
| break; | |
| case NB_LSHIFT: | |
| result = const_folding_safe_lshift(left, right); | |
| break; | |
| case NB_RSHIFT: | |
| result = PyNumber_Rshift(left, right); | |
| break; | |
| case NB_OR: | |
| result = PyNumber_Or(left, right); | |
| break; | |
| case NB_XOR: | |
| result = PyNumber_Xor(left, right); | |
| break; | |
| case NB_AND: | |
| result = PyNumber_And(left, right); | |
| break; | |
| case NB_SUBSCR: | |
| result = PyObject_GetItem(left, right); | |
| break; | |
| case NB_MATRIX_MULTIPLY: | |
| // No builtin constants implement matrix multiplication | |
| break; | |
| default: | |
| Py_UNREACHABLE(); | |
| } | |
| return result; | |
| } | |
| static int | |
| fold_const_binop(basicblock *bb, int i, PyObject *consts, | |
| PyObject *const_cache, _Py_hashtable_t *consts_index) | |
| { | |
| #define BINOP_OPERAND_COUNT 2 | |
| assert(PyDict_CheckExact(const_cache)); | |
| assert(PyList_CheckExact(consts)); | |
| cfg_instr *binop = &bb->b_instr[i]; | |
| assert(binop->i_opcode == BINARY_OP); | |
| cfg_instr *operands_instrs[BINOP_OPERAND_COUNT]; | |
| if (!get_const_loading_instrs(bb, i-1, operands_instrs, BINOP_OPERAND_COUNT)) { | |
| /* not a const sequence */ | |
| return SUCCESS; | |
| } | |
| cfg_instr *lhs_instr = operands_instrs[0]; | |
| assert(loads_const(lhs_instr->i_opcode)); | |
| PyObject *lhs = get_const_value(lhs_instr->i_opcode, lhs_instr->i_oparg, consts); | |
| if (lhs == NULL) { | |
| return ERROR; | |
| } | |
| cfg_instr *rhs_instr = operands_instrs[1]; | |
| assert(loads_const(rhs_instr->i_opcode)); | |
| PyObject *rhs = get_const_value(rhs_instr->i_opcode, rhs_instr->i_oparg, consts); | |
| if (rhs == NULL) { | |
| Py_DECREF(lhs); | |
| return ERROR; | |
| } | |
| PyObject *newconst = eval_const_binop(lhs, binop->i_oparg, rhs); | |
| Py_DECREF(lhs); | |
| Py_DECREF(rhs); | |
| if (newconst == NULL) { | |
| if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) { | |
| return ERROR; | |
| } | |
| PyErr_Clear(); | |
| return SUCCESS; | |
| } | |
| nop_out(operands_instrs, BINOP_OPERAND_COUNT); | |
| return instr_make_load_const(binop, newconst, consts, const_cache, consts_index); | |
| } | |
| static PyObject * | |
| eval_const_unaryop(PyObject *operand, int opcode, int oparg) | |
| { | |
| assert(operand != NULL); | |
| assert( | |
| opcode == UNARY_NEGATIVE || | |
| opcode == UNARY_INVERT || | |
| opcode == UNARY_NOT || | |
| (opcode == CALL_INTRINSIC_1 && oparg == INTRINSIC_UNARY_POSITIVE) | |
| ); | |
| PyObject *result; | |
| switch (opcode) { | |
| case UNARY_NEGATIVE: | |
| result = PyNumber_Negative(operand); | |
| break; | |
| case UNARY_INVERT: | |
| // XXX: This should be removed once the ~bool depreciation expires. | |
| if (PyBool_Check(operand)) { | |
| return NULL; | |
| } | |
| result = PyNumber_Invert(operand); | |
| break; | |
| case UNARY_NOT: { | |
| int r = PyObject_IsTrue(operand); | |
| if (r < 0) { | |
| return NULL; | |
| } | |
| result = PyBool_FromLong(!r); | |
| break; | |
| } | |
| case CALL_INTRINSIC_1: | |
| if (oparg != INTRINSIC_UNARY_POSITIVE) { | |
| Py_UNREACHABLE(); | |
| } | |
| result = PyNumber_Positive(operand); | |
| break; | |
| default: | |
| Py_UNREACHABLE(); | |
| } | |
| return result; | |
| } | |
| static int | |
| fold_const_unaryop(basicblock *bb, int i, PyObject *consts, | |
| PyObject *const_cache, _Py_hashtable_t *consts_index) | |
| { | |
| #define UNARYOP_OPERAND_COUNT 1 | |
| assert(PyDict_CheckExact(const_cache)); | |
| assert(PyList_CheckExact(consts)); | |
| cfg_instr *unaryop = &bb->b_instr[i]; | |
| cfg_instr *operand_instr; | |
| if (!get_const_loading_instrs(bb, i-1, &operand_instr, UNARYOP_OPERAND_COUNT)) { | |
| /* not a const */ | |
| return SUCCESS; | |
| } | |
| assert(loads_const(operand_instr->i_opcode)); | |
| PyObject *operand = get_const_value( | |
| operand_instr->i_opcode, | |
| operand_instr->i_oparg, | |
| consts | |
| ); | |
| if (operand == NULL) { | |
| return ERROR; | |
| } | |
| PyObject *newconst = eval_const_unaryop(operand, unaryop->i_opcode, unaryop->i_oparg); | |
| Py_DECREF(operand); | |
| if (newconst == NULL) { | |
| if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) { | |
| return ERROR; | |
| } | |
| PyErr_Clear(); | |
| return SUCCESS; | |
| } | |
| if (unaryop->i_opcode == UNARY_NOT) { | |
| assert(PyBool_Check(newconst)); | |
| } | |
| nop_out(&operand_instr, UNARYOP_OPERAND_COUNT); | |
| return instr_make_load_const(unaryop, newconst, consts, const_cache, consts_index); | |
| } | |
| #define VISITED (-1) | |
| // Replace an arbitrary run of SWAPs and NOPs with an optimal one that has the | |
| // same effect. | |
| static int | |
| swaptimize(basicblock *block, int *ix) | |
| { | |
| // NOTE: "./python -m test test_patma" serves as a good, quick stress test | |
| // for this function. Make sure to blow away cached *.pyc files first! | |
| assert(*ix < block->b_iused); | |
| cfg_instr *instructions = &block->b_instr[*ix]; | |
| // Find the length of the current sequence of SWAPs and NOPs, and record the | |
| // maximum depth of the stack manipulations: | |
| assert(instructions[0].i_opcode == SWAP); | |
| int depth = instructions[0].i_oparg; | |
| int len = 0; | |
| int more = false; | |
| int limit = block->b_iused - *ix; | |
| while (++len < limit) { | |
| int opcode = instructions[len].i_opcode; | |
| if (opcode == SWAP) { | |
| depth = Py_MAX(depth, instructions[len].i_oparg); | |
| more = true; | |
| } | |
| else if (opcode != NOP) { | |
| break; | |
| } | |
| } | |
| // It's already optimal if there's only one SWAP: | |
| if (!more) { | |
| return SUCCESS; | |
| } | |
| // Create an array with elements {0, 1, 2, ..., depth - 1}: | |
| int *stack = PyMem_Malloc(depth * sizeof(int)); | |
| if (stack == NULL) { | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| for (int i = 0; i < depth; i++) { | |
| stack[i] = i; | |
| } | |
| // Simulate the combined effect of these instructions by "running" them on | |
| // our "stack": | |
| for (int i = 0; i < len; i++) { | |
| if (instructions[i].i_opcode == SWAP) { | |
| int oparg = instructions[i].i_oparg; | |
| int top = stack[0]; | |
| // SWAPs are 1-indexed: | |
| stack[0] = stack[oparg - 1]; | |
| stack[oparg - 1] = top; | |
| } | |
| } | |
| // Now we can begin! Our approach here is based on a solution to a closely | |
| // related problem (https://cs.stackexchange.com/a/13938). It's easiest to | |
| // think of this algorithm as determining the steps needed to efficiently | |
| // "un-shuffle" our stack. By performing the moves in *reverse* order, | |
| // though, we can efficiently *shuffle* it! For this reason, we will be | |
| // replacing instructions starting from the *end* of the run. Since the | |
| // solution is optimal, we don't need to worry about running out of space: | |
| int current = len - 1; | |
| for (int i = 0; i < depth; i++) { | |
| // Skip items that have already been visited, or just happen to be in | |
| // the correct location: | |
| if (stack[i] == VISITED || stack[i] == i) { | |
| continue; | |
| } | |
| // Okay, we've found an item that hasn't been visited. It forms a cycle | |
| // with other items; traversing the cycle and swapping each item with | |
| // the next will put them all in the correct place. The weird | |
| // loop-and-a-half is necessary to insert 0 into every cycle, since we | |
| // can only swap from that position: | |
| int j = i; | |
| while (true) { | |
| // Skip the actual swap if our item is zero, since swapping the top | |
| // item with itself is pointless: | |
| if (j) { | |
| assert(0 <= current); | |
| // SWAPs are 1-indexed: | |
| instructions[current].i_opcode = SWAP; | |
| instructions[current--].i_oparg = j + 1; | |
| } | |
| if (stack[j] == VISITED) { | |
| // Completed the cycle: | |
| assert(j == i); | |
| break; | |
| } | |
| int next_j = stack[j]; | |
| stack[j] = VISITED; | |
| j = next_j; | |
| } | |
| } | |
| // NOP out any unused instructions: | |
| while (0 <= current) { | |
| INSTR_SET_OP0(&instructions[current--], NOP); | |
| } | |
| PyMem_Free(stack); | |
| *ix += len - 1; | |
| return SUCCESS; | |
| } | |
| // This list is pretty small, since it's only okay to reorder opcodes that: | |
| // - can't affect control flow (like jumping or raising exceptions) | |
| // - can't invoke arbitrary code (besides finalizers) | |
| // - only touch the TOS (and pop it when finished) | |
| #define SWAPPABLE(opcode) \ | |
| ((opcode) == STORE_FAST || \ | |
| (opcode) == STORE_FAST_MAYBE_NULL || \ | |
| (opcode) == POP_TOP) | |
| #define STORES_TO(instr) \ | |
| (((instr).i_opcode == STORE_FAST || \ | |
| (instr).i_opcode == STORE_FAST_MAYBE_NULL) \ | |
| ? (instr).i_oparg : -1) | |
| static int | |
| next_swappable_instruction(basicblock *block, int i, int lineno) | |
| { | |
| while (++i < block->b_iused) { | |
| cfg_instr *instruction = &block->b_instr[i]; | |
| if (0 <= lineno && instruction->i_loc.lineno != lineno) { | |
| // Optimizing across this instruction could cause user-visible | |
| // changes in the names bound between line tracing events! | |
| return -1; | |
| } | |
| if (instruction->i_opcode == NOP) { | |
| continue; | |
| } | |
| if (SWAPPABLE(instruction->i_opcode)) { | |
| return i; | |
| } | |
| return -1; | |
| } | |
| return -1; | |
| } | |
| // Attempt to apply SWAPs statically by swapping *instructions* rather than | |
| // stack items. For example, we can replace SWAP(2), POP_TOP, STORE_FAST(42) | |
| // with the more efficient NOP, STORE_FAST(42), POP_TOP. | |
| static void | |
| apply_static_swaps(basicblock *block, int i) | |
| { | |
| // SWAPs are to our left, and potential swaperands are to our right: | |
| for (; 0 <= i; i--) { | |
| assert(i < block->b_iused); | |
| cfg_instr *swap = &block->b_instr[i]; | |
| if (swap->i_opcode != SWAP) { | |
| if (swap->i_opcode == NOP || SWAPPABLE(swap->i_opcode)) { | |
| // Nope, but we know how to handle these. Keep looking: | |
| continue; | |
| } | |
| // We can't reason about what this instruction does. Bail: | |
| return; | |
| } | |
| int j = next_swappable_instruction(block, i, -1); | |
| if (j < 0) { | |
| return; | |
| } | |
| int k = j; | |
| int lineno = block->b_instr[j].i_loc.lineno; | |
| for (int count = swap->i_oparg - 1; 0 < count; count--) { | |
| k = next_swappable_instruction(block, k, lineno); | |
| if (k < 0) { | |
| return; | |
| } | |
| } | |
| // The reordering is not safe if the two instructions to be swapped | |
| // store to the same location, or if any intervening instruction stores | |
| // to the same location as either of them. | |
| int store_j = STORES_TO(block->b_instr[j]); | |
| int store_k = STORES_TO(block->b_instr[k]); | |
| if (store_j >= 0 || store_k >= 0) { | |
| if (store_j == store_k) { | |
| return; | |
| } | |
| for (int idx = j + 1; idx < k; idx++) { | |
| int store_idx = STORES_TO(block->b_instr[idx]); | |
| if (store_idx >= 0 && (store_idx == store_j || store_idx == store_k)) { | |
| return; | |
| } | |
| } | |
| } | |
| // Success! | |
| INSTR_SET_OP0(swap, NOP); | |
| cfg_instr temp = block->b_instr[j]; | |
| block->b_instr[j] = block->b_instr[k]; | |
| block->b_instr[k] = temp; | |
| } | |
| } | |
| static int | |
| basicblock_optimize_load_const(PyObject *const_cache, basicblock *bb, | |
| PyObject *consts, _Py_hashtable_t *consts_index) | |
| { | |
| assert(PyDict_CheckExact(const_cache)); | |
| assert(PyList_CheckExact(consts)); | |
| int opcode = 0; | |
| int oparg = 0; | |
| for (int i = 0; i < bb->b_iused; i++) { | |
| cfg_instr *inst = &bb->b_instr[i]; | |
| if (inst->i_opcode == LOAD_CONST) { | |
| PyObject *constant = get_const_value(inst->i_opcode, inst->i_oparg, consts); | |
| if (constant == NULL) { | |
| return ERROR; | |
| } | |
| int res = maybe_instr_make_load_smallint(inst, constant, consts, const_cache); | |
| Py_DECREF(constant); | |
| if (res < 0) { | |
| return ERROR; | |
| } | |
| } | |
| bool is_copy_of_load_const = (opcode == LOAD_CONST && | |
| inst->i_opcode == COPY && | |
| inst->i_oparg == 1); | |
| if (! is_copy_of_load_const) { | |
| opcode = inst->i_opcode; | |
| oparg = inst->i_oparg; | |
| } | |
| assert(!IS_ASSEMBLER_OPCODE(opcode)); | |
| if (!loads_const(opcode)) { | |
| continue; | |
| } | |
| int nextop = i+1 < bb->b_iused ? bb->b_instr[i+1].i_opcode : 0; | |
| switch(nextop) { | |
| case POP_JUMP_IF_FALSE: | |
| case POP_JUMP_IF_TRUE: | |
| case JUMP_IF_FALSE: | |
| case JUMP_IF_TRUE: | |
| { | |
| /* Remove LOAD_CONST const; conditional jump */ | |
| PyObject* cnt = get_const_value(opcode, oparg, consts); | |
| if (cnt == NULL) { | |
| return ERROR; | |
| } | |
| int is_true = PyObject_IsTrue(cnt); | |
| Py_DECREF(cnt); | |
| if (is_true == -1) { | |
| return ERROR; | |
| } | |
| if (PyCompile_OpcodeStackEffect(nextop, 0) == -1) { | |
| /* POP_JUMP_IF_FALSE or POP_JUMP_IF_TRUE */ | |
| INSTR_SET_OP0(inst, NOP); | |
| } | |
| int jump_if_true = (nextop == POP_JUMP_IF_TRUE || nextop == JUMP_IF_TRUE); | |
| if (is_true == jump_if_true) { | |
| bb->b_instr[i+1].i_opcode = JUMP; | |
| } | |
| else { | |
| INSTR_SET_OP0(&bb->b_instr[i + 1], NOP); | |
| } | |
| break; | |
| } | |
| case IS_OP: | |
| { | |
| // Fold to POP_JUMP_IF_NONE: | |
| // - LOAD_CONST(None) IS_OP(0) POP_JUMP_IF_TRUE | |
| // - LOAD_CONST(None) IS_OP(1) POP_JUMP_IF_FALSE | |
| // - LOAD_CONST(None) IS_OP(0) TO_BOOL POP_JUMP_IF_TRUE | |
| // - LOAD_CONST(None) IS_OP(1) TO_BOOL POP_JUMP_IF_FALSE | |
| // Fold to POP_JUMP_IF_NOT_NONE: | |
| // - LOAD_CONST(None) IS_OP(0) POP_JUMP_IF_FALSE | |
| // - LOAD_CONST(None) IS_OP(1) POP_JUMP_IF_TRUE | |
| // - LOAD_CONST(None) IS_OP(0) TO_BOOL POP_JUMP_IF_FALSE | |
| // - LOAD_CONST(None) IS_OP(1) TO_BOOL POP_JUMP_IF_TRUE | |
| PyObject *cnt = get_const_value(opcode, oparg, consts); | |
| if (cnt == NULL) { | |
| return ERROR; | |
| } | |
| if (!Py_IsNone(cnt)) { | |
| Py_DECREF(cnt); | |
| break; | |
| } | |
| if (bb->b_iused <= i + 2) { | |
| break; | |
| } | |
| cfg_instr *is_instr = &bb->b_instr[i + 1]; | |
| cfg_instr *jump_instr = &bb->b_instr[i + 2]; | |
| // Get rid of TO_BOOL regardless: | |
| if (jump_instr->i_opcode == TO_BOOL) { | |
| INSTR_SET_OP0(jump_instr, NOP); | |
| if (bb->b_iused <= i + 3) { | |
| break; | |
| } | |
| jump_instr = &bb->b_instr[i + 3]; | |
| } | |
| bool invert = is_instr->i_oparg; | |
| if (jump_instr->i_opcode == POP_JUMP_IF_FALSE) { | |
| invert = !invert; | |
| } | |
| else if (jump_instr->i_opcode != POP_JUMP_IF_TRUE) { | |
| break; | |
| } | |
| INSTR_SET_OP0(inst, NOP); | |
| INSTR_SET_OP0(is_instr, NOP); | |
| jump_instr->i_opcode = invert ? POP_JUMP_IF_NOT_NONE | |
| : POP_JUMP_IF_NONE; | |
| break; | |
| } | |
| case TO_BOOL: | |
| { | |
| PyObject *cnt = get_const_value(opcode, oparg, consts); | |
| if (cnt == NULL) { | |
| return ERROR; | |
| } | |
| int is_true = PyObject_IsTrue(cnt); | |
| Py_DECREF(cnt); | |
| if (is_true == -1) { | |
| return ERROR; | |
| } | |
| cnt = PyBool_FromLong(is_true); | |
| int index = add_const(cnt, consts, const_cache, consts_index); | |
| if (index < 0) { | |
| return ERROR; | |
| } | |
| INSTR_SET_OP0(inst, NOP); | |
| INSTR_SET_OP1(&bb->b_instr[i + 1], LOAD_CONST, index); | |
| break; | |
| } | |
| } | |
| if (inst->i_opcode == LOAD_CONST) { | |
| PyObject *constant = get_const_value(inst->i_opcode, inst->i_oparg, consts); | |
| if (constant == NULL) { | |
| return ERROR; | |
| } | |
| int res = maybe_instr_make_load_common_const(inst, constant); | |
| Py_DECREF(constant); | |
| if (res < 0) { | |
| return ERROR; | |
| } | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| optimize_load_const(PyObject *const_cache, cfg_builder *g, PyObject *consts, | |
| _Py_hashtable_t *consts_index) { | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| RETURN_IF_ERROR(basicblock_optimize_load_const(const_cache, b, consts, consts_index)); | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts, | |
| _Py_hashtable_t *consts_index) | |
| { | |
| assert(PyDict_CheckExact(const_cache)); | |
| assert(PyList_CheckExact(consts)); | |
| cfg_instr nop; | |
| INSTR_SET_OP0(&nop, NOP); | |
| for (int i = 0; i < bb->b_iused; i++) { | |
| cfg_instr *inst = &bb->b_instr[i]; | |
| cfg_instr *target; | |
| int opcode = inst->i_opcode; | |
| int oparg = inst->i_oparg; | |
| if (HAS_TARGET(opcode)) { | |
| assert(inst->i_target->b_iused > 0); | |
| target = &inst->i_target->b_instr[0]; | |
| assert(!IS_ASSEMBLER_OPCODE(target->i_opcode)); | |
| } | |
| else { | |
| target = &nop; | |
| } | |
| int nextop = i+1 < bb->b_iused ? bb->b_instr[i+1].i_opcode : 0; | |
| assert(!IS_ASSEMBLER_OPCODE(opcode)); | |
| switch (opcode) { | |
| /* Try to fold tuples of constants. | |
| Skip over BUILD_TUPLE(1) UNPACK_SEQUENCE(1). | |
| Replace BUILD_TUPLE(2) UNPACK_SEQUENCE(2) with SWAP(2). | |
| Replace BUILD_TUPLE(3) UNPACK_SEQUENCE(3) with SWAP(3). */ | |
| case BUILD_TUPLE: | |
| if (nextop == UNPACK_SEQUENCE && oparg == bb->b_instr[i+1].i_oparg) { | |
| switch(oparg) { | |
| case 1: | |
| INSTR_SET_OP0(inst, NOP); | |
| INSTR_SET_OP0(&bb->b_instr[i + 1], NOP); | |
| continue; | |
| case 2: | |
| case 3: | |
| INSTR_SET_OP0(inst, NOP); | |
| bb->b_instr[i+1].i_opcode = SWAP; | |
| continue; | |
| } | |
| } | |
| RETURN_IF_ERROR(fold_tuple_of_constants(bb, i, consts, const_cache, consts_index)); | |
| break; | |
| case BUILD_LIST: | |
| case BUILD_SET: | |
| RETURN_IF_ERROR(optimize_lists_and_sets(bb, i, nextop, consts, const_cache, consts_index)); | |
| break; | |
| case POP_JUMP_IF_NOT_NONE: | |
| case POP_JUMP_IF_NONE: | |
| switch (target->i_opcode) { | |
| case JUMP: | |
| i -= jump_thread(bb, inst, target, inst->i_opcode); | |
| } | |
| break; | |
| case POP_JUMP_IF_FALSE: | |
| switch (target->i_opcode) { | |
| case JUMP: | |
| i -= jump_thread(bb, inst, target, POP_JUMP_IF_FALSE); | |
| } | |
| break; | |
| case POP_JUMP_IF_TRUE: | |
| switch (target->i_opcode) { | |
| case JUMP: | |
| i -= jump_thread(bb, inst, target, POP_JUMP_IF_TRUE); | |
| } | |
| break; | |
| case JUMP_IF_FALSE: | |
| switch (target->i_opcode) { | |
| case JUMP: | |
| case JUMP_IF_FALSE: | |
| i -= jump_thread(bb, inst, target, JUMP_IF_FALSE); | |
| continue; | |
| case JUMP_IF_TRUE: | |
| // No need to check for loops here, a block's b_next | |
| // cannot point to itself. | |
| assert(inst->i_target != inst->i_target->b_next); | |
| inst->i_target = inst->i_target->b_next; | |
| i--; | |
| continue; | |
| } | |
| break; | |
| case JUMP_IF_TRUE: | |
| switch (target->i_opcode) { | |
| case JUMP: | |
| case JUMP_IF_TRUE: | |
| i -= jump_thread(bb, inst, target, JUMP_IF_TRUE); | |
| continue; | |
| case JUMP_IF_FALSE: | |
| // No need to check for loops here, a block's b_next | |
| // cannot point to itself. | |
| assert(inst->i_target != inst->i_target->b_next); | |
| inst->i_target = inst->i_target->b_next; | |
| i--; | |
| continue; | |
| } | |
| break; | |
| case JUMP: | |
| case JUMP_NO_INTERRUPT: | |
| switch (target->i_opcode) { | |
| case JUMP: | |
| i -= jump_thread(bb, inst, target, JUMP); | |
| continue; | |
| case JUMP_NO_INTERRUPT: | |
| i -= jump_thread(bb, inst, target, opcode); | |
| continue; | |
| } | |
| break; | |
| case FOR_ITER: | |
| if (target->i_opcode == JUMP) { | |
| /* This will not work now because the jump (at target) could | |
| * be forward or backward and FOR_ITER only jumps forward. We | |
| * can re-enable this if ever we implement a backward version | |
| * of FOR_ITER. | |
| */ | |
| /* | |
| i -= jump_thread(bb, inst, target, FOR_ITER); | |
| */ | |
| } | |
| break; | |
| case STORE_FAST: | |
| if (opcode == nextop && | |
| oparg == bb->b_instr[i+1].i_oparg && | |
| bb->b_instr[i].i_loc.lineno == bb->b_instr[i+1].i_loc.lineno) { | |
| bb->b_instr[i].i_opcode = POP_TOP; | |
| bb->b_instr[i].i_oparg = 0; | |
| } | |
| break; | |
| case SWAP: | |
| if (oparg == 1) { | |
| INSTR_SET_OP0(inst, NOP); | |
| } | |
| break; | |
| case LOAD_GLOBAL: | |
| if (nextop == PUSH_NULL && (oparg & 1) == 0) { | |
| INSTR_SET_OP1(inst, LOAD_GLOBAL, oparg | 1); | |
| INSTR_SET_OP0(&bb->b_instr[i + 1], NOP); | |
| } | |
| break; | |
| case COMPARE_OP: | |
| if (nextop == TO_BOOL) { | |
| INSTR_SET_OP0(inst, NOP); | |
| INSTR_SET_OP1(&bb->b_instr[i + 1], COMPARE_OP, oparg | 16); | |
| continue; | |
| } | |
| break; | |
| case CONTAINS_OP: | |
| case IS_OP: | |
| if (nextop == TO_BOOL) { | |
| INSTR_SET_OP0(inst, NOP); | |
| INSTR_SET_OP1(&bb->b_instr[i + 1], opcode, oparg); | |
| continue; | |
| } | |
| if (nextop == UNARY_NOT) { | |
| INSTR_SET_OP0(inst, NOP); | |
| int inverted = oparg ^ 1; | |
| assert(inverted == 0 || inverted == 1); | |
| INSTR_SET_OP1(&bb->b_instr[i + 1], opcode, inverted); | |
| continue; | |
| } | |
| break; | |
| case TO_BOOL: | |
| if (nextop == TO_BOOL) { | |
| INSTR_SET_OP0(inst, NOP); | |
| continue; | |
| } | |
| break; | |
| case UNARY_NOT: | |
| if (nextop == TO_BOOL) { | |
| INSTR_SET_OP0(inst, NOP); | |
| INSTR_SET_OP0(&bb->b_instr[i + 1], UNARY_NOT); | |
| continue; | |
| } | |
| if (nextop == UNARY_NOT) { | |
| INSTR_SET_OP0(inst, NOP); | |
| INSTR_SET_OP0(&bb->b_instr[i + 1], NOP); | |
| continue; | |
| } | |
| _Py_FALLTHROUGH; | |
| case UNARY_INVERT: | |
| case UNARY_NEGATIVE: | |
| RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache, consts_index)); | |
| break; | |
| case CALL_INTRINSIC_1: | |
| if (oparg == INTRINSIC_LIST_TO_TUPLE) { | |
| RETURN_IF_ERROR(fold_constant_seq_into_load_const(bb, i, consts, const_cache, consts_index)); | |
| if (inst->i_opcode == CALL_INTRINSIC_1 && nextop == GET_ITER) { | |
| INSTR_SET_OP0(inst, NOP); | |
| } | |
| } | |
| else if (oparg == INTRINSIC_UNARY_POSITIVE) { | |
| RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache, consts_index)); | |
| } | |
| break; | |
| case LIST_APPEND: | |
| case SET_ADD: | |
| if (oparg == 1 && (nextop == GET_ITER || nextop == CONTAINS_OP)) { | |
| RETURN_IF_ERROR(fold_constant_seq_into_load_const( | |
| bb, i, consts, const_cache, consts_index)); | |
| } | |
| break; | |
| case BINARY_OP: | |
| RETURN_IF_ERROR(fold_const_binop(bb, i, consts, const_cache, consts_index)); | |
| break; | |
| } | |
| } | |
| for (int i = 0; i < bb->b_iused; i++) { | |
| cfg_instr *inst = &bb->b_instr[i]; | |
| if (inst->i_opcode == SWAP) { | |
| if (swaptimize(bb, &i) < 0) { | |
| goto error; | |
| } | |
| apply_static_swaps(bb, i); | |
| } | |
| } | |
| return SUCCESS; | |
| error: | |
| return ERROR; | |
| } | |
| static int resolve_line_numbers(cfg_builder *g, int firstlineno); | |
| static int | |
| remove_redundant_nops_and_jumps(cfg_builder *g) | |
| { | |
| int removed_nops, removed_jumps; | |
| do { | |
| /* Convergence is guaranteed because the number of | |
| * redundant jumps and nops only decreases. | |
| */ | |
| removed_nops = remove_redundant_nops(g); | |
| RETURN_IF_ERROR(removed_nops); | |
| removed_jumps = remove_redundant_jumps(g); | |
| RETURN_IF_ERROR(removed_jumps); | |
| } while(removed_nops + removed_jumps > 0); | |
| return SUCCESS; | |
| } | |
| /* Perform optimizations on a control flow graph. | |
| The consts object should still be in list form to allow new constants | |
| to be appended. | |
| Code trasnformations that reduce code size initially fill the gaps with | |
| NOPs. Later those NOPs are removed. | |
| */ | |
| static int | |
| optimize_cfg(cfg_builder *g, PyObject *consts, PyObject *const_cache, | |
| _Py_hashtable_t *consts_index, int firstlineno) | |
| { | |
| assert(PyDict_CheckExact(const_cache)); | |
| RETURN_IF_ERROR(check_cfg(g)); | |
| RETURN_IF_ERROR(inline_small_or_no_lineno_blocks(g->g_entryblock)); | |
| RETURN_IF_ERROR(remove_unreachable(g->g_entryblock)); | |
| RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno)); | |
| RETURN_IF_ERROR(optimize_load_const(const_cache, g, consts, consts_index)); | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| RETURN_IF_ERROR(optimize_basic_block(const_cache, b, consts, consts_index)); | |
| } | |
| RETURN_IF_ERROR(remove_redundant_nops_and_pairs(g->g_entryblock)); | |
| RETURN_IF_ERROR(remove_unreachable(g->g_entryblock)); | |
| RETURN_IF_ERROR(remove_redundant_nops_and_jumps(g)); | |
| assert(no_redundant_jumps(g)); | |
| return SUCCESS; | |
| } | |
| static void | |
| make_super_instruction(cfg_instr *inst1, cfg_instr *inst2, int super_op) | |
| { | |
| int32_t line1 = inst1->i_loc.lineno; | |
| int32_t line2 = inst2->i_loc.lineno; | |
| /* Skip if instructions are on different lines */ | |
| if (line1 >= 0 && line2 >= 0 && line1 != line2) { | |
| return; | |
| } | |
| if (inst1->i_oparg >= 16 || inst2->i_oparg >= 16) { | |
| return; | |
| } | |
| INSTR_SET_OP1(inst1, super_op, (inst1->i_oparg << 4) | inst2->i_oparg); | |
| INSTR_SET_OP0(inst2, NOP); | |
| } | |
| static int | |
| insert_superinstructions(cfg_builder *g) | |
| { | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *inst = &b->b_instr[i]; | |
| int nextop = i+1 < b->b_iused ? b->b_instr[i+1].i_opcode : 0; | |
| switch(inst->i_opcode) { | |
| case LOAD_FAST: | |
| if (nextop == LOAD_FAST) { | |
| make_super_instruction(inst, &b->b_instr[i + 1], LOAD_FAST_LOAD_FAST); | |
| } | |
| break; | |
| case STORE_FAST: | |
| switch (nextop) { | |
| case LOAD_FAST: | |
| make_super_instruction(inst, &b->b_instr[i + 1], STORE_FAST_LOAD_FAST); | |
| break; | |
| case STORE_FAST: | |
| make_super_instruction(inst, &b->b_instr[i + 1], STORE_FAST_STORE_FAST); | |
| break; | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| int res = remove_redundant_nops(g); | |
| assert(no_redundant_nops(g)); | |
| return res; | |
| } | |
| #define NOT_LOCAL -1 | |
| #define DUMMY_INSTR -1 | |
| typedef struct { | |
| // Index of instruction that produced the reference or DUMMY_INSTR. | |
| int instr; | |
| // The local to which the reference refers or NOT_LOCAL. | |
| int local; | |
| } ref; | |
| typedef struct { | |
| ref *refs; | |
| Py_ssize_t size; | |
| Py_ssize_t capacity; | |
| } ref_stack; | |
| static int | |
| ref_stack_push(ref_stack *stack, ref r) | |
| { | |
| if (stack->size == stack->capacity) { | |
| Py_ssize_t new_cap = Py_MAX(32, stack->capacity * 2); | |
| ref *refs = PyMem_Realloc(stack->refs, sizeof(*stack->refs) * new_cap); | |
| if (refs == NULL) { | |
| PyErr_NoMemory(); | |
| return -1; | |
| } | |
| stack->refs = refs; | |
| stack->capacity = new_cap; | |
| } | |
| stack->refs[stack->size] = r; | |
| stack->size++; | |
| return 0; | |
| } | |
| static ref | |
| ref_stack_pop(ref_stack *stack) | |
| { | |
| assert(stack->size > 0); | |
| stack->size--; | |
| ref r = stack->refs[stack->size]; | |
| return r; | |
| } | |
| static void | |
| ref_stack_swap_top(ref_stack *stack, Py_ssize_t off) | |
| { | |
| Py_ssize_t idx = stack->size - off; | |
| assert(idx >= 0 && idx < stack->size); | |
| ref tmp = stack->refs[idx]; | |
| stack->refs[idx] = stack->refs[stack->size - 1]; | |
| stack->refs[stack->size - 1] = tmp; | |
| } | |
| static ref | |
| ref_stack_at(ref_stack *stack, Py_ssize_t idx) | |
| { | |
| assert(idx >= 0 && idx < stack->size); | |
| return stack->refs[idx]; | |
| } | |
| static void | |
| ref_stack_clear(ref_stack *stack) | |
| { | |
| stack->size = 0; | |
| } | |
| static void | |
| ref_stack_fini(ref_stack *stack) | |
| { | |
| if (stack->refs != NULL) { | |
| PyMem_Free(stack->refs); | |
| } | |
| stack->refs = NULL; | |
| stack->capacity = 0; | |
| stack->size = 0; | |
| } | |
| typedef enum { | |
| // The loaded reference is still on the stack when the local is killed | |
| SUPPORT_KILLED = 1, | |
| // The loaded reference is stored into a local | |
| STORED_AS_LOCAL = 2, | |
| // The loaded reference is still on the stack at the end of the basic block | |
| REF_UNCONSUMED = 4, | |
| } LoadFastInstrFlag; | |
| static void | |
| kill_local(uint8_t *instr_flags, ref_stack *refs, int local) | |
| { | |
| for (Py_ssize_t i = 0; i < refs->size; i++) { | |
| ref r = ref_stack_at(refs, i); | |
| if (r.local == local) { | |
| assert(r.instr >= 0); | |
| instr_flags[r.instr] |= SUPPORT_KILLED; | |
| } | |
| } | |
| } | |
| static void | |
| store_local(uint8_t *instr_flags, ref_stack *refs, int local, ref r) | |
| { | |
| kill_local(instr_flags, refs, local); | |
| if (r.instr != DUMMY_INSTR) { | |
| instr_flags[r.instr] |= STORED_AS_LOCAL; | |
| } | |
| } | |
| static void | |
| load_fast_push_block(basicblock ***sp, basicblock *target, | |
| Py_ssize_t start_depth) | |
| { | |
| assert(target->b_startdepth >= 0 && target->b_startdepth == start_depth); | |
| if (!target->b_visited) { | |
| target->b_visited = 1; | |
| *(*sp)++ = target; | |
| } | |
| } | |
| /* | |
| * Strength reduce LOAD_FAST{_LOAD_FAST} instructions into faster variants that | |
| * load borrowed references onto the operand stack. | |
| * | |
| * This is only safe when we can prove that the reference in the frame outlives | |
| * the borrowed reference produced by the instruction. We make this tractable | |
| * by enforcing the following lifetimes: | |
| * | |
| * 1. Borrowed references loaded onto the operand stack live until the end of | |
| * the instruction that consumes them from the stack. Any borrowed | |
| * references that would escape into the heap (e.g. into frame objects or | |
| * generators) are converted into new, strong references. | |
| * | |
| * 2. Locals live until they are either killed by an instruction | |
| * (e.g. STORE_FAST) or the frame is unwound. Any local that is overwritten | |
| * via `f_locals` is added to a tuple owned by the frame object. | |
| * | |
| * To simplify the problem of detecting which supporting references in the | |
| * frame are killed by instructions that overwrite locals, we only allow | |
| * borrowed references to be stored as a local in the frame if they were passed | |
| * as an argument. {RETURN,YIELD}_VALUE convert borrowed references into new, | |
| * strong references. | |
| * | |
| * Using the above, we can optimize any LOAD_FAST{_LOAD_FAST} instructions | |
| * that meet the following criteria: | |
| * | |
| * 1. The produced reference must be consumed from the stack before the | |
| * supporting reference in the frame is killed. | |
| * | |
| * 2. The produced reference cannot be stored as a local. | |
| * | |
| * We use abstract interpretation to identify instructions that meet these | |
| * criteria. For each basic block, we simulate the effect the bytecode has on a | |
| * stack of abstract references and note any instructions that violate the | |
| * criteria above. Once we've processed all the instructions in a block, any | |
| * non-violating LOAD_FAST{_LOAD_FAST} can be optimized. | |
| */ | |
| static int | |
| optimize_load_fast(cfg_builder *g) | |
| { | |
| int status; | |
| ref_stack refs = {0}; | |
| int max_instrs = 0; | |
| basicblock *entryblock = g->g_entryblock; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| max_instrs = Py_MAX(max_instrs, b->b_iused); | |
| } | |
| size_t instr_flags_size = max_instrs * sizeof(uint8_t); | |
| uint8_t *instr_flags = PyMem_Malloc(instr_flags_size); | |
| if (instr_flags == NULL) { | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| basicblock **blocks = make_cfg_traversal_stack(entryblock); | |
| if (blocks == NULL) { | |
| status = ERROR; | |
| goto done; | |
| } | |
| basicblock **sp = blocks; | |
| *sp = entryblock; | |
| sp++; | |
| entryblock->b_startdepth = 0; | |
| entryblock->b_visited = 1; | |
| #define PUSH_REF(instr, local) \ | |
| do { \ | |
| if (ref_stack_push(&refs, (ref){(instr), (local)}) < 0) { \ | |
| status = ERROR; \ | |
| goto done; \ | |
| } \ | |
| } while(0) | |
| while (sp != blocks) { | |
| basicblock *block = *--sp; | |
| assert(block->b_startdepth > -1); | |
| // Reset per-block state. | |
| memset(instr_flags, 0, block->b_iused * sizeof(*instr_flags)); | |
| // Reset the stack of refs. We don't track references on the stack | |
| // across basic blocks, but the bytecode will expect their | |
| // presence. Add dummy references as necessary. | |
| ref_stack_clear(&refs); | |
| for (int i = 0; i < block->b_startdepth; i++) { | |
| PUSH_REF(DUMMY_INSTR, NOT_LOCAL); | |
| } | |
| for (int i = 0; i < block->b_iused; i++) { | |
| cfg_instr *instr = &block->b_instr[i]; | |
| int opcode = instr->i_opcode; | |
| int oparg = instr->i_oparg; | |
| assert(opcode != EXTENDED_ARG); | |
| switch (opcode) { | |
| // Opcodes that load and store locals | |
| case DELETE_FAST: { | |
| kill_local(instr_flags, &refs, oparg); | |
| break; | |
| } | |
| case LOAD_FAST: { | |
| PUSH_REF(i, oparg); | |
| break; | |
| } | |
| case LOAD_FAST_AND_CLEAR: { | |
| kill_local(instr_flags, &refs, oparg); | |
| PUSH_REF(i, oparg); | |
| break; | |
| } | |
| case LOAD_FAST_LOAD_FAST: { | |
| PUSH_REF(i, oparg >> 4); | |
| PUSH_REF(i, oparg & 15); | |
| break; | |
| } | |
| case STORE_FAST: { | |
| ref r = ref_stack_pop(&refs); | |
| store_local(instr_flags, &refs, oparg, r); | |
| break; | |
| } | |
| case STORE_FAST_LOAD_FAST: { | |
| // STORE_FAST | |
| ref r = ref_stack_pop(&refs); | |
| store_local(instr_flags, &refs, oparg >> 4, r); | |
| // LOAD_FAST | |
| PUSH_REF(i, oparg & 15); | |
| break; | |
| } | |
| case STORE_FAST_STORE_FAST: { | |
| // STORE_FAST | |
| ref r = ref_stack_pop(&refs); | |
| store_local(instr_flags, &refs, oparg >> 4, r); | |
| // STORE_FAST | |
| r = ref_stack_pop(&refs); | |
| store_local(instr_flags, &refs, oparg & 15, r); | |
| break; | |
| } | |
| // Opcodes that shuffle values on the stack | |
| case COPY: { | |
| assert(oparg > 0); | |
| Py_ssize_t idx = refs.size - oparg; | |
| ref r = ref_stack_at(&refs, idx); | |
| PUSH_REF(r.instr, r.local); | |
| break; | |
| } | |
| case SWAP: { | |
| assert(oparg >= 2); | |
| ref_stack_swap_top(&refs, oparg); | |
| break; | |
| } | |
| // We treat opcodes that do not consume all of their inputs on | |
| // a case by case basis, as we have no generic way of knowing | |
| // how many inputs should be left on the stack. | |
| // Opcodes that consume no inputs | |
| case FORMAT_SIMPLE: | |
| case GET_ANEXT: | |
| case GET_ITER: | |
| case GET_LEN: | |
| case IMPORT_FROM: | |
| case MATCH_KEYS: | |
| case MATCH_MAPPING: | |
| case MATCH_SEQUENCE: | |
| case WITH_EXCEPT_START: { | |
| int num_popped = _PyOpcode_num_popped(opcode, oparg); | |
| int num_pushed = _PyOpcode_num_pushed(opcode, oparg); | |
| int net_pushed = num_pushed - num_popped; | |
| assert(net_pushed >= 0); | |
| for (int j = 0; j < net_pushed; j++) { | |
| PUSH_REF(i, NOT_LOCAL); | |
| } | |
| break; | |
| } | |
| // Opcodes that consume some inputs and push no new values | |
| case DICT_MERGE: | |
| case DICT_UPDATE: | |
| case LIST_APPEND: | |
| case LIST_EXTEND: | |
| case MAP_ADD: | |
| case RERAISE: | |
| case SET_ADD: | |
| case SET_UPDATE: { | |
| int num_popped = _PyOpcode_num_popped(opcode, oparg); | |
| int num_pushed = _PyOpcode_num_pushed(opcode, oparg); | |
| int net_popped = num_popped - num_pushed; | |
| assert(net_popped > 0); | |
| for (int i = 0; i < net_popped; i++) { | |
| ref_stack_pop(&refs); | |
| } | |
| break; | |
| } | |
| case END_SEND: { | |
| assert(_PyOpcode_num_popped(opcode, oparg) == 3); | |
| assert(_PyOpcode_num_pushed(opcode, oparg) == 1); | |
| ref tos = ref_stack_pop(&refs); | |
| ref_stack_pop(&refs); | |
| ref_stack_pop(&refs); | |
| PUSH_REF(tos.instr, tos.local); | |
| break; | |
| } | |
| case SET_FUNCTION_ATTRIBUTE: { | |
| assert(_PyOpcode_num_popped(opcode, oparg) == 2); | |
| assert(_PyOpcode_num_pushed(opcode, oparg) == 1); | |
| ref tos = ref_stack_pop(&refs); | |
| ref_stack_pop(&refs); | |
| PUSH_REF(tos.instr, tos.local); | |
| break; | |
| } | |
| // Opcodes that consume some inputs and push new values | |
| case CHECK_EXC_MATCH: { | |
| ref_stack_pop(&refs); | |
| PUSH_REF(i, NOT_LOCAL); | |
| break; | |
| } | |
| case FOR_ITER: { | |
| load_fast_push_block(&sp, instr->i_target, refs.size + 1); | |
| PUSH_REF(i, NOT_LOCAL); | |
| break; | |
| } | |
| case LOAD_ATTR: | |
| case LOAD_SUPER_ATTR: { | |
| ref self = ref_stack_pop(&refs); | |
| if (opcode == LOAD_SUPER_ATTR) { | |
| ref_stack_pop(&refs); | |
| ref_stack_pop(&refs); | |
| } | |
| PUSH_REF(i, NOT_LOCAL); | |
| if (oparg & 1) { | |
| // A method call; conservatively assume that self is pushed | |
| // back onto the stack | |
| PUSH_REF(self.instr, self.local); | |
| } | |
| break; | |
| } | |
| case LOAD_SPECIAL: | |
| case PUSH_EXC_INFO: { | |
| ref tos = ref_stack_pop(&refs); | |
| PUSH_REF(i, NOT_LOCAL); | |
| PUSH_REF(tos.instr, tos.local); | |
| break; | |
| } | |
| case SEND: { | |
| load_fast_push_block(&sp, instr->i_target, refs.size); | |
| ref_stack_pop(&refs); | |
| PUSH_REF(i, NOT_LOCAL); | |
| break; | |
| } | |
| // Opcodes that consume all of their inputs | |
| default: { | |
| int num_popped = _PyOpcode_num_popped(opcode, oparg); | |
| int num_pushed = _PyOpcode_num_pushed(opcode, oparg); | |
| if (HAS_TARGET(instr->i_opcode)) { | |
| load_fast_push_block(&sp, instr->i_target, refs.size - num_popped + num_pushed); | |
| } | |
| if (!IS_BLOCK_PUSH_OPCODE(instr->i_opcode)) { | |
| // Block push opcodes only affect the stack when jumping | |
| // to the target. | |
| for (int j = 0; j < num_popped; j++) { | |
| ref_stack_pop(&refs); | |
| } | |
| for (int j = 0; j < num_pushed; j++) { | |
| PUSH_REF(i, NOT_LOCAL); | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| // Push fallthrough block | |
| if (BB_HAS_FALLTHROUGH(block)) { | |
| assert(block->b_next != NULL); | |
| load_fast_push_block(&sp, block->b_next, refs.size); | |
| } | |
| // Mark instructions that produce values that are on the stack at the | |
| // end of the basic block | |
| for (Py_ssize_t i = 0; i < refs.size; i++) { | |
| ref r = ref_stack_at(&refs, i); | |
| if (r.instr != -1) { | |
| instr_flags[r.instr] |= REF_UNCONSUMED; | |
| } | |
| } | |
| // Optimize instructions | |
| for (int i = 0; i < block->b_iused; i++) { | |
| if (!instr_flags[i]) { | |
| cfg_instr *instr = &block->b_instr[i]; | |
| switch (instr->i_opcode) { | |
| case LOAD_FAST: | |
| instr->i_opcode = LOAD_FAST_BORROW; | |
| break; | |
| case LOAD_FAST_LOAD_FAST: | |
| instr->i_opcode = LOAD_FAST_BORROW_LOAD_FAST_BORROW; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| #undef PUSH_REF | |
| status = SUCCESS; | |
| done: | |
| ref_stack_fini(&refs); | |
| PyMem_Free(instr_flags); | |
| PyMem_Free(blocks); | |
| return status; | |
| } | |
| // helper functions for add_checks_for_loads_of_unknown_variables | |
| static inline void | |
| maybe_push(basicblock *b, uint64_t unsafe_mask, basicblock ***sp) | |
| { | |
| // Push b if the unsafe mask is giving us any new information. | |
| // To avoid overflowing the stack, only allow each block once. | |
| // Use b->b_visited=1 to mean that b is currently on the stack. | |
| uint64_t both = b->b_unsafe_locals_mask | unsafe_mask; | |
| if (b->b_unsafe_locals_mask != both) { | |
| b->b_unsafe_locals_mask = both; | |
| // More work left to do. | |
| if (!b->b_visited) { | |
| // not on the stack, so push it. | |
| *(*sp)++ = b; | |
| b->b_visited = 1; | |
| } | |
| } | |
| } | |
| static void | |
| scan_block_for_locals(basicblock *b, basicblock ***sp) | |
| { | |
| // bit i is set if local i is potentially uninitialized | |
| uint64_t unsafe_mask = b->b_unsafe_locals_mask; | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| assert(instr->i_opcode != EXTENDED_ARG); | |
| if (instr->i_except != NULL) { | |
| maybe_push(instr->i_except, unsafe_mask, sp); | |
| } | |
| if (instr->i_oparg >= 64) { | |
| continue; | |
| } | |
| assert(instr->i_oparg >= 0); | |
| uint64_t bit = (uint64_t)1 << instr->i_oparg; | |
| switch (instr->i_opcode) { | |
| case DELETE_FAST: | |
| case LOAD_FAST_AND_CLEAR: | |
| case STORE_FAST_MAYBE_NULL: | |
| unsafe_mask |= bit; | |
| break; | |
| case STORE_FAST: | |
| unsafe_mask &= ~bit; | |
| break; | |
| case LOAD_FAST_CHECK: | |
| // If this doesn't raise, then the local is defined. | |
| unsafe_mask &= ~bit; | |
| break; | |
| case LOAD_FAST: | |
| if (unsafe_mask & bit) { | |
| instr->i_opcode = LOAD_FAST_CHECK; | |
| } | |
| unsafe_mask &= ~bit; | |
| break; | |
| } | |
| } | |
| if (b->b_next && BB_HAS_FALLTHROUGH(b)) { | |
| maybe_push(b->b_next, unsafe_mask, sp); | |
| } | |
| cfg_instr *last = basicblock_last_instr(b); | |
| if (last && is_jump(last)) { | |
| assert(last->i_target != NULL); | |
| maybe_push(last->i_target, unsafe_mask, sp); | |
| } | |
| } | |
| static int | |
| fast_scan_many_locals(basicblock *entryblock, int nlocals) | |
| { | |
| assert(nlocals > 64); | |
| Py_ssize_t *states = PyMem_Calloc(nlocals - 64, sizeof(Py_ssize_t)); | |
| if (states == NULL) { | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| Py_ssize_t blocknum = 0; | |
| // state[i - 64] == blocknum if local i is guaranteed to | |
| // be initialized, i.e., if it has had a previous LOAD_FAST or | |
| // STORE_FAST within that basicblock (not followed by | |
| // DELETE_FAST/LOAD_FAST_AND_CLEAR/STORE_FAST_MAYBE_NULL). | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| blocknum++; | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| assert(instr->i_opcode != EXTENDED_ARG); | |
| int arg = instr->i_oparg; | |
| if (arg < 64) { | |
| continue; | |
| } | |
| assert(arg >= 0); | |
| switch (instr->i_opcode) { | |
| case DELETE_FAST: | |
| case LOAD_FAST_AND_CLEAR: | |
| case STORE_FAST_MAYBE_NULL: | |
| states[arg - 64] = blocknum - 1; | |
| break; | |
| case STORE_FAST: | |
| states[arg - 64] = blocknum; | |
| break; | |
| case LOAD_FAST: | |
| if (states[arg - 64] != blocknum) { | |
| instr->i_opcode = LOAD_FAST_CHECK; | |
| } | |
| states[arg - 64] = blocknum; | |
| break; | |
| Py_UNREACHABLE(); | |
| } | |
| } | |
| } | |
| PyMem_Free(states); | |
| return SUCCESS; | |
| } | |
| static int | |
| remove_unused_consts(basicblock *entryblock, PyObject *consts) | |
| { | |
| assert(PyList_CheckExact(consts)); | |
| Py_ssize_t nconsts = PyList_GET_SIZE(consts); | |
| if (nconsts == 0) { | |
| return SUCCESS; /* nothing to do */ | |
| } | |
| Py_ssize_t *index_map = NULL; | |
| Py_ssize_t *reverse_index_map = NULL; | |
| int err = ERROR; | |
| index_map = PyMem_Malloc(nconsts * sizeof(Py_ssize_t)); | |
| if (index_map == NULL) { | |
| PyErr_NoMemory(); | |
| goto end; | |
| } | |
| for (Py_ssize_t i = 1; i < nconsts; i++) { | |
| index_map[i] = -1; | |
| } | |
| // The first constant may be docstring; keep it always. | |
| index_map[0] = 0; | |
| /* mark used consts */ | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| int opcode = b->b_instr[i].i_opcode; | |
| if (OPCODE_HAS_CONST(opcode)) { | |
| int index = b->b_instr[i].i_oparg; | |
| index_map[index] = index; | |
| } | |
| } | |
| } | |
| /* now index_map[i] == i if consts[i] is used, -1 otherwise */ | |
| /* condense consts */ | |
| Py_ssize_t n_used_consts = 0; | |
| for (Py_ssize_t i = 0; i < nconsts; i++) { | |
| if (index_map[i] != -1) { | |
| assert(index_map[i] == i); | |
| index_map[n_used_consts++] = index_map[i]; | |
| } | |
| } | |
| if (n_used_consts == nconsts) { | |
| /* nothing to do */ | |
| err = SUCCESS; | |
| goto end; | |
| } | |
| /* move all used consts to the beginning of the consts list */ | |
| assert(n_used_consts < nconsts); | |
| for (Py_ssize_t i = 0; i < n_used_consts; i++) { | |
| Py_ssize_t old_index = index_map[i]; | |
| assert(i <= old_index && old_index < nconsts); | |
| if (i != old_index) { | |
| PyObject *value = PyList_GET_ITEM(consts, index_map[i]); | |
| assert(value != NULL); | |
| PyList_SetItem(consts, i, Py_NewRef(value)); | |
| } | |
| } | |
| /* truncate the consts list at its new size */ | |
| if (PyList_SetSlice(consts, n_used_consts, nconsts, NULL) < 0) { | |
| goto end; | |
| } | |
| /* adjust const indices in the bytecode */ | |
| reverse_index_map = PyMem_Malloc(nconsts * sizeof(Py_ssize_t)); | |
| if (reverse_index_map == NULL) { | |
| PyErr_NoMemory(); | |
| goto end; | |
| } | |
| for (Py_ssize_t i = 0; i < nconsts; i++) { | |
| reverse_index_map[i] = -1; | |
| } | |
| for (Py_ssize_t i = 0; i < n_used_consts; i++) { | |
| assert(index_map[i] != -1); | |
| assert(reverse_index_map[index_map[i]] == -1); | |
| reverse_index_map[index_map[i]] = i; | |
| } | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| int opcode = b->b_instr[i].i_opcode; | |
| if (OPCODE_HAS_CONST(opcode)) { | |
| int index = b->b_instr[i].i_oparg; | |
| assert(reverse_index_map[index] >= 0); | |
| assert(reverse_index_map[index] < n_used_consts); | |
| b->b_instr[i].i_oparg = (int)reverse_index_map[index]; | |
| } | |
| } | |
| } | |
| err = SUCCESS; | |
| end: | |
| PyMem_Free(index_map); | |
| PyMem_Free(reverse_index_map); | |
| return err; | |
| } | |
| static int | |
| add_checks_for_loads_of_uninitialized_variables(basicblock *entryblock, | |
| int nlocals, | |
| int nparams) | |
| { | |
| if (nlocals == 0) { | |
| return SUCCESS; | |
| } | |
| if (nlocals > 64) { | |
| // To avoid O(nlocals**2) compilation, locals beyond the first | |
| // 64 are only analyzed one basicblock at a time: initialization | |
| // info is not passed between basicblocks. | |
| if (fast_scan_many_locals(entryblock, nlocals) < 0) { | |
| return ERROR; | |
| } | |
| nlocals = 64; | |
| } | |
| basicblock **stack = make_cfg_traversal_stack(entryblock); | |
| if (stack == NULL) { | |
| return ERROR; | |
| } | |
| basicblock **sp = stack; | |
| // First origin of being uninitialized: | |
| // The non-parameter locals in the entry block. | |
| uint64_t start_mask = 0; | |
| for (int i = nparams; i < nlocals; i++) { | |
| start_mask |= (uint64_t)1 << i; | |
| } | |
| maybe_push(entryblock, start_mask, &sp); | |
| // Second origin of being uninitialized: | |
| // There could be DELETE_FAST somewhere, so | |
| // be sure to scan each basicblock at least once. | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| scan_block_for_locals(b, &sp); | |
| } | |
| // Now propagate the uncertainty from the origins we found: Use | |
| // LOAD_FAST_CHECK for any LOAD_FAST where the local could be undefined. | |
| while (sp > stack) { | |
| basicblock *b = *--sp; | |
| // mark as no longer on stack | |
| b->b_visited = 0; | |
| scan_block_for_locals(b, &sp); | |
| } | |
| PyMem_Free(stack); | |
| return SUCCESS; | |
| } | |
| static int | |
| mark_warm(basicblock *entryblock) { | |
| basicblock **stack = make_cfg_traversal_stack(entryblock); | |
| if (stack == NULL) { | |
| return ERROR; | |
| } | |
| basicblock **sp = stack; | |
| *sp++ = entryblock; | |
| entryblock->b_visited = 1; | |
| while (sp > stack) { | |
| basicblock *b = *(--sp); | |
| assert(!b->b_except_handler); | |
| b->b_warm = 1; | |
| basicblock *next = b->b_next; | |
| if (next && BB_HAS_FALLTHROUGH(b) && !next->b_visited) { | |
| *sp++ = next; | |
| next->b_visited = 1; | |
| } | |
| for (int i=0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| if (is_jump(instr) && !instr->i_target->b_visited) { | |
| *sp++ = instr->i_target; | |
| instr->i_target->b_visited = 1; | |
| } | |
| } | |
| } | |
| PyMem_Free(stack); | |
| return SUCCESS; | |
| } | |
| static int | |
| mark_cold(basicblock *entryblock) { | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| assert(!b->b_cold && !b->b_warm); | |
| } | |
| if (mark_warm(entryblock) < 0) { | |
| return ERROR; | |
| } | |
| basicblock **stack = make_cfg_traversal_stack(entryblock); | |
| if (stack == NULL) { | |
| return ERROR; | |
| } | |
| basicblock **sp = stack; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| if (b->b_except_handler) { | |
| assert(!b->b_warm); | |
| *sp++ = b; | |
| b->b_visited = 1; | |
| } | |
| } | |
| while (sp > stack) { | |
| basicblock *b = *(--sp); | |
| b->b_cold = 1; | |
| basicblock *next = b->b_next; | |
| if (next && BB_HAS_FALLTHROUGH(b)) { | |
| if (!next->b_warm && !next->b_visited) { | |
| *sp++ = next; | |
| next->b_visited = 1; | |
| } | |
| } | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| if (is_jump(instr)) { | |
| assert(i == b->b_iused - 1); | |
| basicblock *target = b->b_instr[i].i_target; | |
| if (!target->b_warm && !target->b_visited) { | |
| *sp++ = target; | |
| target->b_visited = 1; | |
| } | |
| } | |
| } | |
| } | |
| PyMem_Free(stack); | |
| return SUCCESS; | |
| } | |
| static int | |
| push_cold_blocks_to_end(cfg_builder *g) { | |
| basicblock *entryblock = g->g_entryblock; | |
| if (entryblock->b_next == NULL) { | |
| /* single basicblock, no need to reorder */ | |
| return SUCCESS; | |
| } | |
| RETURN_IF_ERROR(mark_cold(entryblock)); | |
| int next_lbl = get_max_label(g->g_entryblock) + 1; | |
| /* If we have a cold block with fallthrough to a warm block, add */ | |
| /* an explicit jump instead of fallthrough */ | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| if (b->b_cold && BB_HAS_FALLTHROUGH(b) && b->b_next && b->b_next->b_warm) { | |
| basicblock *explicit_jump = cfg_builder_new_block(g); | |
| if (explicit_jump == NULL) { | |
| return ERROR; | |
| } | |
| if (!IS_LABEL(b->b_next->b_label)) { | |
| b->b_next->b_label.id = next_lbl++; | |
| } | |
| basicblock_addop(explicit_jump, JUMP_NO_INTERRUPT, b->b_next->b_label.id, | |
| NO_LOCATION, NULL); | |
| explicit_jump->b_cold = 1; | |
| explicit_jump->b_next = b->b_next; | |
| explicit_jump->b_predecessors = 1; | |
| b->b_next = explicit_jump; | |
| /* set target */ | |
| cfg_instr *last = basicblock_last_instr(explicit_jump); | |
| last->i_target = explicit_jump->b_next; | |
| } | |
| } | |
| assert(!entryblock->b_cold); /* First block can't be cold */ | |
| basicblock *cold_blocks = NULL; | |
| basicblock *cold_blocks_tail = NULL; | |
| basicblock *b = entryblock; | |
| while(b->b_next) { | |
| assert(!b->b_cold); | |
| while (b->b_next && !b->b_next->b_cold) { | |
| b = b->b_next; | |
| } | |
| if (b->b_next == NULL) { | |
| /* no more cold blocks */ | |
| break; | |
| } | |
| /* b->b_next is the beginning of a cold streak */ | |
| assert(!b->b_cold && b->b_next->b_cold); | |
| basicblock *b_end = b->b_next; | |
| while (b_end->b_next && b_end->b_next->b_cold) { | |
| b_end = b_end->b_next; | |
| } | |
| /* b_end is the end of the cold streak */ | |
| assert(b_end && b_end->b_cold); | |
| assert(b_end->b_next == NULL || !b_end->b_next->b_cold); | |
| if (cold_blocks == NULL) { | |
| cold_blocks = b->b_next; | |
| } | |
| else { | |
| cold_blocks_tail->b_next = b->b_next; | |
| } | |
| cold_blocks_tail = b_end; | |
| b->b_next = b_end->b_next; | |
| b_end->b_next = NULL; | |
| } | |
| assert(b != NULL && b->b_next == NULL); | |
| b->b_next = cold_blocks; | |
| if (cold_blocks != NULL) { | |
| RETURN_IF_ERROR(remove_redundant_nops_and_jumps(g)); | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| convert_pseudo_conditional_jumps(cfg_builder *g) | |
| { | |
| basicblock *entryblock = g->g_entryblock; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| if (instr->i_opcode == JUMP_IF_FALSE || instr->i_opcode == JUMP_IF_TRUE) { | |
| assert(i == b->b_iused - 1); | |
| instr->i_opcode = instr->i_opcode == JUMP_IF_FALSE ? | |
| POP_JUMP_IF_FALSE : POP_JUMP_IF_TRUE; | |
| location loc = instr->i_loc; | |
| basicblock *except = instr->i_except; | |
| cfg_instr copy = { | |
| .i_opcode = COPY, | |
| .i_oparg = 1, | |
| .i_loc = loc, | |
| .i_target = NULL, | |
| .i_except = except, | |
| }; | |
| RETURN_IF_ERROR(basicblock_insert_instruction(b, i++, ©)); | |
| cfg_instr to_bool = { | |
| .i_opcode = TO_BOOL, | |
| .i_oparg = 0, | |
| .i_loc = loc, | |
| .i_target = NULL, | |
| .i_except = except, | |
| }; | |
| RETURN_IF_ERROR(basicblock_insert_instruction(b, i++, &to_bool)); | |
| } | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| convert_pseudo_ops(cfg_builder *g) | |
| { | |
| basicblock *entryblock = g->g_entryblock; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| if (is_block_push(instr)) { | |
| INSTR_SET_OP0(instr, NOP); | |
| } | |
| else if (instr->i_opcode == LOAD_CLOSURE) { | |
| assert(is_pseudo_target(LOAD_CLOSURE, LOAD_FAST)); | |
| instr->i_opcode = LOAD_FAST; | |
| } | |
| else if (instr->i_opcode == STORE_FAST_MAYBE_NULL) { | |
| assert(is_pseudo_target(STORE_FAST_MAYBE_NULL, STORE_FAST)); | |
| instr->i_opcode = STORE_FAST; | |
| } | |
| } | |
| } | |
| return remove_redundant_nops_and_jumps(g); | |
| } | |
| static inline bool | |
| is_exit_or_eval_check_without_lineno(basicblock *b) { | |
| if (basicblock_exits_scope(b) || basicblock_has_eval_break(b)) { | |
| return basicblock_has_no_lineno(b); | |
| } | |
| else { | |
| return false; | |
| } | |
| } | |
| /* PEP 626 mandates that the f_lineno of a frame is correct | |
| * after a frame terminates. It would be prohibitively expensive | |
| * to continuously update the f_lineno field at runtime, | |
| * so we make sure that all exiting instruction (raises and returns) | |
| * have a valid line number, allowing us to compute f_lineno lazily. | |
| * We can do this by duplicating the exit blocks without line number | |
| * so that none have more than one predecessor. We can then safely | |
| * copy the line number from the sole predecessor block. | |
| */ | |
| static int | |
| duplicate_exits_without_lineno(cfg_builder *g) | |
| { | |
| int next_lbl = get_max_label(g->g_entryblock) + 1; | |
| /* Copy all exit blocks without line number that are targets of a jump. | |
| */ | |
| basicblock *entryblock = g->g_entryblock; | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| if (last == NULL) { | |
| continue; | |
| } | |
| if (is_jump(last)) { | |
| basicblock *target = next_nonempty_block(last->i_target); | |
| if (is_exit_or_eval_check_without_lineno(target) && target->b_predecessors > 1) { | |
| basicblock *new_target = copy_basicblock(g, target); | |
| if (new_target == NULL) { | |
| return ERROR; | |
| } | |
| new_target->b_instr[0].i_loc = last->i_loc; | |
| last->i_target = new_target; | |
| target->b_predecessors--; | |
| new_target->b_predecessors = 1; | |
| new_target->b_next = target->b_next; | |
| new_target->b_label.id = next_lbl++; | |
| target->b_next = new_target; | |
| } | |
| } | |
| } | |
| /* Any remaining reachable exit blocks without line number can only be reached by | |
| * fall through, and thus can only have a single predecessor */ | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| if (BB_HAS_FALLTHROUGH(b) && b->b_next && b->b_iused > 0) { | |
| if (is_exit_or_eval_check_without_lineno(b->b_next)) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| assert(last != NULL); | |
| b->b_next->b_instr[0].i_loc = last->i_loc; | |
| } | |
| } | |
| } | |
| return SUCCESS; | |
| } | |
| /* If an instruction has no line number, but it's predecessor in the BB does, | |
| * then copy the line number. If a successor block has no line number, and only | |
| * one predecessor, then inherit the line number. | |
| * This ensures that all exit blocks (with one predecessor) receive a line number. | |
| * Also reduces the size of the line number table, | |
| * but has no impact on the generated line number events. | |
| */ | |
| static inline void | |
| maybe_propagate_location(basicblock *b, int i, location loc) | |
| { | |
| assert(b->b_iused > i); | |
| if (b->b_instr[i].i_loc.lineno == NO_LOCATION.lineno) { | |
| b->b_instr[i].i_loc = loc; | |
| } | |
| } | |
| static void | |
| propagate_line_numbers(basicblock *entryblock) | |
| { | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| cfg_instr *last = basicblock_last_instr(b); | |
| if (last == NULL) { | |
| continue; | |
| } | |
| location prev_location = NO_LOCATION; | |
| for (int i = 0; i < b->b_iused; i++) { | |
| maybe_propagate_location(b, i, prev_location); | |
| prev_location = b->b_instr[i].i_loc; | |
| } | |
| if (BB_HAS_FALLTHROUGH(b) && b->b_next->b_predecessors == 1) { | |
| if (b->b_next->b_iused > 0) { | |
| maybe_propagate_location(b->b_next, 0, prev_location); | |
| } | |
| } | |
| if (is_jump(last)) { | |
| basicblock *target = last->i_target; | |
| while (target->b_iused == 0 && target->b_predecessors == 1) { | |
| target = target->b_next; | |
| } | |
| if (target->b_predecessors == 1) { | |
| maybe_propagate_location(target, 0, prev_location); | |
| } | |
| } | |
| } | |
| } | |
| static int | |
| resolve_line_numbers(cfg_builder *g, int firstlineno) | |
| { | |
| RETURN_IF_ERROR(duplicate_exits_without_lineno(g)); | |
| propagate_line_numbers(g->g_entryblock); | |
| return SUCCESS; | |
| } | |
| int | |
| _PyCfg_OptimizeCodeUnit(cfg_builder *g, PyObject *consts, PyObject *const_cache, | |
| int nlocals, int nparams, int firstlineno) | |
| { | |
| assert(cfg_builder_check(g)); | |
| assert(g->g_entryblock->b_iused > 0); | |
| /** Preprocessing **/ | |
| /* Map labels to targets and mark exception handlers */ | |
| RETURN_IF_ERROR(translate_jump_labels_to_targets(g->g_entryblock)); | |
| RETURN_IF_ERROR(mark_except_handlers(g->g_entryblock)); | |
| RETURN_IF_ERROR(label_exception_targets(g->g_entryblock)); | |
| /** Optimization **/ | |
| _Py_hashtable_t *consts_index = _Py_hashtable_new( | |
| _Py_hashtable_hash_ptr, _Py_hashtable_compare_direct); | |
| if (consts_index == NULL) { | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| for (Py_ssize_t i = 0; i < PyList_GET_SIZE(consts); i++) { | |
| PyObject *item = PyList_GET_ITEM(consts, i); | |
| if (_Py_hashtable_get_entry(consts_index, (void *)item) != NULL) { | |
| continue; | |
| } | |
| if (_Py_hashtable_set(consts_index, (void *)item, | |
| (void *)(uintptr_t)i) < 0) { | |
| _Py_hashtable_destroy(consts_index); | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| } | |
| int ret = optimize_cfg(g, consts, const_cache, consts_index, firstlineno); | |
| _Py_hashtable_destroy(consts_index); | |
| RETURN_IF_ERROR(ret); | |
| RETURN_IF_ERROR(remove_unused_consts(g->g_entryblock, consts)); | |
| RETURN_IF_ERROR( | |
| add_checks_for_loads_of_uninitialized_variables( | |
| g->g_entryblock, nlocals, nparams)); | |
| RETURN_IF_ERROR(insert_superinstructions(g)); | |
| RETURN_IF_ERROR(push_cold_blocks_to_end(g)); | |
| RETURN_IF_ERROR(resolve_line_numbers(g, firstlineno)); | |
| // temporarily remove assert. See https://github.com/python/cpython/issues/125845 | |
| // assert(all_exits_have_lineno(g->g_entryblock)); | |
| return SUCCESS; | |
| } | |
| static int * | |
| build_cellfixedoffsets(_PyCompile_CodeUnitMetadata *umd) | |
| { | |
| int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames); | |
| int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars); | |
| int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars); | |
| int noffsets = ncellvars + nfreevars; | |
| int *fixed = PyMem_New(int, noffsets); | |
| if (fixed == NULL) { | |
| PyErr_NoMemory(); | |
| return NULL; | |
| } | |
| for (int i = 0; i < noffsets; i++) { | |
| fixed[i] = nlocals + i; | |
| } | |
| PyObject *varname, *cellindex; | |
| Py_ssize_t pos = 0; | |
| while (PyDict_Next(umd->u_cellvars, &pos, &varname, &cellindex)) { | |
| PyObject *varindex; | |
| if (PyDict_GetItemRef(umd->u_varnames, varname, &varindex) < 0) { | |
| goto error; | |
| } | |
| if (varindex == NULL) { | |
| continue; | |
| } | |
| int argoffset = PyLong_AsInt(varindex); | |
| Py_DECREF(varindex); | |
| if (argoffset == -1 && PyErr_Occurred()) { | |
| goto error; | |
| } | |
| int oldindex = PyLong_AsInt(cellindex); | |
| if (oldindex == -1 && PyErr_Occurred()) { | |
| goto error; | |
| } | |
| fixed[oldindex] = argoffset; | |
| } | |
| return fixed; | |
| error: | |
| PyMem_Free(fixed); | |
| return NULL; | |
| } | |
| #define IS_GENERATOR(CF) \ | |
| ((CF) & (CO_GENERATOR | CO_COROUTINE | CO_ASYNC_GENERATOR)) | |
| static int | |
| insert_prefix_instructions(_PyCompile_CodeUnitMetadata *umd, basicblock *entryblock, | |
| int *fixed, int nfreevars) | |
| { | |
| assert(umd->u_firstlineno > 0); | |
| /* Set up cells for any variable that escapes, to be put in a closure. */ | |
| const int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars); | |
| if (ncellvars) { | |
| // umd->u_cellvars has the cells out of order so we sort them | |
| // before adding the MAKE_CELL instructions. Note that we | |
| // adjust for arg cells, which come first. | |
| const int nvars = ncellvars + (int)PyDict_GET_SIZE(umd->u_varnames); | |
| int *sorted = PyMem_RawCalloc(nvars, sizeof(int)); | |
| if (sorted == NULL) { | |
| PyErr_NoMemory(); | |
| return ERROR; | |
| } | |
| for (int i = 0; i < ncellvars; i++) { | |
| sorted[fixed[i]] = i + 1; | |
| } | |
| for (int i = 0, ncellsused = 0; ncellsused < ncellvars; i++) { | |
| int oldindex = sorted[i] - 1; | |
| if (oldindex == -1) { | |
| continue; | |
| } | |
| cfg_instr make_cell = { | |
| .i_opcode = MAKE_CELL, | |
| // This will get fixed in offset_derefs(). | |
| .i_oparg = oldindex, | |
| .i_loc = NO_LOCATION, | |
| .i_target = NULL, | |
| .i_except = NULL, | |
| }; | |
| if (basicblock_insert_instruction(entryblock, ncellsused, &make_cell) < 0) { | |
| PyMem_RawFree(sorted); | |
| return ERROR; | |
| } | |
| ncellsused += 1; | |
| } | |
| PyMem_RawFree(sorted); | |
| } | |
| if (nfreevars) { | |
| cfg_instr copy_frees = { | |
| .i_opcode = COPY_FREE_VARS, | |
| .i_oparg = nfreevars, | |
| .i_loc = NO_LOCATION, | |
| .i_target = NULL, | |
| .i_except = NULL, | |
| }; | |
| RETURN_IF_ERROR(basicblock_insert_instruction(entryblock, 0, ©_frees)); | |
| } | |
| return SUCCESS; | |
| } | |
| static int | |
| fix_cell_offsets(_PyCompile_CodeUnitMetadata *umd, basicblock *entryblock, int *fixedmap) | |
| { | |
| int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames); | |
| int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars); | |
| int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars); | |
| int noffsets = ncellvars + nfreevars; | |
| // First deal with duplicates (arg cells). | |
| int numdropped = 0; | |
| for (int i = 0; i < noffsets ; i++) { | |
| if (fixedmap[i] == i + nlocals) { | |
| fixedmap[i] -= numdropped; | |
| } | |
| else { | |
| // It was a duplicate (cell/arg). | |
| numdropped += 1; | |
| } | |
| } | |
| // Then update offsets, either relative to locals or by cell2arg. | |
| for (basicblock *b = entryblock; b != NULL; b = b->b_next) { | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *inst = &b->b_instr[i]; | |
| // This is called before extended args are generated. | |
| assert(inst->i_opcode != EXTENDED_ARG); | |
| int oldoffset = inst->i_oparg; | |
| switch(inst->i_opcode) { | |
| case MAKE_CELL: | |
| case LOAD_CLOSURE: | |
| case LOAD_DEREF: | |
| case STORE_DEREF: | |
| case DELETE_DEREF: | |
| case LOAD_FROM_DICT_OR_DEREF: | |
| assert(oldoffset >= 0); | |
| assert(oldoffset < noffsets); | |
| assert(fixedmap[oldoffset] >= 0); | |
| inst->i_oparg = fixedmap[oldoffset]; | |
| } | |
| } | |
| } | |
| return numdropped; | |
| } | |
| static int | |
| prepare_localsplus(_PyCompile_CodeUnitMetadata *umd, cfg_builder *g) | |
| { | |
| assert(PyDict_GET_SIZE(umd->u_varnames) < INT_MAX); | |
| assert(PyDict_GET_SIZE(umd->u_cellvars) < INT_MAX); | |
| assert(PyDict_GET_SIZE(umd->u_freevars) < INT_MAX); | |
| int nlocals = (int)PyDict_GET_SIZE(umd->u_varnames); | |
| int ncellvars = (int)PyDict_GET_SIZE(umd->u_cellvars); | |
| int nfreevars = (int)PyDict_GET_SIZE(umd->u_freevars); | |
| assert(INT_MAX - nlocals - ncellvars > 0); | |
| assert(INT_MAX - nlocals - ncellvars - nfreevars > 0); | |
| int nlocalsplus = nlocals + ncellvars + nfreevars; | |
| int* cellfixedoffsets = build_cellfixedoffsets(umd); | |
| if (cellfixedoffsets == NULL) { | |
| return ERROR; | |
| } | |
| // This must be called before fix_cell_offsets(). | |
| if (insert_prefix_instructions(umd, g->g_entryblock, cellfixedoffsets, nfreevars)) { | |
| PyMem_Free(cellfixedoffsets); | |
| return ERROR; | |
| } | |
| int numdropped = fix_cell_offsets(umd, g->g_entryblock, cellfixedoffsets); | |
| PyMem_Free(cellfixedoffsets); // At this point we're done with it. | |
| cellfixedoffsets = NULL; | |
| if (numdropped < 0) { | |
| return ERROR; | |
| } | |
| nlocalsplus -= numdropped; | |
| return nlocalsplus; | |
| } | |
| cfg_builder * | |
| _PyCfg_FromInstructionSequence(_PyInstructionSequence *seq) | |
| { | |
| if (_PyInstructionSequence_ApplyLabelMap(seq) < 0) { | |
| return NULL; | |
| } | |
| cfg_builder *g = _PyCfgBuilder_New(); | |
| if (g == NULL) { | |
| return NULL; | |
| } | |
| for (int i = 0; i < seq->s_used; i++) { | |
| seq->s_instrs[i].i_target = 0; | |
| } | |
| for (int i = 0; i < seq->s_used; i++) { | |
| _PyInstruction *instr = &seq->s_instrs[i]; | |
| if (HAS_TARGET(instr->i_opcode)) { | |
| assert(instr->i_oparg >= 0 && instr->i_oparg < seq->s_used); | |
| seq->s_instrs[instr->i_oparg].i_target = 1; | |
| } | |
| } | |
| int offset = 0; | |
| for (int i = 0; i < seq->s_used; i++) { | |
| _PyInstruction *instr = &seq->s_instrs[i]; | |
| if (instr->i_opcode == ANNOTATIONS_PLACEHOLDER) { | |
| if (seq->s_annotations_code != NULL) { | |
| assert(seq->s_annotations_code->s_labelmap_size == 0 | |
| && seq->s_annotations_code->s_nested == NULL); | |
| for (int j = 0; j < seq->s_annotations_code->s_used; j++) { | |
| _PyInstruction *ann_instr = &seq->s_annotations_code->s_instrs[j]; | |
| assert(!HAS_TARGET(ann_instr->i_opcode)); | |
| if (_PyCfgBuilder_Addop(g, ann_instr->i_opcode, ann_instr->i_oparg, ann_instr->i_loc) < 0) { | |
| goto error; | |
| } | |
| } | |
| offset += seq->s_annotations_code->s_used - 1; | |
| } | |
| else { | |
| offset -= 1; | |
| } | |
| continue; | |
| } | |
| if (instr->i_target) { | |
| jump_target_label lbl_ = {i + offset}; | |
| if (_PyCfgBuilder_UseLabel(g, lbl_) < 0) { | |
| goto error; | |
| } | |
| } | |
| int opcode = instr->i_opcode; | |
| int oparg = instr->i_oparg; | |
| if (HAS_TARGET(opcode)) { | |
| oparg += offset; | |
| } | |
| if (_PyCfgBuilder_Addop(g, opcode, oparg, instr->i_loc) < 0) { | |
| goto error; | |
| } | |
| } | |
| if (_PyCfgBuilder_CheckSize(g) < 0) { | |
| goto error; | |
| } | |
| return g; | |
| error: | |
| _PyCfgBuilder_Free(g); | |
| return NULL; | |
| } | |
| int | |
| _PyCfg_ToInstructionSequence(cfg_builder *g, _PyInstructionSequence *seq) | |
| { | |
| int lbl = 0; | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| b->b_label = (jump_target_label){lbl}; | |
| lbl += 1; | |
| } | |
| for (basicblock *b = g->g_entryblock; b != NULL; b = b->b_next) { | |
| RETURN_IF_ERROR(_PyInstructionSequence_UseLabel(seq, b->b_label.id)); | |
| for (int i = 0; i < b->b_iused; i++) { | |
| cfg_instr *instr = &b->b_instr[i]; | |
| if (HAS_TARGET(instr->i_opcode)) { | |
| /* Set oparg to the label id (it will later be mapped to an offset) */ | |
| instr->i_oparg = instr->i_target->b_label.id; | |
| } | |
| RETURN_IF_ERROR( | |
| _PyInstructionSequence_Addop( | |
| seq, instr->i_opcode, instr->i_oparg, instr->i_loc)); | |
| _PyExceptHandlerInfo *hi = &seq->s_instrs[seq->s_used-1].i_except_handler_info; | |
| if (instr->i_except != NULL) { | |
| hi->h_label = instr->i_except->b_label.id; | |
| hi->h_startdepth = instr->i_except->b_startdepth; | |
| hi->h_preserve_lasti = instr->i_except->b_preserve_lasti; | |
| } | |
| else { | |
| hi->h_label = -1; | |
| } | |
| } | |
| } | |
| if (_PyInstructionSequence_ApplyLabelMap(seq) < 0) { | |
| return ERROR; | |
| } | |
| return SUCCESS; | |
| } | |
| int | |
| _PyCfg_OptimizedCfgToInstructionSequence(cfg_builder *g, | |
| _PyCompile_CodeUnitMetadata *umd, | |
| int *stackdepth, int *nlocalsplus, | |
| _PyInstructionSequence *seq) | |
| { | |
| RETURN_IF_ERROR(convert_pseudo_conditional_jumps(g)); | |
| *stackdepth = calculate_stackdepth(g); | |
| if (*stackdepth < 0) { | |
| return ERROR; | |
| } | |
| *nlocalsplus = prepare_localsplus(umd, g); | |
| if (*nlocalsplus < 0) { | |
| return ERROR; | |
| } | |
| RETURN_IF_ERROR(convert_pseudo_ops(g)); | |
| /* Order of basic blocks must have been determined by now */ | |
| RETURN_IF_ERROR(normalize_jumps(g)); | |
| assert(no_redundant_jumps(g)); | |
| /* Can't modify the bytecode after inserting instructions that produce | |
| * borrowed references. | |
| */ | |
| RETURN_IF_ERROR(optimize_load_fast(g)); | |
| /* Can't modify the bytecode after computing jump offsets. */ | |
| if (_PyCfg_ToInstructionSequence(g, seq) < 0) { | |
| return ERROR; | |
| } | |
| return SUCCESS; | |
| } | |
| /* This is used by _PyCompile_Assemble to fill in the jump and exception | |
| * targets in a synthetic CFG (which is not the output of the builtin compiler). | |
| */ | |
| int | |
| _PyCfg_JumpLabelsToTargets(cfg_builder *g) | |
| { | |
| RETURN_IF_ERROR(translate_jump_labels_to_targets(g->g_entryblock)); | |
| RETURN_IF_ERROR(label_exception_targets(g->g_entryblock)); | |
| return SUCCESS; | |
| } | |
| /* Exported API functions */ | |
| int | |
| PyCompile_OpcodeStackEffectWithJump(int opcode, int oparg, int jump) | |
| { | |
| stack_effects effs; | |
| if (get_stack_effects(opcode, oparg, jump, &effs) < 0) { | |
| return PY_INVALID_STACK_EFFECT; | |
| } | |
| return effs.net; | |
| } | |
| int | |
| PyCompile_OpcodeStackEffect(int opcode, int oparg) | |
| { | |
| stack_effects effs; | |
| if (get_stack_effects(opcode, oparg, -1, &effs) < 0) { | |
| return PY_INVALID_STACK_EFFECT; | |
| } | |
| return effs.net; | |
| } | |
| /* Access to compiler optimizations for unit tests. | |
| * _PyCompile_OptimizeCfg takes an instruction list, constructs | |
| * a CFG, optimizes it and converts back to an instruction list. | |
| */ | |
| static PyObject * | |
| cfg_to_instruction_sequence(cfg_builder *g) | |
| { | |
| _PyInstructionSequence *seq = (_PyInstructionSequence *)_PyInstructionSequence_New(); | |
| if (seq == NULL) { | |
| return NULL; | |
| } | |
| if (_PyCfg_ToInstructionSequence(g, seq) < 0) { | |
| PyInstructionSequence_Fini(seq); | |
| return NULL; | |
| } | |
| return (PyObject*)seq; | |
| } | |
| PyObject * | |
| _PyCompile_OptimizeCfg(PyObject *seq, PyObject *consts, int nlocals) | |
| { | |
| if (!_PyInstructionSequence_Check(seq)) { | |
| PyErr_SetString(PyExc_ValueError, "expected an instruction sequence"); | |
| return NULL; | |
| } | |
| if (!PyList_Check(consts)) { | |
| PyErr_SetString(PyExc_TypeError, "consts must be a list"); | |
| return NULL; | |
| } | |
| PyObject *const_cache = PyDict_New(); | |
| if (const_cache == NULL) { | |
| return NULL; | |
| } | |
| PyObject *res = NULL; | |
| cfg_builder *g = _PyCfg_FromInstructionSequence((_PyInstructionSequence*)seq); | |
| if (g == NULL) { | |
| goto error; | |
| } | |
| int nparams = 0, firstlineno = 1; | |
| if (_PyCfg_OptimizeCodeUnit(g, consts, const_cache, nlocals, | |
| nparams, firstlineno) < 0) { | |
| goto error; | |
| } | |
| if (calculate_stackdepth(g) == ERROR) { | |
| goto error; | |
| } | |
| if (optimize_load_fast(g) != SUCCESS) { | |
| goto error; | |
| } | |
| res = cfg_to_instruction_sequence(g); | |
| error: | |
| Py_DECREF(const_cache); | |
| _PyCfgBuilder_Free(g); | |
| return res; | |
| } |