Skip to content

Commit ba014c0

Browse files
authored
chore: update bytecode support for python 3.14 (#14668)
This change updates the ddtrace code that directly edits or depends on specific bytecode instructions to work with Python 3.14. New tests exercising this code under 3.14 will be added in a forthcoming change. Validation for this change is the fact that existing tests still pass. Pulled from the 3.14 integration branch #14264
1 parent 0a2ef1d commit ba014c0

File tree

7 files changed

+225
-9
lines changed

7 files changed

+225
-9
lines changed

ddtrace/debugging/_expressions.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
from typing import Tuple
4040
from typing import Union
4141

42+
from bytecode import BinaryOp
4243
from bytecode import Bytecode
4344
from bytecode import Compare
4445
from bytecode import Instr
@@ -301,7 +302,12 @@ def _compile_arg_operation(self, ast: DDASTType) -> Optional[List[Instr]]:
301302
raise ValueError("Invalid argument: %r" % a)
302303
if cb is None:
303304
raise ValueError("Invalid argument: %r" % b)
304-
return cv + ca + cb + [Instr("BUILD_SLICE", 2), Instr("BINARY_SUBSCR")]
305+
306+
if PY >= (3, 14):
307+
subscr_instruction = Instr("BINARY_OP", BinaryOp.SUBSCR)
308+
else:
309+
subscr_instruction = Instr("BINARY_SUBSCR")
310+
return cv + ca + cb + [Instr("BUILD_SLICE", 2), subscr_instruction]
305311

306312
if _type == "filter":
307313
a, b = args

ddtrace/internal/assembly.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def transform_instruction(opcode: str, arg: t.Any) -> t.Tuple[str, t.Any]:
4646
opcode = "LOAD_ATTR"
4747
arg = (True, arg)
4848
elif opcode.upper() == "LOAD_ATTR" and not isinstance(arg, tuple):
49-
arg = (False, arg)
49+
arg = (sys.version_info >= (3, 14), arg)
5050

5151
return opcode, arg
5252

@@ -157,6 +157,11 @@ def parse_try_end(self, line: str) -> t.Optional[bc.TryEnd]:
157157

158158
def parse_opcode(self, text: str) -> str:
159159
opcode = text.upper()
160+
161+
# `dis` doesn't include `LOAD_METHOD` in 3.14.0rc1
162+
if sys.version_info >= (3, 14) and opcode == "LOAD_METHOD":
163+
return opcode
164+
160165
if opcode not in dis.opmap:
161166
raise ValueError("unknown opcode %s" % opcode)
162167

ddtrace/internal/bytecode_injection/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ class InvalidLine(Exception):
3030
# the stack to the state prior to the call.
3131

3232
INJECTION_ASSEMBLY = Assembly()
33-
if PY >= (3, 14):
34-
raise NotImplementedError("Python >= 3.14 is not supported yet")
33+
if PY >= (3, 15):
34+
raise NotImplementedError("Python >= 3.15 is not supported yet")
3535
elif PY >= (3, 13):
3636
INJECTION_ASSEMBLY.parse(
3737
r"""

ddtrace/internal/symbol_db/symbols.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ def get_fields(cls: type) -> t.Set[str]:
9696
return {
9797
code.co_names[b.arg]
9898
for a, b in zip(*(islice(t, i, None) for i, t in enumerate(tee(dis.get_instructions(code), 2))))
99-
if a.opname == "LOAD_FAST" and a.arg == 0 and b.opname == "STORE_ATTR"
99+
# Python 3.14 changed this to LOAD_FAST_BORROW
100+
if a.opname.startswith("LOAD_FAST") and a.arg & 15 == 0 and b.opname == "STORE_ATTR"
100101
}
101102
except AttributeError:
102103
return set()

ddtrace/internal/wrapping/asyncs.py

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,133 @@
3434
ASYNC_GEN_ASSEMBLY = Assembly()
3535
ASYNC_HEAD_ASSEMBLY = None
3636

37-
if PY >= (3, 12):
37+
if PY >= (3, 14):
38+
ASYNC_HEAD_ASSEMBLY = Assembly()
39+
ASYNC_HEAD_ASSEMBLY.parse(
40+
r"""
41+
return_generator
42+
pop_top
43+
"""
44+
)
45+
46+
COROUTINE_ASSEMBLY.parse(
47+
r"""
48+
get_awaitable 0
49+
load_const None
50+
51+
presend:
52+
send @send
53+
yield_value 2
54+
resume 3
55+
jump_backward_no_interrupt @presend
56+
send:
57+
end_send
58+
"""
59+
)
60+
61+
ASYNC_GEN_ASSEMBLY.parse(
62+
r"""
63+
try @stopiter
64+
copy 1
65+
store_fast $__ddgen
66+
load_attr (False, 'asend')
67+
store_fast $__ddgensend
68+
load_fast $__ddgen
69+
load_attr (True, '__anext__')
70+
call 0
71+
72+
loop:
73+
get_awaitable 0
74+
load_const None
75+
presend0:
76+
send @send0
77+
tried
78+
79+
try @genexit lasti
80+
yield_value 3
81+
resume 3
82+
jump_backward_no_interrupt @loop
83+
send0:
84+
end_send
85+
86+
yield:
87+
call_intrinsic_1 asm.Intrinsic1Op.INTRINSIC_ASYNC_GEN_WRAP
88+
yield_value 3
89+
resume 1
90+
push_null
91+
swap 2
92+
load_fast $__ddgensend
93+
swap 2
94+
call 1
95+
jump_backward @loop
96+
tried
97+
98+
genexit:
99+
try @stopiter
100+
push_exc_info
101+
load_const GeneratorExit
102+
check_exc_match
103+
pop_jump_if_false @exc
104+
pop_top
105+
load_fast $__ddgen
106+
load_attr (True, 'aclose')
107+
call 0
108+
get_awaitable 0
109+
load_const None
110+
111+
presend1:
112+
send @send1
113+
yield_value 4
114+
resume 3
115+
jump_backward_no_interrupt @presend1
116+
send1:
117+
end_send
118+
pop_top
119+
pop_except
120+
load_const None
121+
return_value
122+
123+
exc:
124+
pop_top
125+
push_null
126+
load_fast $__ddgen
127+
load_attr (False, 'athrow')
128+
push_null
129+
load_const sys.exc_info
130+
call 0
131+
call_function_ex
132+
get_awaitable 0
133+
load_const None
134+
135+
presend2:
136+
send @send2
137+
yield_value 4
138+
resume 3
139+
jump_backward_no_interrupt @presend2
140+
send2:
141+
end_send
142+
swap 2
143+
pop_except
144+
jump_backward @yield
145+
tried
146+
147+
stopiter:
148+
push_exc_info
149+
load_const StopAsyncIteration
150+
check_exc_match
151+
pop_jump_if_false @propagate
152+
pop_top
153+
pop_except
154+
load_const None
155+
return_value
156+
157+
propagate:
158+
reraise 0
159+
"""
160+
)
161+
162+
163+
elif PY >= (3, 12):
38164
ASYNC_HEAD_ASSEMBLY = Assembly()
39165
ASYNC_HEAD_ASSEMBLY.parse(
40166
r"""

ddtrace/internal/wrapping/context.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@
6767
CONTEXT_RETURN = Assembly()
6868
CONTEXT_FOOT = Assembly()
6969

70-
if sys.version_info >= (3, 14):
71-
raise NotImplementedError("Python >= 3.14 is not supported yet")
70+
if sys.version_info >= (3, 15):
71+
raise NotImplementedError("Python >= 3.15 is not supported yet")
7272
elif sys.version_info >= (3, 13):
7373
CONTEXT_HEAD.parse(
7474
r"""

ddtrace/internal/wrapping/generators.py

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,85 @@
3030
GENERATOR_ASSEMBLY = Assembly()
3131
GENERATOR_HEAD_ASSEMBLY = None
3232

33-
if PY >= (3, 12):
33+
if PY >= (3, 14):
34+
GENERATOR_HEAD_ASSEMBLY = Assembly()
35+
GENERATOR_HEAD_ASSEMBLY.parse(
36+
r"""
37+
return_generator
38+
pop_top
39+
"""
40+
)
41+
42+
GENERATOR_ASSEMBLY.parse(
43+
r"""
44+
try @stopiter
45+
copy 1
46+
store_fast $__ddgen
47+
load_attr $send
48+
store_fast $__ddgensend
49+
push_null
50+
load_const next
51+
load_fast $__ddgen
52+
53+
loop:
54+
call 1
55+
tried
56+
57+
yield:
58+
try @genexit lasti
59+
yield_value 3
60+
resume 1
61+
push_null
62+
swap 2
63+
load_fast $__ddgensend
64+
swap 2
65+
jump_backward @loop
66+
tried
67+
68+
genexit:
69+
try @stopiter
70+
push_exc_info
71+
load_const GeneratorExit
72+
check_exc_match
73+
pop_jump_if_false @exc
74+
pop_top
75+
load_fast $__ddgen
76+
load_method $close
77+
call 0
78+
swap 2
79+
pop_except
80+
return_value
81+
82+
exc:
83+
pop_top
84+
push_null
85+
load_fast $__ddgen
86+
load_attr $throw
87+
push_null
88+
load_const sys.exc_info
89+
call 0
90+
call_function_ex
91+
swap 2
92+
pop_except
93+
jump_backward @yield
94+
tried
95+
96+
stopiter:
97+
push_exc_info
98+
load_const StopIteration
99+
check_exc_match
100+
pop_jump_if_false @propagate
101+
pop_top
102+
pop_except
103+
load_const None
104+
return_value
105+
106+
propagate:
107+
reraise 0
108+
"""
109+
)
110+
111+
elif PY >= (3, 12):
34112
GENERATOR_HEAD_ASSEMBLY = Assembly()
35113
GENERATOR_HEAD_ASSEMBLY.parse(
36114
r"""

0 commit comments

Comments
 (0)