GH-119866: Spill the stack around escaping calls. (GH-124392)

* Spill the evaluation around escaping calls in the generated interpreter and JIT. 

* The code generator tracks live, cached values so they can be saved to memory when needed.

* Spills the stack pointer around escaping calls, so that the exact stack is visible to the cycle GC.
This commit is contained in:
Mark Shannon 2024-10-07 14:56:39 +01:00 committed by GitHub
parent cda3b5a576
commit da071fa3e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 3841 additions and 2034 deletions

File diff suppressed because it is too large Load diff

View file

@ -274,7 +274,6 @@ static void monitor_throw(PyThreadState *tstate,
_PyInterpreterFrame *frame,
_Py_CODEUNIT *instr);
static int check_args_iterable(PyThreadState *, PyObject *func, PyObject *vararg);
static int get_exception_handler(PyCodeObject *, int, int*, int*, int*);
static _PyInterpreterFrame *
_PyEvalFramePushAndInit_Ex(PyThreadState *tstate, _PyStackRef func,
@ -394,6 +393,13 @@ const _Py_SpecialMethod _Py_SpecialMethods[] = {
}
};
const size_t _Py_FunctionAttributeOffsets[] = {
[MAKE_FUNCTION_CLOSURE] = offsetof(PyFunctionObject, func_closure),
[MAKE_FUNCTION_ANNOTATIONS] = offsetof(PyFunctionObject, func_annotations),
[MAKE_FUNCTION_KWDEFAULTS] = offsetof(PyFunctionObject, func_kwdefaults),
[MAKE_FUNCTION_DEFAULTS] = offsetof(PyFunctionObject, func_defaults),
[MAKE_FUNCTION_ANNOTATE] = offsetof(PyFunctionObject, func_annotate),
};
// PEP 634: Structural Pattern Matching
@ -1036,6 +1042,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
uopcode = next_uop->opcode;
#ifdef Py_DEBUG
if (lltrace >= 3) {
dump_stack(frame, stack_pointer);
if (next_uop->opcode == _START_EXECUTOR) {
printf("%4d uop: ", 0);
}
@ -1043,8 +1050,7 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
printf("%4d uop: ", (int)(next_uop - current_executor->trace));
}
_PyUOpPrint(next_uop);
printf(" stack_level=%d\n",
(int)(stack_pointer - _PyFrame_Stackbase(frame)));
printf("\n");
}
#endif
next_uop++;
@ -2920,11 +2926,11 @@ _PyEval_CheckExceptStarTypeValid(PyThreadState *tstate, PyObject* right)
return 0;
}
static int
check_args_iterable(PyThreadState *tstate, PyObject *func, PyObject *args)
int
_Py_Check_ArgsIterable(PyThreadState *tstate, PyObject *func, PyObject *args)
{
if (Py_TYPE(args)->tp_iter == NULL && !PySequence_Check(args)) {
/* check_args_iterable() may be called with a live exception:
/* _Py_Check_ArgsIterable() may be called with a live exception:
* clear it to prevent calling _PyObject_FunctionStr() with an
* exception set. */
_PyErr_Clear(tstate);

View file

@ -108,6 +108,7 @@ do { \
/* Do interpreter dispatch accounting for tracing and instrumentation */
#define DISPATCH() \
{ \
assert(frame->stackpointer == NULL); \
NEXTOPARG(); \
PRE_DISPATCH_GOTO(); \
DISPATCH_GOTO(); \

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -115,7 +115,7 @@ static void *opcode_targets[256] = {
&&TARGET_UNPACK_EX,
&&TARGET_UNPACK_SEQUENCE,
&&TARGET_YIELD_VALUE,
&&TARGET__DO_CALL_FUNCTION_EX,
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,
&&_unknown_opcode,

View file

@ -182,7 +182,9 @@ dummy_func(void) {
res = sym_new_type(ctx, &PyFloat_Type);
}
}
res = sym_new_unknown(ctx);
else {
res = sym_new_unknown(ctx);
}
}
op(_BINARY_OP_ADD_INT, (left, right -- res)) {
@ -448,8 +450,10 @@ dummy_func(void) {
top = bottom;
}
op(_SWAP, (bottom, unused[oparg-2], top --
top, unused[oparg-2], bottom)) {
op(_SWAP, (bottom_in, unused[oparg-2], top_in --
top_out, unused[oparg-2], bottom_out)) {
bottom_out = bottom_in;
top_out = top_in;
}
op(_LOAD_ATTR_INSTANCE_VALUE, (offset/1, owner -- attr, null if (oparg & 1))) {
@ -479,9 +483,7 @@ dummy_func(void) {
op(_LOAD_ATTR, (owner -- attr, self_or_null if (oparg & 1))) {
(void)owner;
attr = sym_new_not_null(ctx);
if (oparg & 1) {
self_or_null = sym_new_unknown(ctx);
}
self_or_null = sym_new_unknown(ctx);
}
op(_LOAD_ATTR_MODULE, (index/1, owner -- attr, null if (oparg & 1))) {
@ -570,7 +572,6 @@ dummy_func(void) {
op(_INIT_CALL_PY_EXACT_ARGS, (callable, self_or_null, args[oparg] -- new_frame: _Py_UOpsAbstractFrame *)) {
int argcount = oparg;
(void)callable;
PyCodeObject *co = NULL;
@ -647,11 +648,10 @@ dummy_func(void) {
}
op(_RETURN_VALUE, (retval -- res)) {
SYNC_SP();
SAVE_STACK();
ctx->frame->stack_pointer = stack_pointer;
frame_pop(ctx);
stack_pointer = ctx->frame->stack_pointer;
res = retval;
/* Stack space handling */
assert(corresponding_check_stack == NULL);
@ -666,6 +666,8 @@ dummy_func(void) {
// might be impossible, but bailing is still safe
ctx->done = true;
}
RELOAD_STACK();
res = retval;
}
op(_RETURN_GENERATOR, ( -- res)) {

View file

@ -93,9 +93,9 @@
}
case _END_SEND: {
_Py_UopsSymbol *value;
value = sym_new_not_null(ctx);
stack_pointer[-2] = value;
_Py_UopsSymbol *val;
val = sym_new_not_null(ctx);
stack_pointer[-2] = val;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
@ -630,7 +630,6 @@
ctx->frame->stack_pointer = stack_pointer;
frame_pop(ctx);
stack_pointer = ctx->frame->stack_pointer;
res = retval;
/* Stack space handling */
assert(corresponding_check_stack == NULL);
assert(co != NULL);
@ -643,6 +642,7 @@
// might be impossible, but bailing is still safe
ctx->done = true;
}
res = retval;
stack_pointer[0] = res;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
@ -832,9 +832,7 @@
_Py_UopsSymbol **res;
_Py_UopsSymbol *null = NULL;
res = &stack_pointer[0];
for (int _i = 1; --_i >= 0;) {
res[_i] = sym_new_not_null(ctx);
}
res[0] = sym_new_not_null(ctx);
null = sym_new_null(ctx);
if (oparg & 1) stack_pointer[1] = null;
stack_pointer += 1 + (oparg & 1);
@ -1021,9 +1019,7 @@
owner = stack_pointer[-1];
(void)owner;
attr = sym_new_not_null(ctx);
if (oparg & 1) {
self_or_null = sym_new_unknown(ctx);
}
self_or_null = sym_new_unknown(ctx);
stack_pointer[-1] = attr;
if (oparg & 1) stack_pointer[0] = self_or_null;
stack_pointer += (oparg & 1);
@ -1114,11 +1110,17 @@
PyModuleObject *mod = (PyModuleObject *)sym_get_const(owner);
assert(PyModule_CheckExact(mod));
PyObject *dict = mod->md_dict;
stack_pointer[-1] = attr;
if (oparg & 1) stack_pointer[0] = null;
stack_pointer += (oparg & 1);
assert(WITHIN_STACK_BOUNDS());
PyObject *res = convert_global_to_const(this_instr, dict);
if (res != NULL) {
this_instr[-1].opcode = _POP_TOP;
attr = sym_new_const(ctx, res);
}
stack_pointer += -(oparg & 1);
assert(WITHIN_STACK_BOUNDS());
}
if (attr == NULL) {
/* No conversion made. We don't know what `attr` is. */
@ -1239,7 +1241,11 @@
res = sym_new_type(ctx, &PyBool_Type);
}
else {
stack_pointer += -2;
assert(WITHIN_STACK_BOUNDS());
res = _Py_uop_sym_new_not_null(ctx);
stack_pointer += 2;
assert(WITHIN_STACK_BOUNDS());
}
stack_pointer[-2] = res;
stack_pointer += -1;
@ -1659,12 +1665,13 @@
/* _MONITOR_CALL is not a viable micro-op for tier 2 */
case _PY_FRAME_GENERAL: {
_Py_UopsSymbol **args;
_Py_UopsSymbol *self_or_null;
_Py_UopsSymbol *callable;
_Py_UOpsAbstractFrame *new_frame;
self_or_null = stack_pointer[-1 - oparg];
callable = stack_pointer[-2 - oparg];
stack_pointer += -2 - oparg;
assert(WITHIN_STACK_BOUNDS());
(void)(self_or_null);
(void)(callable);
PyCodeObject *co = NULL;
@ -1675,8 +1682,8 @@
break;
}
new_frame = frame_new(ctx, co, 0, NULL, 0);
stack_pointer[-2 - oparg] = (_Py_UopsSymbol *)new_frame;
stack_pointer += -1 - oparg;
stack_pointer[0] = (_Py_UopsSymbol *)new_frame;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
}
@ -1690,14 +1697,12 @@
}
case _EXPAND_METHOD: {
_Py_UopsSymbol *method;
_Py_UopsSymbol **method;
_Py_UopsSymbol **self;
method = &stack_pointer[-2 - oparg];
self = &stack_pointer[-1 - oparg];
method = sym_new_not_null(ctx);
for (int _i = 1; --_i >= 0;) {
self[_i] = sym_new_not_null(ctx);
}
stack_pointer[-2 - oparg] = method;
method[0] = sym_new_not_null(ctx);
self[0] = sym_new_not_null(ctx);
break;
}
@ -1774,6 +1779,8 @@
(void)callable;
PyCodeObject *co = NULL;
assert((this_instr + 2)->opcode == _PUSH_FRAME);
stack_pointer += -2 - oparg;
assert(WITHIN_STACK_BOUNDS());
co = get_code_with_logging((this_instr + 2));
if (co == NULL) {
ctx->done = true;
@ -1791,8 +1798,8 @@
} else {
new_frame = frame_new(ctx, co, 0, NULL, 0);
}
stack_pointer[-2 - oparg] = (_Py_UopsSymbol *)new_frame;
stack_pointer += -1 - oparg;
stack_pointer[0] = (_Py_UopsSymbol *)new_frame;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
}
@ -1825,9 +1832,11 @@
if (first_valid_check_stack == NULL) {
first_valid_check_stack = corresponding_check_stack;
}
else if (corresponding_check_stack) {
// delete all but the first valid _CHECK_STACK_SPACE
corresponding_check_stack->opcode = _NOP;
else {
if (corresponding_check_stack) {
// delete all but the first valid _CHECK_STACK_SPACE
corresponding_check_stack->opcode = _NOP;
}
}
corresponding_check_stack = NULL;
break;
@ -2005,6 +2014,24 @@
/* _INSTRUMENTED_CALL_KW is not a viable micro-op for tier 2 */
case _MAYBE_EXPAND_METHOD_KW: {
_Py_UopsSymbol **func;
_Py_UopsSymbol **maybe_self;
_Py_UopsSymbol **args;
_Py_UopsSymbol *kwnames_out;
func = &stack_pointer[-3 - oparg];
maybe_self = &stack_pointer[-2 - oparg];
args = &stack_pointer[-1 - oparg];
func[0] = sym_new_not_null(ctx);
maybe_self[0] = sym_new_not_null(ctx);
for (int _i = oparg; --_i >= 0;) {
args[_i] = sym_new_not_null(ctx);
}
kwnames_out = sym_new_not_null(ctx);
stack_pointer[-1] = kwnames_out;
break;
}
/* _DO_CALL_KW is not a viable micro-op for tier 2 */
case _PY_FRAME_KW: {
@ -2038,17 +2065,12 @@
}
case _EXPAND_METHOD_KW: {
_Py_UopsSymbol *method;
_Py_UopsSymbol **method;
_Py_UopsSymbol **self;
_Py_UopsSymbol *kwnames;
method = &stack_pointer[-3 - oparg];
self = &stack_pointer[-2 - oparg];
method = sym_new_not_null(ctx);
for (int _i = 1; --_i >= 0;) {
self[_i] = sym_new_not_null(ctx);
}
kwnames = sym_new_not_null(ctx);
stack_pointer[-3 - oparg] = method;
stack_pointer[-1] = kwnames;
method[0] = sym_new_not_null(ctx);
self[0] = sym_new_not_null(ctx);
break;
}
@ -2067,7 +2089,17 @@
/* _INSTRUMENTED_CALL_FUNCTION_EX is not a viable micro-op for tier 2 */
/* __DO_CALL_FUNCTION_EX is not a viable micro-op for tier 2 */
case _MAKE_CALLARGS_A_TUPLE: {
_Py_UopsSymbol *tuple;
_Py_UopsSymbol *kwargs_out = NULL;
tuple = sym_new_not_null(ctx);
kwargs_out = sym_new_not_null(ctx);
stack_pointer[-1 - (oparg & 1)] = tuple;
if (oparg & 1) stack_pointer[-(oparg & 1)] = kwargs_out;
break;
}
/* _DO_CALL_FUNCTION_EX is not a viable micro-op for tier 2 */
case _MAKE_FUNCTION: {
_Py_UopsSymbol *func;
@ -2077,9 +2109,9 @@
}
case _SET_FUNCTION_ATTRIBUTE: {
_Py_UopsSymbol *func_st;
func_st = sym_new_not_null(ctx);
stack_pointer[-2] = func_st;
_Py_UopsSymbol *func_out;
func_out = sym_new_not_null(ctx);
stack_pointer[-2] = func_out;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
@ -2098,14 +2130,14 @@
assert(framesize > 0);
assert(framesize <= curr_space);
curr_space -= framesize;
stack_pointer[0] = res;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
co = get_code(this_instr);
if (co == NULL) {
// might be impossible, but bailing is still safe
ctx->done = true;
}
stack_pointer[0] = res;
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
break;
}
@ -2174,7 +2206,9 @@
res = sym_new_type(ctx, &PyFloat_Type);
}
}
res = sym_new_unknown(ctx);
else {
res = sym_new_unknown(ctx);
}
stack_pointer[-2] = res;
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -2182,12 +2216,16 @@
}
case _SWAP: {
_Py_UopsSymbol *top;
_Py_UopsSymbol *bottom;
top = stack_pointer[-1];
bottom = stack_pointer[-2 - (oparg-2)];
stack_pointer[-2 - (oparg-2)] = top;
stack_pointer[-1] = bottom;
_Py_UopsSymbol *top_in;
_Py_UopsSymbol *bottom_in;
_Py_UopsSymbol *top_out;
_Py_UopsSymbol *bottom_out;
top_in = stack_pointer[-1];
bottom_in = stack_pointer[-2 - (oparg-2)];
bottom_out = bottom_in;
top_out = top_in;
stack_pointer[-2 - (oparg-2)] = top_out;
stack_pointer[-1] = bottom_out;
break;
}
@ -2213,7 +2251,11 @@
if (sym_is_const(flag)) {
PyObject *value = sym_get_const(flag);
assert(value != NULL);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
eliminate_pop_guard(this_instr, value != Py_True);
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
}
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -2226,7 +2268,11 @@
if (sym_is_const(flag)) {
PyObject *value = sym_get_const(flag);
assert(value != NULL);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
eliminate_pop_guard(this_instr, value != Py_False);
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
}
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
@ -2239,14 +2285,22 @@
if (sym_is_const(flag)) {
PyObject *value = sym_get_const(flag);
assert(value != NULL);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
eliminate_pop_guard(this_instr, !Py_IsNone(value));
}
else if (sym_has_type(flag)) {
assert(!sym_matches_type(flag, &_PyNone_Type));
eliminate_pop_guard(this_instr, true);
else {
if (sym_has_type(flag)) {
assert(!sym_matches_type(flag, &_PyNone_Type));
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
eliminate_pop_guard(this_instr, true);
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
}
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
}
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
}
@ -2256,14 +2310,22 @@
if (sym_is_const(flag)) {
PyObject *value = sym_get_const(flag);
assert(value != NULL);
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
eliminate_pop_guard(this_instr, Py_IsNone(value));
}
else if (sym_has_type(flag)) {
assert(!sym_matches_type(flag, &_PyNone_Type));
eliminate_pop_guard(this_instr, false);
else {
if (sym_has_type(flag)) {
assert(!sym_matches_type(flag, &_PyNone_Type));
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
eliminate_pop_guard(this_instr, false);
stack_pointer += 1;
assert(WITHIN_STACK_BOUNDS());
}
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
}
stack_pointer += -1;
assert(WITHIN_STACK_BOUNDS());
break;
}