mirror of
https://github.com/python/cpython
synced 2024-09-16 01:13:03 +00:00
gh-93143: Avoid NULL check in LOAD_FAST based on analysis in the compiler (GH-93144)
This commit is contained in:
parent
8a5e3c2ec6
commit
f425f3bb27
19
Include/internal/pycore_opcode.h
generated
19
Include/internal/pycore_opcode.h
generated
|
@ -158,6 +158,7 @@ const uint8_t _PyOpcode_Deopt[256] = {
|
|||
[LOAD_CONST__LOAD_FAST] = LOAD_CONST,
|
||||
[LOAD_DEREF] = LOAD_DEREF,
|
||||
[LOAD_FAST] = LOAD_FAST,
|
||||
[LOAD_FAST_CHECK] = LOAD_FAST_CHECK,
|
||||
[LOAD_FAST__LOAD_CONST] = LOAD_FAST,
|
||||
[LOAD_FAST__LOAD_FAST] = LOAD_FAST,
|
||||
[LOAD_GLOBAL] = LOAD_GLOBAL,
|
||||
|
@ -340,6 +341,7 @@ const uint8_t _PyOpcode_Original[256] = {
|
|||
[LOAD_CONST__LOAD_FAST] = LOAD_CONST,
|
||||
[LOAD_DEREF] = LOAD_DEREF,
|
||||
[LOAD_FAST] = LOAD_FAST,
|
||||
[LOAD_FAST_CHECK] = LOAD_FAST_CHECK,
|
||||
[LOAD_FAST__LOAD_CONST] = LOAD_FAST,
|
||||
[LOAD_FAST__LOAD_FAST] = LOAD_FAST,
|
||||
[LOAD_GLOBAL] = LOAD_GLOBAL,
|
||||
|
@ -547,7 +549,7 @@ static const char *const _PyOpcode_OpName[256] = {
|
|||
[LOAD_FAST] = "LOAD_FAST",
|
||||
[STORE_FAST] = "STORE_FAST",
|
||||
[DELETE_FAST] = "DELETE_FAST",
|
||||
[LOAD_METHOD_WITH_VALUES] = "LOAD_METHOD_WITH_VALUES",
|
||||
[LOAD_FAST_CHECK] = "LOAD_FAST_CHECK",
|
||||
[POP_JUMP_FORWARD_IF_NOT_NONE] = "POP_JUMP_FORWARD_IF_NOT_NONE",
|
||||
[POP_JUMP_FORWARD_IF_NONE] = "POP_JUMP_FORWARD_IF_NONE",
|
||||
[RAISE_VARARGS] = "RAISE_VARARGS",
|
||||
|
@ -561,9 +563,9 @@ static const char *const _PyOpcode_OpName[256] = {
|
|||
[STORE_DEREF] = "STORE_DEREF",
|
||||
[DELETE_DEREF] = "DELETE_DEREF",
|
||||
[JUMP_BACKWARD] = "JUMP_BACKWARD",
|
||||
[RESUME_QUICK] = "RESUME_QUICK",
|
||||
[LOAD_METHOD_WITH_VALUES] = "LOAD_METHOD_WITH_VALUES",
|
||||
[CALL_FUNCTION_EX] = "CALL_FUNCTION_EX",
|
||||
[STORE_ATTR_ADAPTIVE] = "STORE_ATTR_ADAPTIVE",
|
||||
[RESUME_QUICK] = "RESUME_QUICK",
|
||||
[EXTENDED_ARG] = "EXTENDED_ARG",
|
||||
[LIST_APPEND] = "LIST_APPEND",
|
||||
[SET_ADD] = "SET_ADD",
|
||||
|
@ -573,33 +575,33 @@ static const char *const _PyOpcode_OpName[256] = {
|
|||
[YIELD_VALUE] = "YIELD_VALUE",
|
||||
[RESUME] = "RESUME",
|
||||
[MATCH_CLASS] = "MATCH_CLASS",
|
||||
[STORE_ATTR_ADAPTIVE] = "STORE_ATTR_ADAPTIVE",
|
||||
[STORE_ATTR_INSTANCE_VALUE] = "STORE_ATTR_INSTANCE_VALUE",
|
||||
[STORE_ATTR_SLOT] = "STORE_ATTR_SLOT",
|
||||
[FORMAT_VALUE] = "FORMAT_VALUE",
|
||||
[BUILD_CONST_KEY_MAP] = "BUILD_CONST_KEY_MAP",
|
||||
[BUILD_STRING] = "BUILD_STRING",
|
||||
[STORE_ATTR_SLOT] = "STORE_ATTR_SLOT",
|
||||
[STORE_ATTR_WITH_HINT] = "STORE_ATTR_WITH_HINT",
|
||||
[STORE_FAST__LOAD_FAST] = "STORE_FAST__LOAD_FAST",
|
||||
[LOAD_METHOD] = "LOAD_METHOD",
|
||||
[STORE_FAST__STORE_FAST] = "STORE_FAST__STORE_FAST",
|
||||
[STORE_FAST__LOAD_FAST] = "STORE_FAST__LOAD_FAST",
|
||||
[LIST_EXTEND] = "LIST_EXTEND",
|
||||
[SET_UPDATE] = "SET_UPDATE",
|
||||
[DICT_MERGE] = "DICT_MERGE",
|
||||
[DICT_UPDATE] = "DICT_UPDATE",
|
||||
[STORE_FAST__STORE_FAST] = "STORE_FAST__STORE_FAST",
|
||||
[STORE_SUBSCR_ADAPTIVE] = "STORE_SUBSCR_ADAPTIVE",
|
||||
[STORE_SUBSCR_DICT] = "STORE_SUBSCR_DICT",
|
||||
[STORE_SUBSCR_LIST_INT] = "STORE_SUBSCR_LIST_INT",
|
||||
[UNPACK_SEQUENCE_ADAPTIVE] = "UNPACK_SEQUENCE_ADAPTIVE",
|
||||
[UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST",
|
||||
[CALL] = "CALL",
|
||||
[KW_NAMES] = "KW_NAMES",
|
||||
[POP_JUMP_BACKWARD_IF_NOT_NONE] = "POP_JUMP_BACKWARD_IF_NOT_NONE",
|
||||
[POP_JUMP_BACKWARD_IF_NONE] = "POP_JUMP_BACKWARD_IF_NONE",
|
||||
[POP_JUMP_BACKWARD_IF_FALSE] = "POP_JUMP_BACKWARD_IF_FALSE",
|
||||
[POP_JUMP_BACKWARD_IF_TRUE] = "POP_JUMP_BACKWARD_IF_TRUE",
|
||||
[UNPACK_SEQUENCE_LIST] = "UNPACK_SEQUENCE_LIST",
|
||||
[UNPACK_SEQUENCE_TUPLE] = "UNPACK_SEQUENCE_TUPLE",
|
||||
[UNPACK_SEQUENCE_TWO_TUPLE] = "UNPACK_SEQUENCE_TWO_TUPLE",
|
||||
[179] = "<179>",
|
||||
[180] = "<180>",
|
||||
[181] = "<181>",
|
||||
[182] = "<182>",
|
||||
|
@ -680,7 +682,6 @@ static const char *const _PyOpcode_OpName[256] = {
|
|||
#endif
|
||||
|
||||
#define EXTRA_CASES \
|
||||
case 179: \
|
||||
case 180: \
|
||||
case 181: \
|
||||
case 182: \
|
||||
|
|
31
Include/opcode.h
generated
31
Include/opcode.h
generated
|
@ -81,6 +81,7 @@ extern "C" {
|
|||
#define LOAD_FAST 124
|
||||
#define STORE_FAST 125
|
||||
#define DELETE_FAST 126
|
||||
#define LOAD_FAST_CHECK 127
|
||||
#define POP_JUMP_FORWARD_IF_NOT_NONE 128
|
||||
#define POP_JUMP_FORWARD_IF_NONE 129
|
||||
#define RAISE_VARARGS 130
|
||||
|
@ -173,21 +174,21 @@ extern "C" {
|
|||
#define LOAD_METHOD_MODULE 86
|
||||
#define LOAD_METHOD_NO_DICT 113
|
||||
#define LOAD_METHOD_WITH_DICT 121
|
||||
#define LOAD_METHOD_WITH_VALUES 127
|
||||
#define RESUME_QUICK 141
|
||||
#define STORE_ATTR_ADAPTIVE 143
|
||||
#define STORE_ATTR_INSTANCE_VALUE 153
|
||||
#define STORE_ATTR_SLOT 154
|
||||
#define STORE_ATTR_WITH_HINT 158
|
||||
#define STORE_FAST__LOAD_FAST 159
|
||||
#define STORE_FAST__STORE_FAST 161
|
||||
#define STORE_SUBSCR_ADAPTIVE 166
|
||||
#define STORE_SUBSCR_DICT 167
|
||||
#define STORE_SUBSCR_LIST_INT 168
|
||||
#define UNPACK_SEQUENCE_ADAPTIVE 169
|
||||
#define UNPACK_SEQUENCE_LIST 170
|
||||
#define UNPACK_SEQUENCE_TUPLE 177
|
||||
#define UNPACK_SEQUENCE_TWO_TUPLE 178
|
||||
#define LOAD_METHOD_WITH_VALUES 141
|
||||
#define RESUME_QUICK 143
|
||||
#define STORE_ATTR_ADAPTIVE 153
|
||||
#define STORE_ATTR_INSTANCE_VALUE 154
|
||||
#define STORE_ATTR_SLOT 158
|
||||
#define STORE_ATTR_WITH_HINT 159
|
||||
#define STORE_FAST__LOAD_FAST 161
|
||||
#define STORE_FAST__STORE_FAST 166
|
||||
#define STORE_SUBSCR_ADAPTIVE 167
|
||||
#define STORE_SUBSCR_DICT 168
|
||||
#define STORE_SUBSCR_LIST_INT 169
|
||||
#define UNPACK_SEQUENCE_ADAPTIVE 170
|
||||
#define UNPACK_SEQUENCE_LIST 177
|
||||
#define UNPACK_SEQUENCE_TUPLE 178
|
||||
#define UNPACK_SEQUENCE_TWO_TUPLE 179
|
||||
#define DO_TRACING 255
|
||||
|
||||
#define HAS_CONST(op) (false\
|
||||
|
|
|
@ -406,6 +406,7 @@ def _write_atomic(path, data, mode=0o666):
|
|||
|
||||
# Python 3.12a1 3500 (Remove PRECALL opcode)
|
||||
# Python 3.12a1 3501 (YIELD_VALUE oparg == stack_depth)
|
||||
# Python 3.12a1 3502 (LOAD_FAST_CHECK, no NULL-check in LOAD_FAST)
|
||||
|
||||
# Python 3.13 will start with 3550
|
||||
|
||||
|
@ -419,7 +420,7 @@ def _write_atomic(path, data, mode=0o666):
|
|||
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
|
||||
# in PC/launcher.c must also be updated.
|
||||
|
||||
MAGIC_NUMBER = (3501).to_bytes(2, 'little') + b'\r\n'
|
||||
MAGIC_NUMBER = (3502).to_bytes(2, 'little') + b'\r\n'
|
||||
|
||||
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
|
||||
|
||||
|
|
|
@ -139,12 +139,14 @@ def jabs_op(name, op):
|
|||
def_op('COPY', 120)
|
||||
def_op('BINARY_OP', 122)
|
||||
jrel_op('SEND', 123) # Number of bytes to skip
|
||||
def_op('LOAD_FAST', 124) # Local variable number
|
||||
def_op('LOAD_FAST', 124) # Local variable number, no null check
|
||||
haslocal.append(124)
|
||||
def_op('STORE_FAST', 125) # Local variable number
|
||||
haslocal.append(125)
|
||||
def_op('DELETE_FAST', 126) # Local variable number
|
||||
haslocal.append(126)
|
||||
def_op('LOAD_FAST_CHECK', 127) # Local variable number
|
||||
haslocal.append(127)
|
||||
jrel_op('POP_JUMP_FORWARD_IF_NOT_NONE', 128)
|
||||
jrel_op('POP_JUMP_FORWARD_IF_NONE', 129)
|
||||
def_op('RAISE_VARARGS', 130) # Number of raise arguments (1, 2, or 3)
|
||||
|
|
|
@ -360,7 +360,7 @@ def bug42562():
|
|||
--> BINARY_OP 11 (/)
|
||||
POP_TOP
|
||||
|
||||
%3d LOAD_FAST 1 (tb)
|
||||
%3d LOAD_FAST_CHECK 1 (tb)
|
||||
RETURN_VALUE
|
||||
>> PUSH_EXC_INFO
|
||||
|
||||
|
@ -1399,7 +1399,7 @@ def _prepare_test_cases():
|
|||
Instruction(opname='LOAD_CONST', opcode=100, arg=4, argval='I can haz else clause?', argrepr="'I can haz else clause?'", offset=100, starts_line=None, is_jump_target=False, positions=None),
|
||||
Instruction(opname='CALL', opcode=171, arg=1, argval=1, argrepr='', offset=102, starts_line=None, is_jump_target=False, positions=None),
|
||||
Instruction(opname='POP_TOP', opcode=1, arg=None, argval=None, argrepr='', offset=112, starts_line=None, is_jump_target=False, positions=None),
|
||||
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=114, starts_line=11, is_jump_target=True, positions=None),
|
||||
Instruction(opname='LOAD_FAST_CHECK', opcode=127, arg=0, argval='i', argrepr='i', offset=114, starts_line=11, is_jump_target=True, positions=None),
|
||||
Instruction(opname='POP_JUMP_FORWARD_IF_FALSE', opcode=114, arg=34, argval=186, argrepr='to 186', offset=116, starts_line=None, is_jump_target=False, positions=None),
|
||||
Instruction(opname='LOAD_GLOBAL', opcode=116, arg=3, argval='print', argrepr='NULL + print', offset=118, starts_line=12, is_jump_target=True, positions=None),
|
||||
Instruction(opname='LOAD_FAST', opcode=124, arg=0, argval='i', argrepr='i', offset=130, starts_line=None, is_jump_target=False, positions=None),
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import dis
|
||||
from itertools import combinations, product
|
||||
import sys
|
||||
import textwrap
|
||||
import unittest
|
||||
|
||||
|
@ -682,5 +683,184 @@ def test_bpo_45773_pop_jump_if_false(self):
|
|||
compile("while True or not spam: pass", "<test>", "exec")
|
||||
|
||||
|
||||
class TestMarkingVariablesAsUnKnown(BytecodeTestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.addCleanup(sys.settrace, sys.gettrace())
|
||||
sys.settrace(None)
|
||||
|
||||
def test_load_fast_known_simple(self):
|
||||
def f():
|
||||
x = 1
|
||||
y = x + x
|
||||
self.assertInBytecode(f, 'LOAD_FAST')
|
||||
|
||||
def test_load_fast_unknown_simple(self):
|
||||
def f():
|
||||
if condition():
|
||||
x = 1
|
||||
print(x)
|
||||
self.assertInBytecode(f, 'LOAD_FAST_CHECK')
|
||||
self.assertNotInBytecode(f, 'LOAD_FAST')
|
||||
|
||||
def test_load_fast_unknown_because_del(self):
|
||||
def f():
|
||||
x = 1
|
||||
del x
|
||||
print(x)
|
||||
self.assertInBytecode(f, 'LOAD_FAST_CHECK')
|
||||
self.assertNotInBytecode(f, 'LOAD_FAST')
|
||||
|
||||
def test_load_fast_known_because_parameter(self):
|
||||
def f1(x):
|
||||
print(x)
|
||||
self.assertInBytecode(f1, 'LOAD_FAST')
|
||||
self.assertNotInBytecode(f1, 'LOAD_FAST_CHECK')
|
||||
|
||||
def f2(*, x):
|
||||
print(x)
|
||||
self.assertInBytecode(f2, 'LOAD_FAST')
|
||||
self.assertNotInBytecode(f2, 'LOAD_FAST_CHECK')
|
||||
|
||||
def f3(*args):
|
||||
print(args)
|
||||
self.assertInBytecode(f3, 'LOAD_FAST')
|
||||
self.assertNotInBytecode(f3, 'LOAD_FAST_CHECK')
|
||||
|
||||
def f4(**kwargs):
|
||||
print(kwargs)
|
||||
self.assertInBytecode(f4, 'LOAD_FAST')
|
||||
self.assertNotInBytecode(f4, 'LOAD_FAST_CHECK')
|
||||
|
||||
def f5(x=0):
|
||||
print(x)
|
||||
self.assertInBytecode(f5, 'LOAD_FAST')
|
||||
self.assertNotInBytecode(f5, 'LOAD_FAST_CHECK')
|
||||
|
||||
def test_load_fast_known_because_already_loaded(self):
|
||||
def f():
|
||||
if condition():
|
||||
x = 1
|
||||
print(x)
|
||||
print(x)
|
||||
self.assertInBytecode(f, 'LOAD_FAST_CHECK')
|
||||
self.assertInBytecode(f, 'LOAD_FAST')
|
||||
|
||||
def test_load_fast_known_multiple_branches(self):
|
||||
def f():
|
||||
if condition():
|
||||
x = 1
|
||||
else:
|
||||
x = 2
|
||||
print(x)
|
||||
self.assertInBytecode(f, 'LOAD_FAST')
|
||||
self.assertNotInBytecode(f, 'LOAD_FAST_CHECK')
|
||||
|
||||
def test_load_fast_unknown_after_error(self):
|
||||
def f():
|
||||
try:
|
||||
res = 1 / 0
|
||||
except ZeroDivisionError:
|
||||
pass
|
||||
return res
|
||||
# LOAD_FAST (known) still occurs in the no-exception branch.
|
||||
# Assert that it doesn't occur in the LOAD_FAST_CHECK branch.
|
||||
self.assertInBytecode(f, 'LOAD_FAST_CHECK')
|
||||
|
||||
def test_load_fast_unknown_after_error_2(self):
|
||||
def f():
|
||||
try:
|
||||
1 / 0
|
||||
except:
|
||||
print(a, b, c, d, e, f, g)
|
||||
a = b = c = d = e = f = g = 1
|
||||
self.assertInBytecode(f, 'LOAD_FAST_CHECK')
|
||||
self.assertNotInBytecode(f, 'LOAD_FAST')
|
||||
|
||||
def test_setting_lineno_adds_check(self):
|
||||
code = textwrap.dedent("""\
|
||||
def f():
|
||||
x = 2
|
||||
L = 3
|
||||
L = 4
|
||||
for i in range(55):
|
||||
x + 6
|
||||
del x
|
||||
L = 8
|
||||
L = 9
|
||||
L = 10
|
||||
""")
|
||||
ns = {}
|
||||
exec(code, ns)
|
||||
f = ns['f']
|
||||
self.assertInBytecode(f, "LOAD_FAST")
|
||||
def trace(frame, event, arg):
|
||||
if event == 'line' and frame.f_lineno == 9:
|
||||
frame.f_lineno = 2
|
||||
sys.settrace(None)
|
||||
return None
|
||||
return trace
|
||||
sys.settrace(trace)
|
||||
f()
|
||||
self.assertNotInBytecode(f, "LOAD_FAST")
|
||||
|
||||
def make_function_with_no_checks(self):
|
||||
code = textwrap.dedent("""\
|
||||
def f():
|
||||
x = 2
|
||||
L = 3
|
||||
L = 4
|
||||
L = 5
|
||||
if not L:
|
||||
x + 7
|
||||
y = 2
|
||||
""")
|
||||
ns = {}
|
||||
exec(code, ns)
|
||||
f = ns['f']
|
||||
self.assertInBytecode(f, "LOAD_FAST")
|
||||
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
|
||||
return f
|
||||
|
||||
def test_deleting_local_adds_check(self):
|
||||
f = self.make_function_with_no_checks()
|
||||
def trace(frame, event, arg):
|
||||
if event == 'line' and frame.f_lineno == 4:
|
||||
del frame.f_locals["x"]
|
||||
sys.settrace(None)
|
||||
return None
|
||||
return trace
|
||||
sys.settrace(trace)
|
||||
f()
|
||||
self.assertNotInBytecode(f, "LOAD_FAST")
|
||||
self.assertInBytecode(f, "LOAD_FAST_CHECK")
|
||||
|
||||
def test_modifying_local_does_not_add_check(self):
|
||||
f = self.make_function_with_no_checks()
|
||||
def trace(frame, event, arg):
|
||||
if event == 'line' and frame.f_lineno == 4:
|
||||
frame.f_locals["x"] = 42
|
||||
sys.settrace(None)
|
||||
return None
|
||||
return trace
|
||||
sys.settrace(trace)
|
||||
f()
|
||||
self.assertInBytecode(f, "LOAD_FAST")
|
||||
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
|
||||
|
||||
def test_initializing_local_does_not_add_check(self):
|
||||
f = self.make_function_with_no_checks()
|
||||
def trace(frame, event, arg):
|
||||
if event == 'line' and frame.f_lineno == 4:
|
||||
frame.f_locals["y"] = 42
|
||||
sys.settrace(None)
|
||||
return None
|
||||
return trace
|
||||
sys.settrace(trace)
|
||||
f()
|
||||
self.assertInBytecode(f, "LOAD_FAST")
|
||||
self.assertNotInBytecode(f, "LOAD_FAST_CHECK")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
Avoid ``NULL`` checks for uninitialized local variables by determining at compile time which variables must be initialized.
|
|
@ -455,6 +455,26 @@ _PyFrame_GetState(PyFrameObject *frame)
|
|||
Py_UNREACHABLE();
|
||||
}
|
||||
|
||||
static void
|
||||
add_load_fast_null_checks(PyCodeObject *co)
|
||||
{
|
||||
_Py_CODEUNIT *instructions = _PyCode_CODE(co);
|
||||
for (Py_ssize_t i = 0; i < Py_SIZE(co); i++) {
|
||||
switch (_Py_OPCODE(instructions[i])) {
|
||||
case LOAD_FAST:
|
||||
case LOAD_FAST__LOAD_FAST:
|
||||
case LOAD_FAST__LOAD_CONST:
|
||||
_Py_SET_OPCODE(instructions[i], LOAD_FAST_CHECK);
|
||||
break;
|
||||
case LOAD_CONST__LOAD_FAST:
|
||||
_Py_SET_OPCODE(instructions[i], LOAD_CONST);
|
||||
break;
|
||||
case STORE_FAST__LOAD_FAST:
|
||||
_Py_SET_OPCODE(instructions[i], STORE_FAST);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Setter for f_lineno - you can set f_lineno from within a trace function in
|
||||
* order to jump to a given line of code, subject to some restrictions. Most
|
||||
|
@ -545,6 +565,8 @@ frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignore
|
|||
return -1;
|
||||
}
|
||||
|
||||
add_load_fast_null_checks(f->f_frame->f_code);
|
||||
|
||||
/* PyCode_NewWithPosOnlyArgs limits co_code to be under INT_MAX so this
|
||||
* should never overflow. */
|
||||
int len = (int)Py_SIZE(f->f_frame->f_code);
|
||||
|
@ -1047,6 +1069,7 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
|
|||
}
|
||||
fast = _PyFrame_GetLocalsArray(frame);
|
||||
co = frame->f_code;
|
||||
bool added_null_checks = false;
|
||||
|
||||
PyErr_Fetch(&error_type, &error_value, &error_traceback);
|
||||
for (int i = 0; i < co->co_nlocalsplus; i++) {
|
||||
|
@ -1066,6 +1089,10 @@ _PyFrame_LocalsToFast(_PyInterpreterFrame *frame, int clear)
|
|||
}
|
||||
}
|
||||
PyObject *oldvalue = fast[i];
|
||||
if (!added_null_checks && oldvalue != NULL && value == NULL) {
|
||||
add_load_fast_null_checks(co);
|
||||
added_null_checks = true;
|
||||
}
|
||||
PyObject *cell = NULL;
|
||||
if (kind == CO_FAST_FREE) {
|
||||
// The cell was set when the frame was created from
|
||||
|
|
|
@ -1813,7 +1813,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
DISPATCH();
|
||||
}
|
||||
|
||||
TARGET(LOAD_FAST) {
|
||||
TARGET(LOAD_FAST_CHECK) {
|
||||
PyObject *value = GETLOCAL(oparg);
|
||||
if (value == NULL) {
|
||||
goto unbound_local_error;
|
||||
|
@ -1823,6 +1823,14 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
DISPATCH();
|
||||
}
|
||||
|
||||
TARGET(LOAD_FAST) {
|
||||
PyObject *value = GETLOCAL(oparg);
|
||||
assert(value != NULL);
|
||||
Py_INCREF(value);
|
||||
PUSH(value);
|
||||
DISPATCH();
|
||||
}
|
||||
|
||||
TARGET(LOAD_CONST) {
|
||||
PREDICTED(LOAD_CONST);
|
||||
PyObject *value = GETITEM(consts, oparg);
|
||||
|
@ -1840,17 +1848,13 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
|
||||
TARGET(LOAD_FAST__LOAD_FAST) {
|
||||
PyObject *value = GETLOCAL(oparg);
|
||||
if (value == NULL) {
|
||||
goto unbound_local_error;
|
||||
}
|
||||
assert(value != NULL);
|
||||
NEXTOPARG();
|
||||
next_instr++;
|
||||
Py_INCREF(value);
|
||||
PUSH(value);
|
||||
value = GETLOCAL(oparg);
|
||||
if (value == NULL) {
|
||||
goto unbound_local_error;
|
||||
}
|
||||
assert(value != NULL);
|
||||
Py_INCREF(value);
|
||||
PUSH(value);
|
||||
NOTRACE_DISPATCH();
|
||||
|
@ -1858,9 +1862,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
|
||||
TARGET(LOAD_FAST__LOAD_CONST) {
|
||||
PyObject *value = GETLOCAL(oparg);
|
||||
if (value == NULL) {
|
||||
goto unbound_local_error;
|
||||
}
|
||||
assert(value != NULL);
|
||||
NEXTOPARG();
|
||||
next_instr++;
|
||||
Py_INCREF(value);
|
||||
|
@ -1877,9 +1879,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
NEXTOPARG();
|
||||
next_instr++;
|
||||
value = GETLOCAL(oparg);
|
||||
if (value == NULL) {
|
||||
goto unbound_local_error;
|
||||
}
|
||||
assert(value != NULL);
|
||||
Py_INCREF(value);
|
||||
PUSH(value);
|
||||
NOTRACE_DISPATCH();
|
||||
|
@ -1902,9 +1902,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
|
|||
Py_INCREF(value);
|
||||
PUSH(value);
|
||||
value = GETLOCAL(oparg);
|
||||
if (value == NULL) {
|
||||
goto unbound_local_error;
|
||||
}
|
||||
assert(value != NULL);
|
||||
Py_INCREF(value);
|
||||
PUSH(value);
|
||||
NOTRACE_DISPATCH();
|
||||
|
|
108
Python/compile.c
108
Python/compile.c
|
@ -1109,6 +1109,7 @@ stack_effect(int opcode, int oparg, int jump)
|
|||
return 1;
|
||||
|
||||
case LOAD_FAST:
|
||||
case LOAD_FAST_CHECK:
|
||||
return 1;
|
||||
case STORE_FAST:
|
||||
return -1;
|
||||
|
@ -7746,6 +7747,109 @@ assemble_jump_offsets(struct assembler *a, struct compiler *c)
|
|||
} while (extended_arg_recompile);
|
||||
}
|
||||
|
||||
|
||||
// Ensure each basicblock is only put onto the stack once.
|
||||
#define MAYBE_PUSH(B) do { \
|
||||
if ((B)->b_visited == 0) { \
|
||||
*(*stack_top)++ = (B); \
|
||||
(B)->b_visited = 1; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
static void
|
||||
scan_block_for_local(int target, basicblock *b, bool unsafe_to_start,
|
||||
basicblock ***stack_top)
|
||||
{
|
||||
bool unsafe = unsafe_to_start;
|
||||
for (int i = 0; i < b->b_iused; i++) {
|
||||
struct instr *instr = &b->b_instr[i];
|
||||
assert(instr->i_opcode != EXTENDED_ARG);
|
||||
assert(instr->i_opcode != EXTENDED_ARG_QUICK);
|
||||
assert(instr->i_opcode != LOAD_FAST__LOAD_FAST);
|
||||
assert(instr->i_opcode != STORE_FAST__LOAD_FAST);
|
||||
assert(instr->i_opcode != LOAD_CONST__LOAD_FAST);
|
||||
assert(instr->i_opcode != STORE_FAST__STORE_FAST);
|
||||
assert(instr->i_opcode != LOAD_FAST__LOAD_CONST);
|
||||
if (unsafe && instr->i_except != NULL) {
|
||||
MAYBE_PUSH(instr->i_except);
|
||||
}
|
||||
if (instr->i_oparg != target) {
|
||||
continue;
|
||||
}
|
||||
switch (instr->i_opcode) {
|
||||
case LOAD_FAST_CHECK:
|
||||
// if this doesn't raise, then var is defined
|
||||
unsafe = false;
|
||||
break;
|
||||
case LOAD_FAST:
|
||||
if (unsafe) {
|
||||
instr->i_opcode = LOAD_FAST_CHECK;
|
||||
}
|
||||
unsafe = false;
|
||||
break;
|
||||
case STORE_FAST:
|
||||
unsafe = false;
|
||||
break;
|
||||
case DELETE_FAST:
|
||||
unsafe = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (unsafe) {
|
||||
// unsafe at end of this block,
|
||||
// so unsafe at start of next blocks
|
||||
if (b->b_next && !b->b_nofallthrough) {
|
||||
MAYBE_PUSH(b->b_next);
|
||||
}
|
||||
if (b->b_iused > 0) {
|
||||
struct instr *last = &b->b_instr[b->b_iused-1];
|
||||
if (is_jump(last)) {
|
||||
assert(last->i_target != NULL);
|
||||
MAYBE_PUSH(last->i_target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#undef MAYBE_PUSH
|
||||
|
||||
static int
|
||||
add_checks_for_loads_of_unknown_variables(struct assembler *a,
|
||||
struct compiler *c)
|
||||
{
|
||||
basicblock **stack = make_cfg_traversal_stack(a->a_entry);
|
||||
if (stack == NULL) {
|
||||
return -1;
|
||||
}
|
||||
Py_ssize_t nparams = PyList_GET_SIZE(c->u->u_ste->ste_varnames);
|
||||
int nlocals = (int)PyDict_GET_SIZE(c->u->u_varnames);
|
||||
for (int target = 0; target < nlocals; target++) {
|
||||
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
|
||||
b->b_visited = 0;
|
||||
}
|
||||
basicblock **stack_top = stack;
|
||||
|
||||
// First pass: find the relevant DFS starting points:
|
||||
// the places where "being uninitialized" originates,
|
||||
// which are the entry block and any DELETE_FAST statements.
|
||||
if (target >= nparams) {
|
||||
// only non-parameter locals start out uninitialized.
|
||||
*(stack_top++) = a->a_entry;
|
||||
a->a_entry->b_visited = 1;
|
||||
}
|
||||
for (basicblock *b = a->a_entry; b != NULL; b = b->b_next) {
|
||||
scan_block_for_local(target, b, false, &stack_top);
|
||||
}
|
||||
|
||||
// Second pass: Depth-first search to propagate uncertainty
|
||||
while (stack_top > stack) {
|
||||
basicblock *b = *--stack_top;
|
||||
scan_block_for_local(target, b, true, &stack_top);
|
||||
}
|
||||
}
|
||||
PyMem_Free(stack);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
dict_keys_inorder(PyObject *dict, Py_ssize_t offset)
|
||||
{
|
||||
|
@ -8385,6 +8489,10 @@ assemble(struct compiler *c, int addNone)
|
|||
/* Order of basic blocks must have been determined by now */
|
||||
normalize_jumps(&a);
|
||||
|
||||
if (add_checks_for_loads_of_unknown_variables(&a, c) < 0) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
/* Can't modify the bytecode after computing jump offsets. */
|
||||
assemble_jump_offsets(&a, c);
|
||||
|
||||
|
|
16
Python/opcode_targets.h
generated
16
Python/opcode_targets.h
generated
|
@ -126,7 +126,7 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_LOAD_FAST,
|
||||
&&TARGET_STORE_FAST,
|
||||
&&TARGET_DELETE_FAST,
|
||||
&&TARGET_LOAD_METHOD_WITH_VALUES,
|
||||
&&TARGET_LOAD_FAST_CHECK,
|
||||
&&TARGET_POP_JUMP_FORWARD_IF_NOT_NONE,
|
||||
&&TARGET_POP_JUMP_FORWARD_IF_NONE,
|
||||
&&TARGET_RAISE_VARARGS,
|
||||
|
@ -140,9 +140,9 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_STORE_DEREF,
|
||||
&&TARGET_DELETE_DEREF,
|
||||
&&TARGET_JUMP_BACKWARD,
|
||||
&&TARGET_RESUME_QUICK,
|
||||
&&TARGET_LOAD_METHOD_WITH_VALUES,
|
||||
&&TARGET_CALL_FUNCTION_EX,
|
||||
&&TARGET_STORE_ATTR_ADAPTIVE,
|
||||
&&TARGET_RESUME_QUICK,
|
||||
&&TARGET_EXTENDED_ARG,
|
||||
&&TARGET_LIST_APPEND,
|
||||
&&TARGET_SET_ADD,
|
||||
|
@ -152,30 +152,31 @@ static void *opcode_targets[256] = {
|
|||
&&TARGET_YIELD_VALUE,
|
||||
&&TARGET_RESUME,
|
||||
&&TARGET_MATCH_CLASS,
|
||||
&&TARGET_STORE_ATTR_ADAPTIVE,
|
||||
&&TARGET_STORE_ATTR_INSTANCE_VALUE,
|
||||
&&TARGET_STORE_ATTR_SLOT,
|
||||
&&TARGET_FORMAT_VALUE,
|
||||
&&TARGET_BUILD_CONST_KEY_MAP,
|
||||
&&TARGET_BUILD_STRING,
|
||||
&&TARGET_STORE_ATTR_SLOT,
|
||||
&&TARGET_STORE_ATTR_WITH_HINT,
|
||||
&&TARGET_STORE_FAST__LOAD_FAST,
|
||||
&&TARGET_LOAD_METHOD,
|
||||
&&TARGET_STORE_FAST__STORE_FAST,
|
||||
&&TARGET_STORE_FAST__LOAD_FAST,
|
||||
&&TARGET_LIST_EXTEND,
|
||||
&&TARGET_SET_UPDATE,
|
||||
&&TARGET_DICT_MERGE,
|
||||
&&TARGET_DICT_UPDATE,
|
||||
&&TARGET_STORE_FAST__STORE_FAST,
|
||||
&&TARGET_STORE_SUBSCR_ADAPTIVE,
|
||||
&&TARGET_STORE_SUBSCR_DICT,
|
||||
&&TARGET_STORE_SUBSCR_LIST_INT,
|
||||
&&TARGET_UNPACK_SEQUENCE_ADAPTIVE,
|
||||
&&TARGET_UNPACK_SEQUENCE_LIST,
|
||||
&&TARGET_CALL,
|
||||
&&TARGET_KW_NAMES,
|
||||
&&TARGET_POP_JUMP_BACKWARD_IF_NOT_NONE,
|
||||
&&TARGET_POP_JUMP_BACKWARD_IF_NONE,
|
||||
&&TARGET_POP_JUMP_BACKWARD_IF_FALSE,
|
||||
&&TARGET_POP_JUMP_BACKWARD_IF_TRUE,
|
||||
&&TARGET_UNPACK_SEQUENCE_LIST,
|
||||
&&TARGET_UNPACK_SEQUENCE_TUPLE,
|
||||
&&TARGET_UNPACK_SEQUENCE_TWO_TUPLE,
|
||||
&&_unknown_opcode,
|
||||
|
@ -253,6 +254,5 @@ static void *opcode_targets[256] = {
|
|||
&&_unknown_opcode,
|
||||
&&_unknown_opcode,
|
||||
&&_unknown_opcode,
|
||||
&&_unknown_opcode,
|
||||
&&TARGET_DO_TRACING
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue