Skip to content

Commit d2fa4b2

Browse files
authored
gh-148211: decompose _POP_TOP_LOAD_CONST_INLINE(_BORROW) in JIT (GH-148230)
1 parent 461125a commit d2fa4b2

File tree

5 files changed

+62
-71
lines changed

5 files changed

+62
-71
lines changed

Lib/test/test_capi/test_opt.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1470,8 +1470,8 @@ class Bar:
14701470
opnames = list(iter_opnames(ex))
14711471
self.assertEqual(res, TIER2_THRESHOLD * 2 + 2)
14721472
call = opnames.index("_CALL_BUILTIN_FAST")
1473-
load_attr_top = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", 0, call)
1474-
load_attr_bottom = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", call)
1473+
load_attr_top = opnames.index("_LOAD_CONST_INLINE_BORROW", 0, call)
1474+
load_attr_bottom = opnames.index("_LOAD_CONST_INLINE_BORROW", call)
14751475
self.assertEqual(opnames[:load_attr_top].count("_GUARD_TYPE_VERSION"), 1)
14761476
self.assertEqual(opnames[call:load_attr_bottom].count("_CHECK_VALIDITY"), 2)
14771477

@@ -1493,8 +1493,8 @@ class Foo:
14931493
self.assertIsNotNone(ex)
14941494
self.assertEqual(res, TIER2_THRESHOLD * 2)
14951495
call = opnames.index("_CALL_BUILTIN_FAST_WITH_KEYWORDS")
1496-
load_attr_top = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", 0, call)
1497-
load_attr_bottom = opnames.index("_POP_TOP_LOAD_CONST_INLINE_BORROW", call)
1496+
load_attr_top = opnames.index("_LOAD_CONST_INLINE_BORROW", 0, call)
1497+
load_attr_bottom = opnames.index("_LOAD_CONST_INLINE_BORROW", call)
14981498
self.assertEqual(opnames[:load_attr_top].count("_GUARD_TYPE_VERSION"), 1)
14991499
self.assertEqual(opnames[call:load_attr_bottom].count("_CHECK_VALIDITY"), 2)
15001500

Python/optimizer_analysis.c

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ type_watcher_callback(PyTypeObject* type)
155155
}
156156

