Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0114bb7
Add frame support functions
Fidget-Spinner Feb 20, 2024
d210b5d
Abstract interp analysis done
Fidget-Spinner Feb 20, 2024
f165138
Add frame inlining heuristics
Fidget-Spinner Feb 20, 2024
7551b65
inlining decision pass
Fidget-Spinner Feb 21, 2024
683927d
add inline markers
Fidget-Spinner Feb 21, 2024
4100b61
more analysis work
Fidget-Spinner Feb 21, 2024
11262ea
propagate non inlineable
Fidget-Spinner Feb 21, 2024
e4a68e3
Merge remote-tracking branch 'upstream/main' into tier2_inliner
Fidget-Spinner Feb 21, 2024
7856988
rewrite loads/store, remove replicates for load and store
Fidget-Spinner Feb 21, 2024
7b69a62
frame reconstruction
Fidget-Spinner Feb 21, 2024
e1ee2ad
fix a bunch of bugs in the abstract interp
Fidget-Spinner Feb 21, 2024
7a12a7b
Simplify
Fidget-Spinner Mar 3, 2024
1791803
Merge remote-tracking branch 'upstream/main' into tier2_inliner_redux
Fidget-Spinner Mar 3, 2024
9734d90
Fix main merge problems
Fidget-Spinner Mar 3, 2024
f0274cb
fix methods
Fidget-Spinner Mar 3, 2024
2eff546
cleanup more
Fidget-Spinner Mar 3, 2024
1322578
make tests pass
Fidget-Spinner Mar 3, 2024
16efbe0
📜🤖 Added by blurb_it.
blurb-it[bot] Mar 3, 2024
2e65b86
remove false check
Fidget-Spinner Mar 3, 2024
f74c315
Merge branch 'tier2_inliner_redux' of github.com:Fidget-Spinner/cpyth…
Fidget-Spinner Mar 3, 2024
df20f69
Merge remote-tracking branch 'upstream/main' into tier2_inliner_redux
Fidget-Spinner Mar 3, 2024
24b127f
fix failing tests
Fidget-Spinner Mar 4, 2024
b04215f
Merge remote-tracking branch 'upstream/main' into tier2_inliner_redux
Fidget-Spinner Mar 4, 2024
64788ed
Merge commit '23db9c62272' into kenjin_tier2_inliner_redux_pristine
gvanrossum Mar 6, 2024
dd78dd4
Fix minor buglet in _PyFrame_ConvertToTier2
gvanrossum Mar 6, 2024
983dc54
Tentative test_sys.py fix for Windows
gvanrossum Mar 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
frame reconstruction
  • Loading branch information
