Skip to content

Commit 6b359a4

Browse files
authored
refactor(internal): tidy up versioning in wrapping submodule (#5879)
Tidy up the compatibility support for the different versions of the supported CPython bytecode versions in the wrapping submodule to make it more easily maintainable as new versions of CPython are released that introduce bytecode breaking changes. ## Checklist - [x] Change(s) are motivated and described in the PR description. - [x] Testing strategy is described if automated tests are not included in the PR. - [x] Risk is outlined (performance impact, potential for breakage, maintainability, etc). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] [Library release note guidelines](https://ddtrace.readthedocs.io/en/stable/contributing.html#Release-Note-Guidelines) are followed. - [x] Documentation is included (in-code, generated user docs, [public corp docs](https://github.com/DataDog/documentation/)). - [x] OPTIONAL: PR description includes explicit acknowledgement of the performance implications of the change as reported in the benchmarks PR comment. ## Reviewer Checklist - [x] Title is accurate. - [x] No unnecessary changes are introduced. - [x] Description motivates each change. - [x] Avoids breaking [API](https://ddtrace.readthedocs.io/en/stable/versioning.html#interfaces) changes unless absolutely necessary. - [x] Testing strategy adequately addresses listed risk(s). - [x] Change is maintainable (easy to change, telemetry, documentation). - [x] Release note makes sense to a user of the library. - [x] Reviewer has explicitly acknowledged and discussed the performance implications of this PR as reported in the benchmarks PR comment.
1 parent cc95ac5 commit 6b359a4

File tree

1 file changed

+90
-57
lines changed

1 file changed

+90
-57
lines changed

ddtrace/internal/wrapping.py

Lines changed: 90 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -40,27 +40,89 @@ def __call__(self, *args, **kwargs):
4040

4141
def _compare_exc(label, lineno):
4242
"""Compat helper for comparing exceptions."""
43-
return (
44-
Instr("COMPARE_OP", Compare.EXC_MATCH, lineno=lineno)
45-
if PY < (3, 9)
46-
else Instr("JUMP_IF_NOT_EXC_MATCH", label, lineno=lineno)
47-
)
43+
if PY < (3, 9):
44+
return Instr("COMPARE_OP", Compare.EXC_MATCH, lineno=lineno)
45+
return Instr("JUMP_IF_NOT_EXC_MATCH", label, lineno=lineno)
4846

4947

5048
def _jump_if_false(label, lineno):
5149
"""Compat helper for jumping if false after comparing exceptions."""
52-
return Instr("POP_JUMP_IF_FALSE", label, lineno=lineno) if PY < (3, 9) else Instr("NOP", lineno=lineno)
50+
if PY < (3, 9):
51+
return Instr("POP_JUMP_IF_FALSE", label, lineno=lineno)
52+
return Instr("NOP", lineno=lineno)
5353

5454

5555
def _end_finally(lineno):
5656
"""Compat helper for ending finally blocks."""
5757
if PY < (3, 9):
5858
return Instr("END_FINALLY", lineno=lineno)
59-
elif PY < (3, 10):
59+
if PY < (3, 10):
6060
return Instr("RERAISE", lineno=lineno)
6161
return Instr("RERAISE", 0, lineno=lineno)
6262

6363

64+
def _pop_except(lineno):
65+
"""Compat helper for popping except blocks."""
66+
if PY >= (3,):
67+
return Instr("POP_EXCEPT", lineno=lineno)
68+
return Instr("NOP", lineno=lineno)
69+
70+
71+
def _setup_block(label, lineno):
72+
if PY < (3, 8):
73+
return Instr("SETUP_EXCEPT", label, lineno=lineno)
74+
return Instr("SETUP_FINALLY", label, lineno=lineno)
75+
76+
77+
def _call_variadic(lineno):
78+
if PY < (3, 6):
79+
return Instr("CALL_FUNCTION_VAR", 0, lineno=lineno)
80+
return Instr("CALL_FUNCTION_EX", 0, lineno=lineno)
81+
82+
83+
def _add(lineno):
84+
if PY < (3, 11):
85+
return Instr("INPLACE_ADD", lineno=lineno)
86+
return Instr("BINARY_OP", b.BinaryOp.ADD, lineno=lineno)
87+
88+
89+
def _update_map_block(varkwargsname, lineno):
90+
if PY < (3, 11):
91+
return [
92+
Instr("DUP_TOP", lineno=lineno),
93+
Instr("LOAD_ATTR", "update", lineno=lineno),
94+
Instr("LOAD_FAST", varkwargsname, lineno=lineno),
95+
Instr("CALL_FUNCTION", 1, lineno=lineno),
96+
Instr("POP_TOP", lineno=lineno),
97+
]
98+
99+
return [
100+
Instr("COPY", 1, lineno=lineno),
101+
Instr("LOAD_METHOD", "update", lineno=lineno),
102+
Instr("LOAD_FAST", varkwargsname, lineno=lineno),
103+
Instr("PRECALL", 1, lineno=lineno),
104+
Instr("CALL", 1, lineno=lineno),
105+
Instr("POP_TOP", lineno=lineno),
106+
]
107+
108+
109+
def _call_return_block(arg, lineno):
110+
if PY < (3, 11):
111+
return [
112+
Instr("CALL_FUNCTION", arg, lineno=lineno),
113+
Instr("RETURN_VALUE", lineno=lineno),
114+
]
115+
116+
return [
117+
Instr("PRECALL", arg, lineno=lineno),
118+
Instr("CALL", arg, lineno=lineno),
119+
Instr("RETURN_VALUE", lineno=lineno),
120+
]
121+
122+
123+
FIRSTLINENO_OFFSET = int(PY >= (3, 11))
124+
125+
64126
# -----------------------------------------------------------------------------
65127
# Generator Wrapping
66128
# -----------------------------------------------------------------------------
@@ -91,7 +153,7 @@ def _wrap_generator(instrs, code, lineno):
91153
_yield = Label()
92154

93155
instrs[-1:] = [
94-
Instr("SETUP_EXCEPT" if PY < (3, 8) else "SETUP_FINALLY", stopiter, lineno=lineno),
156+
_setup_block(stopiter, lineno),
95157
Instr("DUP_TOP", lineno=lineno),
96158
Instr("STORE_FAST", "__ddgen", lineno=lineno),
97159
Instr("LOAD_ATTR", "send", lineno=lineno),
@@ -101,7 +163,7 @@ def _wrap_generator(instrs, code, lineno):
101163
loop,
102164
Instr("CALL_FUNCTION", 1, lineno=lineno),
103165
_yield,
104-
Instr("SETUP_EXCEPT" if PY < (3, 8) else "SETUP_FINALLY", genexit, lineno=lineno),
166+
_setup_block(genexit, lineno=lineno),
105167
Instr("YIELD_VALUE", lineno=lineno),
106168
Instr("POP_BLOCK", lineno=lineno),
107169
Instr("LOAD_FAST", "__ddgensend", lineno=lineno),
@@ -129,11 +191,11 @@ def _wrap_generator(instrs, code, lineno):
129191
Instr("LOAD_ATTR", "throw", lineno=lineno),
130192
Instr("LOAD_CONST", sys.exc_info, lineno=lineno),
131193
Instr("CALL_FUNCTION", 0, lineno=lineno),
132-
Instr("CALL_FUNCTION_VAR" if PY < (3, 6) else "CALL_FUNCTION_EX", 0, lineno=lineno),
194+
_call_variadic(lineno),
133195
# DEV: We cannot use ROT_FOUR because it was removed in 3.5 and added
134196
# back in 3.8
135197
Instr("STORE_FAST", "__value", lineno=lineno),
136-
Instr("POP_EXCEPT" if PY >= (3,) else "NOP", lineno=lineno),
198+
_pop_except(lineno),
137199
Instr("LOAD_FAST", "__value", lineno=lineno),
138200
Instr("JUMP_ABSOLUTE", _yield, lineno=lineno),
139201
stopiter, # except StopIteration:
@@ -144,7 +206,7 @@ def _wrap_generator(instrs, code, lineno):
144206
Instr("POP_TOP", lineno=lineno),
145207
Instr("POP_TOP", lineno=lineno),
146208
Instr("POP_TOP", lineno=lineno),
147-
Instr("POP_EXCEPT" if PY >= (3,) else "NOP", lineno=lineno),
209+
_pop_except(lineno),
148210
Instr("LOAD_CONST", None, lineno=lineno),
149211
Instr("RETURN_VALUE", lineno=lineno),
150212
propagate,
@@ -160,7 +222,10 @@ def _wrap_generator_py311(instrs, code, lineno):
160222
pass
161223

162224

163-
wrap_generator = _wrap_generator_py311 if PY >= (3, 11) else _wrap_generator
225+
if PY >= (3, 11):
226+
wrap_generator = _wrap_generator_py311
227+
else:
228+
wrap_generator = _wrap_generator
164229

165230

166231
# -----------------------------------------------------------------------------
@@ -202,7 +267,7 @@ def _wrap_special_function_py3(instrs, code, lineno):
202267
_yield = Label()
203268

204269
instrs[-1:] = [
205-
Instr("SETUP_EXCEPT" if PY < (3, 8) else "SETUP_FINALLY", stopiter, lineno=lineno),
270+
_setup_block(stopiter, lineno=lineno),
206271
Instr("DUP_TOP", lineno=lineno),
207272
Instr("STORE_FAST", "__ddgen", lineno=lineno),
208273
Instr("LOAD_ATTR", "asend", lineno=lineno),
@@ -215,7 +280,7 @@ def _wrap_special_function_py3(instrs, code, lineno):
215280
Instr("LOAD_CONST", None, lineno=lineno),
216281
Instr("YIELD_FROM", lineno=lineno),
217282
_yield,
218-
Instr("SETUP_EXCEPT" if PY < (3, 8) else "SETUP_FINALLY", genexit, lineno=lineno),
283+
_setup_block(genexit, lineno=lineno),
219284
Instr("YIELD_VALUE", lineno=lineno),
220285
Instr("POP_BLOCK", lineno=lineno),
221286
Instr("LOAD_FAST", "__ddgensend", lineno=lineno),
@@ -282,7 +347,10 @@ def _wrap_special_function_py311(instrs, code, lineno):
282347
pass
283348

284349

285-
wrap_special_function = _wrap_special_function_py3 if sys.version_info < (3, 11) else _wrap_special_function_py311
350+
if PY >= (3, 11):
351+
wrap_special_function = _wrap_special_function_py311
352+
else:
353+
wrap_special_function = _wrap_special_function_py3
286354

287355

288356
def wrap_bytecode(wrapper, wrapped):
@@ -298,7 +366,7 @@ def wrap_bytecode(wrapper, wrapped):
298366
"""
299367

300368
code = wrapped.__code__
301-
lineno = code.co_firstlineno + (sys.version_info >= (3, 11))
369+
lineno = code.co_firstlineno + FIRSTLINENO_OFFSET
302370
varargs = bool(code.co_flags & CompilerFlags.VARARGS)
303371
varkwargs = bool(code.co_flags & CompilerFlags.VARKEYWORDS)
304372
nargs = code.co_argcount
@@ -317,7 +385,8 @@ def wrap_bytecode(wrapper, wrapped):
317385
Instr("LOAD_CONST", wrapper, lineno=lineno),
318386
Instr("LOAD_CONST", wrapped, lineno=lineno),
319387
]
320-
if sys.version_info >= (3, 11):
388+
if PY >= (3, 11):
389+
# THis is required to start a new frame
321390
instrs[0:0] = [
322391
Instr("RESUME", 0, lineno=lineno - 1),
323392
Instr("PUSH_NULL", lineno=lineno),
@@ -331,9 +400,7 @@ def wrap_bytecode(wrapper, wrapped):
331400
instrs.extend(
332401
[
333402
Instr("LOAD_FAST", varargsname, lineno=lineno),
334-
Instr("INPLACE_ADD", lineno=lineno)
335-
if sys.version_info < (3, 11)
336-
else Instr("BINARY_OP", b.BinaryOp.ADD, lineno=lineno),
403+
_add(lineno),
337404
]
338405
)
339406
elif varargs:
@@ -352,27 +419,7 @@ def wrap_bytecode(wrapper, wrapped):
352419
)
353420
instrs.append(Instr("BUILD_MAP", kwonlyargs, lineno=lineno))
354421
if varkwargs:
355-
if sys.version_info < (3, 11):
356-
instrs.extend(
357-
[
358-
Instr("DUP_TOP", lineno=lineno),
359-
Instr("LOAD_ATTR", "update", lineno=lineno),
360-
Instr("LOAD_FAST", varkwargsname, lineno=lineno),
361-
Instr("CALL_FUNCTION", 1, lineno=lineno),
362-
Instr("POP_TOP", lineno=lineno),
363-
]
364-
)
365-
else:
366-
instrs.extend(
367-
[
368-
Instr("COPY", 1, lineno=lineno),
369-
Instr("LOAD_METHOD", "update", lineno=lineno),
370-
Instr("LOAD_FAST", varkwargsname, lineno=lineno),
371-
Instr("PRECALL", 1, lineno=lineno),
372-
Instr("CALL", 1, lineno=lineno),
373-
Instr("POP_TOP", lineno=lineno),
374-
]
375-
)
422+
instrs.extend(_update_map_block(varkwargsname, lineno))
376423

377424
elif varkwargs:
378425
instrs.append(Instr("LOAD_FAST", varkwargsname, lineno=lineno))
@@ -382,21 +429,7 @@ def wrap_bytecode(wrapper, wrapped):
382429

383430
# Call the wrapper function with the wrapped function, the positional and
384431
# keyword arguments, and return the result.
385-
if sys.version_info < (3, 11):
386-
instrs.extend(
387-
[
388-
Instr("CALL_FUNCTION", 3, lineno=lineno),
389-
Instr("RETURN_VALUE", lineno=lineno),
390-
]
391-
)
392-
else:
393-
instrs.extend(
394-
[
395-
Instr("PRECALL", 3, lineno=lineno),
396-
Instr("CALL", 3, lineno=lineno),
397-
Instr("RETURN_VALUE", lineno=lineno),
398-
]
399-
)
432+
instrs.extend(_call_return_block(3, lineno))
400433

401434
# If the function has special flags set, like the generator, async generator
402435
# or coroutine, inject unraveling code before the return opcode.

0 commit comments

Comments
 (0)