157157
static PyObject *
158-
convert_global_to_const(_PyUOpInstruction *inst, PyObject *obj, bool pop, bool insert)
158+
convert_global_to_const(_PyUOpInstruction *inst, PyObject *obj, bool insert)
159159
{
160160
assert(inst->opcode == _LOAD_GLOBAL_MODULE || inst->opcode == _LOAD_GLOBAL_BUILTINS || inst->opcode == _LOAD_ATTR_MODULE);
161161
assert(PyDict_CheckExact(obj));
@@ -183,9 +183,9 @@ convert_global_to_const(_PyUOpInstruction *inst, PyObject *obj, bool pop, bool i
183183
}
184184
} else {
185185
if (_Py_IsImmortal(res)) {
186-
inst->opcode = pop ? _POP_TOP_LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE_BORROW;
186+
inst->opcode = _LOAD_CONST_INLINE_BORROW;
187187
} else {
188-
inst->opcode = pop ? _POP_TOP_LOAD_CONST_INLINE : _LOAD_CONST_INLINE;
188+
inst->opcode = _LOAD_CONST_INLINE;
189189
}
190190
if (inst->oparg & 1) {
191191
assert(inst[1].opcode == _PUSH_NULL_CONDITIONAL);
@@ -340,10 +340,12 @@ optimize_to_bool(
340340
int truthiness = sym_truthiness(ctx, value);
341341
if (truthiness >= 0) {
342342
PyObject *load = truthiness ? Py_True : Py_False;
343-
int opcode = insert_mode ?
344-
_INSERT_1_LOAD_CONST_INLINE_BORROW :
345-
_POP_TOP_LOAD_CONST_INLINE_BORROW;
346-
ADD_OP(opcode, 0, (uintptr_t)load);
343+
if (insert_mode) {
344+
ADD_OP(_INSERT_1_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load);
345+
} else {
346+
ADD_OP(_POP_TOP, 0, 0);
347+
ADD_OP(_LOAD_CONST_INLINE_BORROW, 0, (uintptr_t)load);
348+
}
347349
*result_ptr = sym_new_const(ctx, load);
348350
return 1;
349351
}
@@ -389,19 +391,22 @@ eliminate_pop_guard(_PyUOpInstruction *this_instr, JitOptContext *ctx, bool exit
389391

390392
static JitOptRef
391393
lookup_attr(JitOptContext *ctx, _PyBloomFilter *dependencies, _PyUOpInstruction *this_instr,
392-
PyTypeObject *type, PyObject *name, uint16_t immortal,
393-
uint16_t mortal)
394+
PyTypeObject *type, PyObject *name, bool pop)
394395
{
395396
// The cached value may be dead, so we need to do the lookup again... :(
396397
if (type && PyType_Check(type)) {
397398
PyObject *lookup = _PyType_Lookup(type, name);
398399
if (lookup) {
399-
int opcode = mortal;
400-
// if the object is immortal or the type is immutable, borrowing is safe
401-
if (_Py_IsImmortal(lookup) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE)) {
402-
opcode = immortal;
400+
bool immortal = _Py_IsImmortal(lookup) || (type->tp_flags & Py_TPFLAGS_IMMUTABLETYPE);
401+
if (pop) {
402+
ADD_OP(_POP_TOP, 0, 0);
403+
ADD_OP(immortal ? _LOAD_CONST_INLINE_BORROW : _LOAD_CONST_INLINE,
404+
0, (uintptr_t)lookup);
405+
}
406+
else {
407+
ADD_OP(immortal ? _INSERT_1_LOAD_CONST_INLINE_BORROW : _INSERT_1_LOAD_CONST_INLINE,
408+
0, (uintptr_t)lookup);
403409
}
404-
ADD_OP(opcode, 0, (uintptr_t)lookup);
405410
PyType_Watch(TYPE_WATCHER_ID, (PyObject *)type);
406411
_Py_BloomFilter_Add(dependencies, type);
407412
return sym_new_const(ctx, lookup);

Python/optimizer_bytecodes.c

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -757,14 +757,6 @@ dummy_func(void) {
757757
value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
758758
}
759759

760-
op(_POP_TOP_LOAD_CONST_INLINE, (ptr/4, pop -- value)) {
761-
value = sym_new_const(ctx, ptr);
762-
}
763-
764-
op(_POP_TOP_LOAD_CONST_INLINE_BORROW, (ptr/4, pop -- value)) {
765-
value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
766-
}
767-
768760
op(_POP_CALL_LOAD_CONST_INLINE_BORROW, (ptr/4, unused, unused -- value)) {
769761
value = PyJitRef_Borrow(sym_new_const(ctx, ptr));
770762
}
@@ -845,7 +837,7 @@ dummy_func(void) {
845837
if (watched_mutations < _Py_MAX_ALLOWED_GLOBALS_MODIFICATIONS) {
846838
PyDict_Watch(GLOBALS_WATCHER_ID, dict);
847839
_Py_BloomFilter_Add(dependencies, dict);
848-
PyObject *res = convert_global_to_const(this_instr, dict, false, true);
840+
PyObject *res = convert_global_to_const(this_instr, dict, true);
849841
if (res == NULL) {
850842
attr = sym_new_not_null(ctx);
851843
}
@@ -898,35 +890,31 @@ dummy_func(void) {
898890
PyTypeObject *type = (PyTypeObject *)sym_get_const(ctx, owner);
899891
PyObject *name = get_co_name(ctx, oparg >> 1);
900892
attr = lookup_attr(ctx, dependencies, this_instr, type, name,
901-
_POP_TOP_LOAD_CONST_INLINE_BORROW,
902-
_POP_TOP_LOAD_CONST_INLINE);
893+
true);
903894
}
904895

905896
op(_LOAD_ATTR_NONDESCRIPTOR_WITH_VALUES, (descr/4, owner -- attr)) {
906897
(void)descr;
907898
PyTypeObject *type = sym_get_type(owner);
908899
PyObject *name = get_co_name(ctx, oparg >> 1);
909900
attr = lookup_attr(ctx, dependencies, this_instr, type, name,
910-
_POP_TOP_LOAD_CONST_INLINE_BORROW,
911-
_POP_TOP_LOAD_CONST_INLINE);
901+
true);
912902
}
913903

914904
op(_LOAD_ATTR_NONDESCRIPTOR_NO_DICT, (descr/4, owner -- attr)) {
915905
(void)descr;
916906
PyTypeObject *type = sym_get_type(owner);
917907
PyObject *name = get_co_name(ctx, oparg >> 1);
918908
attr = lookup_attr(ctx, dependencies, this_instr, type, name,
919-
_POP_TOP_LOAD_CONST_INLINE_BORROW,
920-
_POP_TOP_LOAD_CONST_INLINE);
909+
true);
921910
}
922911

923912
op(_LOAD_ATTR_METHOD_WITH_VALUES, (descr/4, owner -- attr, self)) {
924913
(void)descr;
925914
PyTypeObject *type = sym_get_type(owner);
926915
PyObject *name = get_co_name(ctx, oparg >> 1);
927916
attr = lookup_attr(ctx, dependencies, this_instr, type, name,
928-
_INSERT_1_LOAD_CONST_INLINE_BORROW,
929-
_INSERT_1_LOAD_CONST_INLINE);
917+
false);
930918
self = owner;
931919
}
932920

@@ -935,8 +923,7 @@ dummy_func(void) {
935923
PyTypeObject *type = sym_get_type(owner);
936924
PyObject *name = get_co_name(ctx, oparg >> 1);
937925
attr = lookup_attr(ctx, dependencies, this_instr, type, name,
938-
_INSERT_1_LOAD_CONST_INLINE_BORROW,
939-
_INSERT_1_LOAD_CONST_INLINE);
926+
false);
940927
self = owner;
941928
}
942929

@@ -945,8 +932,7 @@ dummy_func(void) {
945932
PyTypeObject *type = sym_get_type(owner);
946933
PyObject *name = get_co_name(ctx, oparg >> 1);
947934
attr = lookup_attr(ctx, dependencies, this_instr, type, name,
948-
_INSERT_1_LOAD_CONST_INLINE_BORROW,
949-
_INSERT_1_LOAD_CONST_INLINE);
935+
false);
950936
self = owner;
951937
}
952938

@@ -2010,7 +1996,7 @@ dummy_func(void) {
20101996
ctx->builtins_watched = true;
20111997
}
20121998
if (ctx->frame->globals_checked_version != 0 && ctx->frame->globals_watched) {
2013-
cnst = convert_global_to_const(this_instr, builtins, false, false);
1999+
cnst = convert_global_to_const(this_instr, builtins, false);
20142000
}
20152001
}
20162002
if (cnst == NULL) {
@@ -2049,7 +2035,7 @@ dummy_func(void) {
20492035
ctx->frame->globals_checked_version = version;
20502036
}
20512037
if (ctx->frame->globals_checked_version == version) {
2052-
cnst = convert_global_to_const(this_instr, globals, false, false);
2038+
cnst = convert_global_to_const(this_instr, globals, false);
20532039
}
20542040
}
20552041
}

