Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
8 changes: 8 additions & 0 deletions Include/internal/pycore_optimizer.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,15 @@ typedef struct _JitOptContext {
// Arena for the symbolic types.
ty_arena t_arena;

// Arena for the slots mappings.
slots_arena s_arena;

JitOptRef *n_consumed;
JitOptRef *limit;
JitOptRef locals_and_stack[MAX_ABSTRACT_INTERP_SIZE];
_PyJitUopBuffer out_buffer;
// Index of the last escaped uop in out_buffer.
int last_escape_index;
} JitOptContext;


Expand Down Expand Up @@ -295,6 +300,9 @@ extern JitOptRef _Py_uop_sym_new_truthiness(JitOptContext *ctx, JitOptRef value,
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 JitOptRef _Py_uop_sym_new_slots_object(JitOptContext *ctx, unsigned int type_version);
extern JitOptRef _Py_uop_sym_slots_getattr(JitOptContext *ctx, JitOptRef ref, uint16_t slot_index);
extern void _Py_uop_sym_slots_setattr(JitOptContext *ctx, JitOptRef ref, uint16_t slot_index, JitOptRef value);
extern JitOptRef _Py_uop_sym_new_predicate(JitOptContext *ctx, JitOptRef lhs_ref, JitOptRef rhs_ref, JitOptPredicateKind kind);
extern void _Py_uop_sym_apply_predicate_narrowing(JitOptContext *ctx, JitOptRef sym, bool branch_is_true);

Expand Down
24 changes: 24 additions & 0 deletions Include/internal/pycore_optimizer_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ extern "C" {

#define TY_ARENA_SIZE (UOP_MAX_TRACE_LENGTH * 5)

// Maximum slots per object tracked symbolically
#define MAX_SYMBOLIC_SLOTS_SIZE 16
#define SLOTS_ARENA_SIZE (MAX_SYMBOLIC_SLOTS_SIZE * 100)
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might need to adjust this arena_size.


// Need extras for root frame and for overflow frame (see TRACE_STACK_PUSH())
#define MAX_ABSTRACT_FRAME_DEPTH (16)

Expand All @@ -41,6 +45,7 @@ typedef enum _JitSymType {
JIT_SYM_TRUTHINESS_TAG = 9,
JIT_SYM_COMPACT_INT = 10,
JIT_SYM_PREDICATE_TAG = 11,
JIT_SYM_SLOTS_TAG = 12,
} JitSymType;

typedef struct _jit_opt_known_class {
Expand Down Expand Up @@ -89,6 +94,19 @@ typedef struct {
uint8_t tag;
} JitOptCompactInt;

typedef struct {
uint16_t slot_index;
uint16_t symbol;
} JitOptDescrMapping;
Comment on lines +111 to +114
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it safe to use this for both normal objects and objects with __slots__, or do we need a separate symbol?

Basically I'm asking if it's possible for an object to both have STORE_ATTR_INSTANCE_VALUE and STORE_ATTR_SLOT, as this will cause a index collision between the slots and offset. It it safe also with STORE_ATTR_WITH_HINT, as can that can mix with STORE_ATTR_INSTANCE_VALUE?

If you think of an answer, please let me know, and we can add it as a comment in the code. otherwise, this is kind of scary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two never appear on the same object type because:
STORE_ATTR_INSTANCE_VALUE needs Py_TPFLAGS_MANAGED_DICT flag.
Therefore, there is no index collision between slot offsets and inline value offsets.

cpython/Python/specialize.c

Lines 665 to 669 in 5f57f69

// No descriptor, or non overriding.
if ((type->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
SPECIALIZATION_FAIL(base_op, SPEC_FAIL_ATTR_NOT_MANAGED_DICT);
return 0;
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, conflicts between STORE_ATTR_INSTANCE_VALUE and STORE_ATTR_WITH_HINT can indeed occur. Perhaps a flag could be added to the index to distinguish between the two types?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we track STORE_ATTR_WITH_HINT in this PR?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No lets ignore with hint for now.


Comment thread
cocolato marked this conversation as resolved.
typedef struct _jit_opt_slots {
uint8_t tag;
uint8_t num_slots;
uint16_t last_modified_index; // Index in out_buffer when this object was last modified
uint32_t type_version;
JitOptDescrMapping *slots;
} JitOptSlotsObject;

typedef union _jit_opt_symbol {
uint8_t tag;
JitOptKnownClass cls;
Expand All @@ -97,6 +115,7 @@ typedef union _jit_opt_symbol {
JitOptTuple tuple;
JitOptTruthiness truthiness;
JitOptCompactInt compact;
JitOptSlotsObject slots;
JitOptPredicate predicate;
} JitOptSymbol;

Expand Down Expand Up @@ -126,6 +145,11 @@ typedef struct ty_arena {
JitOptSymbol arena[TY_ARENA_SIZE];
} ty_arena;

typedef struct slots_arena {
int slots_curr_number;
int slots_max_number;
JitOptDescrMapping arena[SLOTS_ARENA_SIZE];
} slots_arena;

#ifdef __cplusplus
}
Expand Down
7 changes: 7 additions & 0 deletions Python/optimizer_analysis.c
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ add_op(JitOptContext *ctx, _PyUOpInstruction *this_instr,
#define sym_is_compact_int _Py_uop_sym_is_compact_int
#define sym_new_compact_int _Py_uop_sym_new_compact_int
#define sym_new_truthiness _Py_uop_sym_new_truthiness
#define sym_new_slots_object _Py_uop_sym_new_slots_object
#define sym_slots_getattr _Py_uop_sym_slots_getattr
#define sym_slots_setattr _Py_uop_sym_slots_setattr
#define sym_new_predicate _Py_uop_sym_new_predicate
#define sym_apply_predicate_narrowing _Py_uop_sym_apply_predicate_narrowing

Expand Down Expand Up @@ -506,6 +509,10 @@ optimize_uops(
if (ctx->out_buffer.next == out_ptr) {
*(ctx->out_buffer.next++) = *this_instr;
}
// Track escapes
if (_PyUop_Flags[out_ptr->opcode] & HAS_ESCAPES_FLAG) {
ctx->last_escape_index = uop_buffer_length(&ctx->out_buffer);
}
Copy link
Copy Markdown
Member Author

@cocolato cocolato Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fidget-Spinner If we use this method to detect escapes, then each POP_TOP following _STORE_ATTR_SLOT will cause tracked slot values invalid due to HAS_ESCAPES_FLAG. Could you please give me some guidance? Thanks!

assert(ctx->frame != NULL);
if (!CURRENT_FRAME_IS_INIT_SHIM()) {
DPRINTF(3, " stack_level %d\n", STACK_LEVEL());
Expand Down
17 changes: 11 additions & 6 deletions Python/optimizer_bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ typedef struct _Py_UOpsAbstractFrame _Py_UOpsAbstractFrame;
#define sym_new_compact_int _Py_uop_sym_new_compact_int
#define sym_is_compact_int _Py_uop_sym_is_compact_int
#define sym_new_truthiness _Py_uop_sym_new_truthiness
#define sym_new_slots_object _Py_uop_sym_new_slots_object
#define sym_slots_getattr _Py_uop_sym_slots_getattr
#define sym_slots_setattr _Py_uop_sym_slots_setattr
#define sym_new_predicate _Py_uop_sym_new_predicate
#define sym_apply_predicate_narrowing _Py_uop_sym_apply_predicate_narrowing

Expand Down Expand Up @@ -125,8 +128,7 @@ dummy_func(void) {
}

op(_STORE_ATTR_SLOT, (index/1, value, owner -- o)) {
(void)index;
(void)value;
sym_slots_setattr(ctx, owner, (uint16_t)index, value);
o = owner;
}

Expand Down Expand Up @@ -710,8 +712,7 @@ dummy_func(void) {
}

op(_LOAD_ATTR_SLOT, (index/1, owner -- attr, o)) {
attr = sym_new_not_null(ctx);
(void)index;
attr = sym_slots_getattr(ctx, owner, (uint16_t)index);
o = owner;
}

Expand Down Expand Up @@ -895,10 +896,14 @@ dummy_func(void) {
}

op(_CHECK_AND_ALLOCATE_OBJECT, (type_version/2, callable, self_or_null, args[oparg] -- callable, self_or_null, args[oparg])) {
(void)type_version;
(void)args;
callable = sym_new_not_null(ctx);
self_or_null = sym_new_not_null(ctx);
PyTypeObject *tp = _PyType_LookupByVersion(type_version);
if (tp != NULL && tp->tp_basicsize > sizeof(PyObject) && !(tp->tp_flags & Py_TPFLAGS_MANAGED_DICT)) {
self_or_null = sym_new_slots_object(ctx, type_version);
} else {
self_or_null = sym_new_not_null(ctx);
}
}

op(_CREATE_INIT_FRAME, (init, self, args[oparg] -- init_frame)) {
Expand Down
14 changes: 8 additions & 6 deletions Python/optimizer_cases.c.h

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

Loading
Loading