|
114 | 114 | (opcode) == RAISE_VARARGS || \
|
115 | 115 | (opcode) == RERAISE)
|
116 | 116 |
|
| 117 | +#define IS_SUPERINSTRUCTION_OPCODE(opcode) \ |
| 118 | + ((opcode) == LOAD_FAST__LOAD_FAST || \ |
| 119 | + (opcode) == LOAD_FAST__LOAD_CONST || \ |
| 120 | + (opcode) == LOAD_CONST__LOAD_FAST || \ |
| 121 | + (opcode) == STORE_FAST__LOAD_FAST || \ |
| 122 | + (opcode) == STORE_FAST__STORE_FAST) |
| 123 | + |
117 | 124 | #define IS_TOP_LEVEL_AWAIT(c) ( \
|
118 | 125 | (c->c_flags->cf_flags & PyCF_ALLOW_TOP_LEVEL_AWAIT) \
|
119 | 126 | && (c->u->u_ste->ste_type == ModuleBlock))
|
@@ -258,6 +265,8 @@ typedef struct basicblock_ {
|
258 | 265 | int b_iused;
|
259 | 266 | /* length of instruction array (b_instr) */
|
260 | 267 | int b_ialloc;
|
| 268 | + /* Used by add_checks_for_loads_of_unknown_variables */ |
| 269 | + uint64_t b_unsafe_locals_mask; |
261 | 270 | /* Number of predecessors that a block has. */
|
262 | 271 | int b_predecessors;
|
263 | 272 | /* depth of stack upon entry of block, computed by stackdepth() */
|
@@ -8052,103 +8061,165 @@ assemble_jump_offsets(basicblock *entryblock)
|
8052 | 8061 | }
|
8053 | 8062 |
|
8054 | 8063 |
|
8055 |
| -// Ensure each basicblock is only put onto the stack once. |
8056 |
| -#define MAYBE_PUSH(B) do { \ |
8057 |
| - if ((B)->b_visited == 0) { \ |
8058 |
| - *(*stack_top)++ = (B); \ |
8059 |
| - (B)->b_visited = 1; \ |
8060 |
| - } \ |
8061 |
| - } while (0) |
| 8064 | +// helper functions for add_checks_for_loads_of_unknown_variables |
| 8065 | +static inline void |
| 8066 | +maybe_push(basicblock *b, uint64_t unsafe_mask, basicblock ***sp) |
| 8067 | +{ |
| 8068 | + // Push b if the unsafe mask is giving us any new information. |
| 8069 | + // To avoid overflowing the stack, only allow each block once. |
| 8070 | + // Use b->b_visited=1 to mean that b is currently on the stack. |
| 8071 | + uint64_t both = b->b_unsafe_locals_mask | unsafe_mask; |
| 8072 | + if (b->b_unsafe_locals_mask != both) { |
| 8073 | + b->b_unsafe_locals_mask = both; |
| 8074 | + // More work left to do. |
| 8075 | + if (!b->b_visited) { |
| 8076 | + // not on the stack, so push it. |
| 8077 | + *(*sp)++ = b; |
| 8078 | + b->b_visited = 1; |
| 8079 | + } |
| 8080 | + } |
| 8081 | +} |
8062 | 8082 |
|
8063 | 8083 | static void
|
8064 |
| -scan_block_for_local(int target, basicblock *b, bool unsafe_to_start, |
8065 |
| - basicblock ***stack_top) |
| 8084 | +scan_block_for_locals(basicblock *b, basicblock ***sp) |
8066 | 8085 | {
|
8067 |
| - bool unsafe = unsafe_to_start; |
| 8086 | + // bit i is set if local i is potentially uninitialized |
| 8087 | + uint64_t unsafe_mask = b->b_unsafe_locals_mask; |
8068 | 8088 | for (int i = 0; i < b->b_iused; i++) {
|
8069 | 8089 | struct instr *instr = &b->b_instr[i];
|
8070 | 8090 | assert(instr->i_opcode != EXTENDED_ARG);
|
8071 | 8091 | assert(instr->i_opcode != EXTENDED_ARG_QUICK);
|
8072 |
| - assert(instr->i_opcode != LOAD_FAST__LOAD_FAST); |
8073 |
| - assert(instr->i_opcode != STORE_FAST__LOAD_FAST); |
8074 |
| - assert(instr->i_opcode != LOAD_CONST__LOAD_FAST); |
8075 |
| - assert(instr->i_opcode != STORE_FAST__STORE_FAST); |
8076 |
| - assert(instr->i_opcode != LOAD_FAST__LOAD_CONST); |
8077 |
| - if (unsafe && instr->i_except != NULL) { |
8078 |
| - MAYBE_PUSH(instr->i_except); |
8079 |
| - } |
8080 |
| - if (instr->i_oparg != target) { |
| 8092 | + assert(!IS_SUPERINSTRUCTION_OPCODE(instr->i_opcode)); |
| 8093 | + if (instr->i_except != NULL) { |
| 8094 | + maybe_push(instr->i_except, unsafe_mask, sp); |
| 8095 | + } |
| 8096 | + if (instr->i_oparg >= 64) { |
8081 | 8097 | continue;
|
8082 | 8098 | }
|
| 8099 | + assert(instr->i_oparg >= 0); |
| 8100 | + uint64_t bit = (uint64_t)1 << instr->i_oparg; |
8083 | 8101 | switch (instr->i_opcode) {
|
| 8102 | + case DELETE_FAST: |
| 8103 | + unsafe_mask |= bit; |
| 8104 | + break; |
| 8105 | + case STORE_FAST: |
| 8106 | + unsafe_mask &= ~bit; |
| 8107 | + break; |
8084 | 8108 | case LOAD_FAST_CHECK:
|
8085 |
| - // if this doesn't raise, then var is defined |
8086 |
| - unsafe = false; |
| 8109 | + // If this doesn't raise, then the local is defined. |
| 8110 | + unsafe_mask &= ~bit; |
8087 | 8111 | break;
|
8088 | 8112 | case LOAD_FAST:
|
8089 |
| - if (unsafe) { |
| 8113 | + if (unsafe_mask & bit) { |
8090 | 8114 | instr->i_opcode = LOAD_FAST_CHECK;
|
8091 | 8115 | }
|
8092 |
| - unsafe = false; |
8093 |
| - break; |
8094 |
| - case STORE_FAST: |
8095 |
| - unsafe = false; |
8096 |
| - break; |
8097 |
| - case DELETE_FAST: |
8098 |
| - unsafe = true; |
| 8116 | + unsafe_mask &= ~bit; |
8099 | 8117 | break;
|
8100 | 8118 | }
|
8101 | 8119 | }
|
8102 |
| - if (unsafe) { |
8103 |
| - // unsafe at end of this block, |
8104 |
| - // so unsafe at start of next blocks |
8105 |
| - if (b->b_next && BB_HAS_FALLTHROUGH(b)) { |
8106 |
| - MAYBE_PUSH(b->b_next); |
8107 |
| - } |
8108 |
| - struct instr *last = basicblock_last_instr(b); |
8109 |
| - if (last != NULL) { |
8110 |
| - if (is_jump(last)) { |
8111 |
| - assert(last->i_target != NULL); |
8112 |
| - MAYBE_PUSH(last->i_target); |
| 8120 | + if (b->b_next && BB_HAS_FALLTHROUGH(b)) { |
| 8121 | + maybe_push(b->b_next, unsafe_mask, sp); |
| 8122 | + } |
| 8123 | + struct instr *last = basicblock_last_instr(b); |
| 8124 | + if (last && is_jump(last)) { |
| 8125 | + assert(last->i_target != NULL); |
| 8126 | + maybe_push(last->i_target, unsafe_mask, sp); |
| 8127 | + } |
| 8128 | +} |
| 8129 | + |
| 8130 | +static int |
| 8131 | +fast_scan_many_locals(basicblock *entryblock, int nlocals) |
| 8132 | +{ |
| 8133 | + assert(nlocals > 64); |
| 8134 | + Py_ssize_t *states = PyMem_Calloc(nlocals - 64, sizeof(Py_ssize_t)); |
| 8135 | + if (states == NULL) { |
| 8136 | + PyErr_NoMemory(); |
| 8137 | + return -1; |
| 8138 | + } |
| 8139 | + Py_ssize_t blocknum = 0; |
| 8140 | + // state[i - 64] == blocknum if local i is guaranteed to |
| 8141 | + // be initialized, i.e., if it has had a previous LOAD_FAST or |
| 8142 | + // STORE_FAST within that basicblock (not followed by DELETE_FAST). |
| 8143 | + for (basicblock *b = entryblock; b != NULL; b = b->b_next) { |
| 8144 | + blocknum++; |
| 8145 | + for (int i = 0; i < b->b_iused; i++) { |
| 8146 | + struct instr *instr = &b->b_instr[i]; |
| 8147 | + assert(instr->i_opcode != EXTENDED_ARG); |
| 8148 | + assert(instr->i_opcode != EXTENDED_ARG_QUICK); |
| 8149 | + assert(!IS_SUPERINSTRUCTION_OPCODE(instr->i_opcode)); |
| 8150 | + int arg = instr->i_oparg; |
| 8151 | + if (arg < 64) { |
| 8152 | + continue; |
| 8153 | + } |
| 8154 | + assert(arg >= 0); |
| 8155 | + switch (instr->i_opcode) { |
| 8156 | + case DELETE_FAST: |
| 8157 | + states[arg - 64] = blocknum - 1; |
| 8158 | + break; |
| 8159 | + case STORE_FAST: |
| 8160 | + states[arg - 64] = blocknum; |
| 8161 | + break; |
| 8162 | + case LOAD_FAST: |
| 8163 | + if (states[arg - 64] != blocknum) { |
| 8164 | + instr->i_opcode = LOAD_FAST_CHECK; |
| 8165 | + } |
| 8166 | + states[arg - 64] = blocknum; |
| 8167 | + break; |
| 8168 | + case LOAD_FAST_CHECK: |
| 8169 | + Py_UNREACHABLE(); |
8113 | 8170 | }
|
8114 | 8171 | }
|
8115 | 8172 | }
|
| 8173 | + PyMem_Free(states); |
| 8174 | + return 0; |
8116 | 8175 | }
|
8117 |
| -#undef MAYBE_PUSH |
8118 | 8176 |
|
8119 | 8177 | static int
|
8120 | 8178 | add_checks_for_loads_of_uninitialized_variables(basicblock *entryblock,
|
8121 | 8179 | struct compiler *c)
|
8122 | 8180 | {
|
| 8181 | + int nlocals = (int)PyDict_GET_SIZE(c->u->u_varnames); |
| 8182 | + if (nlocals == 0) { |
| 8183 | + return 0; |
| 8184 | + } |
| 8185 | + if (nlocals > 64) { |
| 8186 | + // To avoid O(nlocals**2) compilation, locals beyond the first |
| 8187 | + // 64 are only analyzed one basicblock at a time: initialization |
| 8188 | + // info is not passed between basicblocks. |
| 8189 | + if (fast_scan_many_locals(entryblock, nlocals) < 0) { |
| 8190 | + return -1; |
| 8191 | + } |
| 8192 | + nlocals = 64; |
| 8193 | + } |
8123 | 8194 | basicblock **stack = make_cfg_traversal_stack(entryblock);
|
8124 | 8195 | if (stack == NULL) {
|
8125 | 8196 | return -1;
|
8126 | 8197 | }
|
8127 |
| - Py_ssize_t nparams = PyList_GET_SIZE(c->u->u_ste->ste_varnames); |
8128 |
| - int nlocals = (int)PyDict_GET_SIZE(c->u->u_varnames); |
8129 |
| - for (int target = 0; target < nlocals; target++) { |
8130 |
| - for (basicblock *b = entryblock; b != NULL; b = b->b_next) { |
8131 |
| - b->b_visited = 0; |
8132 |
| - } |
8133 |
| - basicblock **stack_top = stack; |
| 8198 | + basicblock **sp = stack; |
8134 | 8199 |
|
8135 |
| - // First pass: find the relevant DFS starting points: |
8136 |
| - // the places where "being uninitialized" originates, |
8137 |
| - // which are the entry block and any DELETE_FAST statements. |
8138 |
| - if (target >= nparams) { |
8139 |
| - // only non-parameter locals start out uninitialized. |
8140 |
| - *(stack_top++) = entryblock; |
8141 |
| - entryblock->b_visited = 1; |
8142 |
| - } |
8143 |
| - for (basicblock *b = entryblock; b != NULL; b = b->b_next) { |
8144 |
| - scan_block_for_local(target, b, false, &stack_top); |
8145 |
| - } |
| 8200 | + // First origin of being uninitialized: |
| 8201 | + // The non-parameter locals in the entry block. |
| 8202 | + int nparams = (int)PyList_GET_SIZE(c->u->u_ste->ste_varnames); |
| 8203 | + uint64_t start_mask = 0; |
| 8204 | + for (int i = nparams; i < nlocals; i++) { |
| 8205 | + start_mask |= (uint64_t)1 << i; |
| 8206 | + } |
| 8207 | + maybe_push(entryblock, start_mask, &sp); |
8146 | 8208 |
|
8147 |
| - // Second pass: Depth-first search to propagate uncertainty |
8148 |
| - while (stack_top > stack) { |
8149 |
| - basicblock *b = *--stack_top; |
8150 |
| - scan_block_for_local(target, b, true, &stack_top); |
8151 |
| - } |
| 8209 | + // Second origin of being uninitialized: |
| 8210 | + // There could be DELETE_FAST somewhere, so |
| 8211 | + // be sure to scan each basicblock at least once. |
| 8212 | + for (basicblock *b = entryblock; b != NULL; b = b->b_next) { |
| 8213 | + scan_block_for_locals(b, &sp); |
| 8214 | + } |
| 8215 | + |
| 8216 | + // Now propagate the uncertainty from the origins we found: Use |
| 8217 | + // LOAD_FAST_CHECK for any LOAD_FAST where the local could be undefined. |
| 8218 | + while (sp > stack) { |
| 8219 | + basicblock *b = *--sp; |
| 8220 | + // mark as no longer on stack |
| 8221 | + b->b_visited = 0; |
| 8222 | + scan_block_for_locals(b, &sp); |
8152 | 8223 | }
|
8153 | 8224 | PyMem_Free(stack);
|
8154 | 8225 | return 0;
|
|
0 commit comments