Python/optimizer_cases.c.h

Lines changed: 14 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Tools/cases_generator/optimizer_generator.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -238,18 +238,23 @@ def replace_opcode_if_evaluates_pure(
238238
# Map input count to output index (from TOS) and the appropriate constant-loading uop
239239
input_count_to_uop = {
240240
1: {
241-
# (a -- a), usually for unary ops
242-
0: "_POP_TOP_LOAD_CONST_INLINE_BORROW",
241+
# (a -- res), usually for unary ops
242+
0: [("_POP_TOP", "0, 0"),
243+
("_LOAD_CONST_INLINE_BORROW",
244+
"0, (uintptr_t)result")],
243245
# (left -- res, left)
244246
# usually for unary ops with passthrough references
245-
1: "_INSERT_1_LOAD_CONST_INLINE_BORROW",
247+
1: [("_INSERT_1_LOAD_CONST_INLINE_BORROW",
248+
"0, (uintptr_t)result")],
246249
},
247250
2: {
248-
# (a. b -- res), usually for binary ops
249-
0: "_POP_TWO_LOAD_CONST_INLINE_BORROW",
251+
# (a, b -- res), usually for binary ops
252+
0: [("_POP_TWO_LOAD_CONST_INLINE_BORROW",
253+
"0, (uintptr_t)result")],
250254
# (left, right -- res, left, right)
251255
# usually for binary ops with passthrough references
252-
2: "_INSERT_2_LOAD_CONST_INLINE_BORROW",
256+
2: [("_INSERT_2_LOAD_CONST_INLINE_BORROW",
257+
"0, (uintptr_t)result")],
253258
},
254259
}
255260

@@ -263,14 +268,16 @@ def replace_opcode_if_evaluates_pure(
263268
assert output_index >= 0
264269
input_count = len(used_stack_inputs)
265270
if input_count in input_count_to_uop and output_index in input_count_to_uop[input_count]:
266-
replacement_uop = input_count_to_uop[input_count][output_index]
271+
ops = input_count_to_uop[input_count][output_index]
267272
input_desc = "one input" if input_count == 1 else "two inputs"
273+
ops_desc = " + ".join(op for op, _ in ops)
268274

269275
emitter.emit(f"if (sym_is_const(ctx, {output_identifier.text})) {{\n")
270276
emitter.emit(f"PyObject *result = sym_get_const(ctx, {output_identifier.text});\n")
271277
emitter.emit(f"if (_Py_IsImmortal(result)) {{\n")
272-
emitter.emit(f"// Replace with {replacement_uop} since we have {input_desc} and an immortal result\n")
273-
emitter.emit(f"ADD_OP({replacement_uop}, 0, (uintptr_t)result);\n")
278+
emitter.emit(f"// Replace with {ops_desc} since we have {input_desc} and an immortal result\n")
279+
for op, args in ops:
280+
emitter.emit(f"ADD_OP({op}, {args});\n")
274281
emitter.emit("}\n")
275282
emitter.emit("}\n")
276283

0 commit comments

Comments
 (0)