Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ typedef struct _PyExitData {
typedef struct _PyExecutorObject {
PyObject_VAR_HEAD
const _PyUOpInstruction *trace;
PyObject *constant_pool;
_PyVMData vm_data; /* Used by the VM, but opaque to the optimizer */
uint32_t exit_count;
uint32_t code_size;
Expand Down Expand Up @@ -98,7 +99,7 @@ PyAPI_FUNC(void) _Py_Executors_InvalidateCold(PyInterpreterState *interp);

int _Py_uop_analyze_and_optimize(_PyInterpreterFrame *frame,
_PyUOpInstruction *trace, int trace_len, int curr_stackentries,
_PyBloomFilter *dependencies);
_PyBloomFilter *dependencies, PyObject **constant_pool_ptr);

extern PyTypeObject _PyUOpExecutor_Type;

Expand Down Expand Up @@ -278,6 +279,7 @@ typedef struct _JitOptContext {
bool contradiction;
// Has the builtins dict been watched?
bool builtins_watched;
PyObject *constant_pool;
// The current "executing" frame.
_Py_UOpsAbstractFrame *frame;
_Py_UOpsAbstractFrame frames[MAX_ABSTRACT_FRAME_DEPTH];
Expand Down Expand Up @@ -324,8 +326,9 @@ extern bool _Py_uop_sym_is_compact_int(JitOptRef sym);
extern JitOptRef _Py_uop_sym_new_compact_int(JitOptContext *ctx);
extern void _Py_uop_sym_set_compact_int(JitOptContext *ctx, JitOptRef sym);

extern void _Py_uop_abstractcontext_init(JitOptContext *ctx);
extern int _Py_uop_abstractcontext_init(JitOptContext *ctx);
extern void _Py_uop_abstractcontext_fini(JitOptContext *ctx);
extern int _Py_uop_promote_to_constant_pool(JitOptContext *ctx, PyObject *obj);

extern _Py_UOpsAbstractFrame *_Py_uop_frame_new(
JitOptContext *ctx,
Expand Down
3 changes: 1 addition & 2 deletions Lib/test/test_capi/test_opt.py
Original file line number Diff line number Diff line change
Expand Up @@ -2594,8 +2594,7 @@ def testfunc(n):
self.assertIsNotNone(ex)
uops = get_opnames(ex)

# For now... until we constant propagate it away.
self.assertIn("_BINARY_OP", uops)
self.assertIn("_LOAD_CONST_INLINE_BORROW", uops)

def test_jitted_code_sees_changed_globals(self):
"Issue 136154: Check that jitted code spots the change in the globals"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add a constant pool to all JIT executors and allow promotion of constants to the pool. Patch by Ken Jin. Implementation in CPython inspired by PyPy/RPython.
20 changes: 13 additions & 7 deletions Python/optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ insert_executor(PyCodeObject *code, _Py_CODEUNIT *instr, int index, _PyExecutorO
}

static _PyExecutorObject *
make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFilter *dependencies);
make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFilter *dependencies, PyObject *constant_pool);

static int
uop_optimize(_PyInterpreterFrame *frame, _Py_CODEUNIT *instr,
Expand Down Expand Up @@ -388,6 +388,7 @@ static int
executor_traverse(PyObject *o, visitproc visit, void *arg)
{
_PyExecutorObject *executor = _PyExecutorObject_CAST(o);
Py_VISIT(executor->constant_pool);
for (uint32_t i = 0; i < executor->exit_count; i++) {
Py_VISIT(executor->exits[i].executor);
}
Expand Down Expand Up @@ -1115,13 +1116,15 @@ prepare_for_execution(_PyUOpInstruction *buffer, int length)
/* Executor side exits */

static _PyExecutorObject *
allocate_executor(int exit_count, int length)
allocate_executor(int exit_count, int length, PyObject *constant_pool)
{
int size = exit_count*sizeof(_PyExitData) + length*sizeof(_PyUOpInstruction);
_PyExecutorObject *res = PyObject_GC_NewVar(_PyExecutorObject, &_PyUOpExecutor_Type, size);
if (res == NULL) {
return NULL;
}
// Transfer ownership
res->constant_pool = constant_pool;
res->trace = (_PyUOpInstruction *)(res->exits + exit_count);
res->code_size = length;
res->exit_count = exit_count;
Expand Down Expand Up @@ -1196,10 +1199,10 @@ sanity_check(_PyExecutorObject *executor)
* and not a NOP.
*/
static _PyExecutorObject *
make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFilter *dependencies)
make_executor_from_uops(_PyUOpInstruction *buffer, int length, const _PyBloomFilter *dependencies, PyObject *constant_pool)
{
int exit_count = count_exits(buffer, length);
_PyExecutorObject *executor = allocate_executor(exit_count, length);
_PyExecutorObject *executor = allocate_executor(exit_count, length, constant_pool);
if (executor == NULL) {
return NULL;
}
Expand Down Expand Up @@ -1293,6 +1296,7 @@ uop_optimize(
_PyBloomFilter dependencies;
_Py_BloomFilter_Init(&dependencies);
PyInterpreterState *interp = _PyInterpreterState_GET();
PyObject *constant_pool = NULL;
if (interp->jit_uop_buffer == NULL) {
interp->jit_uop_buffer = (_PyUOpInstruction *)_PyObject_VirtualAlloc(UOP_BUFFER_SIZE);
if (interp->jit_uop_buffer == NULL) {
Expand All @@ -1316,7 +1320,7 @@ uop_optimize(
if (!is_noopt) {
length = _Py_uop_analyze_and_optimize(frame, buffer,
length,
curr_stackentries, &dependencies);
curr_stackentries, &dependencies, &constant_pool);
if (length <= 0) {
return length;
}
Expand All @@ -1339,7 +1343,7 @@ uop_optimize(
OPT_HIST(effective_trace_length(buffer, length), optimized_trace_length_hist);
length = prepare_for_execution(buffer, length);
assert(length <= UOP_MAX_TRACE_LENGTH);
_PyExecutorObject *executor = make_executor_from_uops(buffer, length, &dependencies);
_PyExecutorObject *executor = make_executor_from_uops(buffer, length, &dependencies, constant_pool);
if (executor == NULL) {
return -1;
}
Expand Down Expand Up @@ -1512,7 +1516,7 @@ _PyExecutor_GetColdExecutor(void)
if (interp->cold_executor != NULL) {
return interp->cold_executor;
}
_PyExecutorObject *cold = allocate_executor(0, 1);
_PyExecutorObject *cold = allocate_executor(0, 1, NULL);
if (cold == NULL) {
Py_FatalError("Cannot allocate core JIT code");
}
Expand Down Expand Up @@ -1575,6 +1579,8 @@ executor_clear(PyObject *op)
unlink_executor(executor);
executor->vm_data.valid = 0;

Py_CLEAR(executor->constant_pool);

/* It is possible for an executor to form a reference
* cycle with itself, so decref'ing a side exit could
* free the executor unless we hold a strong reference to it
Expand Down
17 changes: 13 additions & 4 deletions Python/optimizer_analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ incorrect_keys(PyObject *obj, uint32_t version)
(INST)->oparg = ARG; \
(INST)->operand0 = OPERAND;

#define PROMOTE_TO_CONSTANT_POOL _Py_uop_promote_to_constant_pool

/* Shortened forms for convenience, used in optimizer_bytecodes.c */
#define sym_is_not_null _Py_uop_sym_is_not_null
#define sym_is_const _Py_uop_sym_is_const
Expand Down Expand Up @@ -290,7 +292,8 @@ optimize_uops(
_PyUOpInstruction *trace,
int trace_len,
int curr_stacklen,
_PyBloomFilter *dependencies
_PyBloomFilter *dependencies,
PyObject **constant_pool_ptr
)
{
assert(!PyErr_Occurred());
Expand All @@ -310,9 +313,13 @@ optimize_uops(
interp->type_watchers[TYPE_WATCHER_ID] = type_watcher_callback;
}

_Py_uop_abstractcontext_init(ctx);
if (_Py_uop_abstractcontext_init(ctx)) {
return 0;
}

_Py_UOpsAbstractFrame *frame = _Py_uop_frame_new(ctx, (PyCodeObject *)func->func_code, curr_stacklen, NULL, 0);
if (frame == NULL) {
_Py_uop_abstractcontext_fini(ctx);
return 0;
}
frame->func = func;
Expand Down Expand Up @@ -367,6 +374,7 @@ optimize_uops(

/* Either reached the end or cannot optimize further, but there
* would be no benefit in retrying later */
*constant_pool_ptr = Py_NewRef(ctx->constant_pool);
_Py_uop_abstractcontext_fini(ctx);
if (first_valid_check_stack != NULL) {
assert(first_valid_check_stack->opcode == _CHECK_STACK_SPACE);
Expand Down Expand Up @@ -522,14 +530,15 @@ _Py_uop_analyze_and_optimize(
_PyUOpInstruction *buffer,
int length,
int curr_stacklen,
_PyBloomFilter *dependencies
_PyBloomFilter *dependencies,
PyObject **constant_pool_ptr
)
{
OPT_STAT_INC(optimizer_attempts);

length = optimize_uops(
_PyFrame_GetFunction(frame), buffer,
length, curr_stacklen, dependencies);
length, curr_stacklen, dependencies, constant_pool_ptr);

if (length == 0) {
return length;
Expand Down
Loading
Loading