Fidget-Spinner committed Feb 21, 2024
commit 7b69a6274598a1a2d6b706f796c33517353769b9
7 changes: 4 additions & 3 deletions Include/internal/pycore_frame.h
Original file line number Diff line number Diff line change
Expand Up @@ -289,13 +289,14 @@ static inline int
_PyFrame_ConvertToTier2(PyThreadState *tstate, _PyInterpreterFrame *frame,
int localsplus_grow)
{
if (frame->owner != FRAME_OWNED_BY_THREAD) {
return 1;
}
assert(localsplus_grow > 0);
// Already grown previously
if (frame->tier2_extra_size >= localsplus_grow) {
return 0;
}
if (frame->owner != FRAME_OWNED_BY_THREAD) {
return 1;
}
if (!_PyThreadState_HasStackSpace(tstate, localsplus_grow)) {
return 1;
}
Expand Down
2 changes: 1 addition & 1 deletion Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ extern "C" {
#include "pycore_uop_ids.h"

// This is the length of the trace we project initially.
#define UOP_MAX_TRACE_LENGTH 512
#define UOP_MAX_TRACE_LENGTH 1024

#define TRACE_STACK_SIZE 5

Expand Down
25 changes: 13 additions & 12 deletions Include/internal/pycore_uop_ids.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Include/internal/pycore_uop_metadata.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -887,5 +887,24 @@ def testfunc(n):
self.assertLessEqual(len(guard_both_float_count), 1)
self.assertIn("_COMPARE_OP_STR", uops)

def test_function_inlining(self):
def testfunc(n):
a = 1
for _ in range(n):
x = foo(a, a)
return x

res, ex = self._run_with_optimizer(testfunc, 32)
self.assertTrue(res)
self.assertIsNotNone(ex)
uops = get_opnames(ex)
self.assertLessEqual(len(guard_both_float_count), 1)
self.assertIn("_COMPARE_OP_STR", uops)


def foo(x, y):
return x + y


if __name__ == "__main__":
unittest.main()
8 changes: 6 additions & 2 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -4212,15 +4212,15 @@ dummy_func(
// Inlining postlude
op(_POST_INLINE, (reconstructer/4 -- retval)) {
// clear the locals
PyObject **end = frame->localsplus + oparg;
PyObject *ret = PEEK(1);
stack_pointer--;
PyObject **end = stack_pointer - oparg;
while (stack_pointer > end) {
Py_CLEAR(stack_pointer[-1]);
stack_pointer--;
}
retval = ret;
frame->frame_reconstruction_inst = ((int64_t)reconstructer == -1
frame->frame_reconstruction_inst = ((int64_t)reconstructer == 0
? NULL
: current_executor->trace + (int64_t)reconstructer);
CHECK_EVAL_BREAKER();
Expand All @@ -4230,6 +4230,10 @@ dummy_func(
DEOPT_IF(_PyFrame_ConvertToTier2(tstate, frame, oparg));
}

// Dummy instruction to indicate this is frame reconstruction data.
op(_RECONSTRUCT_FRAME_INFO, (--)) {
}

// Sentinel for true end of trace.
op(_TRUE_END, (--)) {}
// END BYTECODES //
Expand Down
128 changes: 126 additions & 2 deletions Python/ceval.c
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,8 @@ _PyEvalFramePushAndInit(PyThreadState *tstate, PyFunctionObject *func,
static _PyInterpreterFrame *
_PyEvalFramePushAndInit_Ex(PyThreadState *tstate, PyFunctionObject *func,
PyObject *locals, Py_ssize_t nargs, PyObject *callargs, PyObject *kwargs);
static _PyInterpreterFrame *
_PyEvalFrame_ReconstructTier2Frame(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject ***stackptr_ptr);

#ifdef HAVE_ERRNO_H
#include <errno.h>
Expand Down Expand Up @@ -1071,6 +1073,10 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int
}
#endif
OPT_HIST(trace_uop_execution_counter, trace_run_length_hist);
frame = _PyEvalFrame_ReconstructTier2Frame(tstate, frame, &stack_pointer);
if (frame == NULL) {
goto resume_with_error;
}
frame->return_offset = 0; // Don't leave this random
_PyFrame_SetStackPointer(frame, stack_pointer);
Py_DECREF(current_executor);
Expand All @@ -1079,6 +1085,11 @@ _PyEval_EvalFrameDefault(PyThreadState *tstate, _PyInterpreterFrame *frame, int

// Jump here from DEOPT_IF()
deoptimize:
frame = _PyEvalFrame_ReconstructTier2Frame(tstate, frame, &stack_pointer);
// Unrecoverable memory error.
if (frame == NULL) {
goto error_tier_two;
}
next_instr = next_uop[-1].target + _PyCode_CODE(_PyFrame_GetCode(frame));
#ifdef Py_DEBUG
if (lltrace >= 2) {
Expand Down Expand Up @@ -1671,8 +1682,9 @@ clear_thread_frame(PyThreadState *tstate, _PyInterpreterFrame * frame)
assert(frame->owner == FRAME_OWNED_BY_THREAD);
// Make sure that this is, indeed, the top frame. We can't check this in
// _PyThreadState_PopFrame, since f_code is already cleared at that point:
assert((PyObject **)frame + _PyFrame_GetCode(frame)->co_framesize ==
tstate->datastack_top);
// This doesn't apply to tier 2 frames.
assert(frame->tier2_extra_size == 0 ? (PyObject **)frame + _PyFrame_GetCode(frame)->co_framesize ==
tstate->datastack_top : 1);
tstate->c_recursion_remaining--;
assert(frame->frame_obj == NULL || frame->frame_obj->f_frame == frame);
_PyFrame_ClearExceptCode(frame);
Expand Down Expand Up @@ -1786,6 +1798,118 @@ _PyEvalFramePushAndInit_Ex(PyThreadState *tstate, PyFunctionObject *func,
return NULL;
}

// Tells the current frame how to reconstruct truly inlined function frames.
// See optimizer_analysis.c for what each field represents.
static _PyInterpreterFrame *
_PyEvalFrame_ReconstructTier2Frame(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject ***stackptr_ptr)
{
// Does not need reconstruction.
if (frame->frame_reconstruction_inst == NULL) {
return frame;
}
#ifdef LLTRACE
printf("pre-reconstruction stack: \n");
dump_stack(frame, *stackptr_ptr);
#endif
_PyInterpreterFrame *prev_frame = frame;
_PyInterpreterFrame *recentmost_frame = frame;
_PyUOpInstruction *curr = frame->frame_reconstruction_inst;
int opcode = curr->opcode;
while (opcode == _RECONSTRUCT_FRAME_INFO) {
// Hit the root frame.
if ((curr+1)->opcode != _RECONSTRUCT_FRAME_INFO) {
break;
}
#ifdef LLTRACE
printf("reconstructing frame... \n");
#endif
PyCodeObject* code = (PyCodeObject *)(uintptr_t)curr->operand;
assert(PyCode_Check(code));
assert((curr+1)->opcode == _RECONSTRUCT_FRAME_INFO);
assert(PyFunction_Check((PyObject*)(uintptr_t)(curr+1)->operand));
assert((curr+2)->opcode == _SAVE_RETURN_OFFSET);

// We must retrieve a cached function and code object because the user might have
// modified them since execution. Thus, to remain consistent and give the appearance
// that the frame has existed since before modification, we use a manual code object
// rather than obtaining the function's.
PyFunctionObject *callable = (PyFunctionObject *)(uintptr_t)((curr+1)->operand);
int code_flags = ((PyCodeObject*)code)->co_flags;
PyObject *locals = code_flags & CO_OPTIMIZED ? NULL : Py_NewRef(PyFunction_GET_GLOBALS(callable));

_PyInterpreterFrame *new_frame = _PyThreadState_PushFrame(tstate, code->co_framesize);
if (new_frame == NULL) {
goto fail;
}

// TODO CONSUME callable from the stack to deal with refleak.
_PyFrame_Initialize(new_frame, (PyFunctionObject*)Py_NewRef(callable),
locals, (PyCodeObject *)code,
((PyCodeObject *)code)->co_nlocalsplus);
new_frame->previous = prev_frame;
new_frame->return_offset = (curr+2)->oparg;
new_frame->instr_ptr = _PyCode_CODE(code) + (int)(curr+2)->operand;
prev_frame = new_frame;
// Copy over locals, stack and friends.
#ifdef LLTRACE
printf("copying over stack with offset %d: , locals count: %d, stacksize: %d\n", curr->oparg, code->co_nlocalsplus, code->co_stacksize);
dump_stack(frame, frame->localsplus + curr->oparg);
#endif
int total_len = (code->co_nlocalsplus + code->co_stacksize);
memcpy(new_frame->localsplus, frame->localsplus + curr->oparg,
sizeof(PyObject *) * total_len);

#ifdef LLTRACE
printf("setting stacktop: %d + co_nlocalsplus\n", (curr+1)->oparg);
#endif
// Finally, set the stack pointer
new_frame->stacktop = _PyFrame_GetCode(new_frame)->co_nlocalsplus + (curr+1)->oparg;
assert(new_frame->stacktop >= 0 || (int)(curr+1)->oparg < 0);

//#ifdef LLTRACE
// if (!(((int16_t)(curr+2)->oparg) < 0)) {
// printf("the new frame %p has stack entries %d: \n", new_frame, (curr+2)->oparg);
// dump_stack(new_frame, &(new_frame->localsplus[new_frame->stacktop]));
// }
//#endif
recentmost_frame = new_frame;
curr+=3;
}
PyObject **curr_stacklevel = *stackptr_ptr;
// Recentmost frame stack pointer is set by the current level.
int recentmost_stackentries = (int)(curr_stacklevel - (frame->localsplus + curr->oparg + (_PyFrame_GetCode(recentmost_frame)->co_nlocalsplus)));
*stackptr_ptr = recentmost_frame->localsplus + (_PyFrame_GetCode(recentmost_frame)->co_nlocalsplus) + recentmost_stackentries;
#ifdef LLTRACE
printf("restoring offset %d\n", (int)(frame->instr_ptr - (_PyCode_CODE(_PyFrame_GetCode(frame)))));
#endif
recentmost_frame->instr_ptr = (_PyCode_CODE(_PyFrame_GetCode(recentmost_frame))) + (frame->instr_ptr - (_PyCode_CODE(_PyFrame_GetCode(frame))));
recentmost_frame->return_offset = -1;
recentmost_frame->stacktop = (*stackptr_ptr - recentmost_frame->localsplus);
// Set root frame stack pointer.
assert(curr->opcode == _RECONSTRUCT_FRAME_INFO);
assert((curr+1)->opcode == _SAVE_RETURN_OFFSET);

assert(curr->oparg >= 0);
frame->stacktop = _PyFrame_GetCode(frame)->co_nlocalsplus + curr->oparg;
frame->return_offset = (curr+1)->oparg;
frame->instr_ptr = _PyCode_CODE(_PyFrame_GetCode(frame)) + (int)(curr+1)->operand;
frame->f_names = Py_NewRef(_PyFrame_GetCode(frame)->co_names);
tstate->current_frame = recentmost_frame;
frame->frame_reconstruction_inst = NULL;

#ifdef LLTRACE
printf("after reconstruction root stack, with n_stackentries %d: \n", curr->oparg);
dump_stack(frame, &(frame->localsplus[frame->stacktop]));
printf("after reconstruction topmost stack, with n_stackentries %d: \n", recentmost_stackentries);
dump_stack(recentmost_frame, *stackptr_ptr);
#endif
return recentmost_frame;
fail:
PyErr_NoMemory();
return NULL;
}


PyObject *
_PyEval_Vector(PyThreadState *tstate, PyFunctionObject *func,
PyObject *locals,
Expand Down
8 changes: 6 additions & 2 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading