Skip to content

Commit 73681b1

Browse files
feat(python): Update BytecodeParser opcode awareness for upcoming Python 3.14
1 parent 4b309cf commit 73681b1

File tree

1 file changed

+32
-5
lines changed

1 file changed

+32
-5
lines changed

py-polars/polars/_utils/udfs.py

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class StackValue(NamedTuple):
5050

5151
_MIN_PY311 = sys.version_info >= (3, 11)
5252
_MIN_PY312 = _MIN_PY311 and sys.version_info >= (3, 12)
53+
_MIN_PY314 = _MIN_PY312 and sys.version_info >= (3, 14)
5354

5455

5556
class OpNames:
@@ -87,6 +88,10 @@ class OpNames:
8788
LOAD_VALUES = frozenset(("LOAD_CONST", "LOAD_DEREF", "LOAD_FAST", "LOAD_GLOBAL"))
8889
LOAD_ATTR = frozenset({"LOAD_METHOD", "LOAD_ATTR"})
8990
LOAD = LOAD_VALUES | LOAD_ATTR
91+
SIMPLIFY_SPECIALIZED: ClassVar[dict[str, str]] = {
92+
"LOAD_FAST_BORROW": "LOAD_FAST",
93+
"LOAD_SMALL_INT": "LOAD_CONST",
94+
}
9095
SYNTHETIC: ClassVar[dict[str, int]] = {
9196
"POLARS_EXPRESSION": 1,
9297
}
@@ -102,7 +107,9 @@ class OpNames:
102107
| set(SYNTHETIC)
103108
| LOAD_VALUES
104109
)
105-
MATCHABLE_OPS = PARSEABLE_OPS | set(BINARY) | LOAD_ATTR | CALL
110+
MATCHABLE_OPS = (
111+
set(SIMPLIFY_SPECIALIZED) | PARSEABLE_OPS | set(BINARY) | LOAD_ATTR | CALL
112+
)
106113
UNARY_VALUES = frozenset(UNARY.values())
107114

108115

@@ -736,6 +743,7 @@ class RewrittenInstructions:
736743
[
737744
"COPY",
738745
"COPY_FREE_VARS",
746+
"NOT_TAKEN",
739747
"POP_TOP",
740748
"PRECALL",
741749
"PUSH_NULL",
@@ -762,7 +770,7 @@ def __init__(
762770
if inst.opname not in OpNames.MATCHABLE_OPS:
763771
self._rewritten_instructions = []
764772
return
765-
upgraded_inst = self._upgrade_instruction(inst)
773+
upgraded_inst = self._update_instruction(inst)
766774
normalised_instructions.append(upgraded_inst)
767775

768776
self._rewritten_instructions = self._rewrite(normalised_instructions)
@@ -1077,7 +1085,10 @@ def _unpack_superinstructions(
10771085
) -> Iterator[Instruction]:
10781086
"""Expand known 'superinstructions' into their component parts."""
10791087
for inst in instructions:
1080-
if inst.opname == "LOAD_FAST_LOAD_FAST":
1088+
if inst.opname in (
1089+
"LOAD_FAST_LOAD_FAST",
1090+
"LOAD_FAST_BORROW_LOAD_FAST_BORROW",
1091+
):
10811092
for idx in (0, 1):
10821093
yield inst._replace(
10831094
opname="LOAD_FAST",
@@ -1088,13 +1099,29 @@ def _unpack_superinstructions(
10881099
yield inst
10891100

10901101
@staticmethod
1091-
def _upgrade_instruction(inst: Instruction) -> Instruction:
1092-
"""Rewrite any older binary opcodes using py 3.11 'BINARY_OP' instead."""
1102+
def _update_instruction(inst: Instruction) -> Instruction:
1103+
"""Update/modify specific instructions to simplify multi-version parsing."""
10931104
if not _MIN_PY311 and inst.opname in OpNames.BINARY:
1105+
# update older binary opcodes using py >= 3.11 'BINARY_OP' instead
10941106
inst = inst._replace(
10951107
argrepr=OpNames.BINARY[inst.opname],
10961108
opname="BINARY_OP",
10971109
)
1110+
elif _MIN_PY314:
1111+
if (opname := inst.opname) in OpNames.SIMPLIFY_SPECIALIZED:
1112+
# simplify specialised opcode variants to their more generic form
1113+
# (eg: 'LOAD_FAST_BORROW' -> 'LOAD_FAST', etc)
1114+
updated_params = {"opname": OpNames.SIMPLIFY_SPECIALIZED[inst.opname]}
1115+
if opname == "LOAD_SMALL_INT":
1116+
updated_params["argrepr"] = str(inst.argval)
1117+
inst = inst._replace(**updated_params)
1118+
1119+
elif opname == "BINARY_OP" and inst.argrepr == "[]":
1120+
# special case for new '[]' binary op; revert to 'BINARY_SUBSCR'
1121+
inst = inst._replace(
1122+
opname="BINARY_SUBSCR", arg=None, argval=None, argrepr=""
1123+
)
1124+
10981125
return inst
10991126

11001127
def _is_stdlib_datetime(

0 commit comments

Comments
 (0)