Skip to content

Commit b513af5

Browse files
committed
address review
1 parent 33093df commit b513af5

4 files changed

Lines changed: 72 additions & 22 deletions

File tree

Include/internal/pycore_optimizer.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,12 @@ typedef struct {
498498

499499
PyAPI_DATA(const _PyOpcodeRecordEntry) _PyOpcode_RecordEntries[256];
500500
PyAPI_DATA(const _PyOpcodeRecordSlotMap) _PyOpcode_RecordSlotMaps[256];
501+
502+
/* Convert a family-recorded value to the form a recorder uop expects.
503+
* If no transform is needed, return the input value unchanged.
504+
* Takes ownership of `value` and returns a new strong reference or NULL.
505+
*/
506+
PyAPI_FUNC(PyObject *) _PyOpcode_RecordTransformValue(int uop, PyObject *value);
501507
#endif
502508

503509
#ifdef __cplusplus

Python/optimizer.c

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -612,14 +612,6 @@ is_terminator(const _PyUOpInstruction *uop)
612612
);
613613
}
614614

615-
/* Transform a recorded value before storing it in operand0.
616-
*
617-
* Family-wide recording stores the base opcode's raw value. Some uops need a
618-
* derived form instead, such as the value's type. A NULL entry leaves the
619-
* recorded value unchanged.
620-
*/
621-
typedef PyObject *(*_Py_RecordTraceTransformFn)(PyObject *value);
622-
623615
static PyObject *
624616
record_trace_transform_to_type(PyObject *value)
625617
{
@@ -663,14 +655,6 @@ record_trace_transform_bound_method(PyObject *value)
663655
return result;
664656
}
665657

666-
static const _Py_RecordTraceTransformFn record_trace_transforms[MAX_UOP_ID + 1] = {
667-
[_RECORD_TOS_TYPE] = record_trace_transform_to_type,
668-
[_RECORD_NOS_TYPE] = record_trace_transform_to_type,
669-
[_RECORD_NOS_GEN_FUNC] = record_trace_transform_gen_func,
670-
[_RECORD_3OS_GEN_FUNC] = record_trace_transform_gen_func,
671-
[_RECORD_BOUND_METHOD] = record_trace_transform_bound_method,
672-
};
673-
674658
/* Returns 1 on success (added to trace), 0 on trace end.
675659
*/
676660
// gh-142543: inlining this function causes stack overflows
@@ -1016,10 +1000,8 @@ _PyJit_translate_single_bytecode_to_trace(
10161000

10171001
PyObject *recorded_value = tracer->prev_state.recorded_values[record_slot];
10181002
tracer->prev_state.recorded_values[record_slot] = NULL;
1019-
_Py_RecordTraceTransformFn transform =
1020-
record_slot_map->needs_family_transform ? record_trace_transforms[uop] : NULL;
1021-
if (transform != NULL && recorded_value != NULL) {
1022-
recorded_value = transform(recorded_value);
1003+
if (record_slot_map->needs_family_transform && recorded_value != NULL) {
1004+
recorded_value = _PyOpcode_RecordTransformValue(uop, recorded_value);
10231005
}
10241006
operand = (uintptr_t)recorded_value;
10251007
}

Python/record_functions.c.h

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

Tools/cases_generator/record_function_generator.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,16 @@
2828
# Must match MAX_RECORDED_VALUES in Include/internal/pycore_optimizer.h.
2929
MAX_RECORDED_VALUES = 3
3030

31+
# Map `_RECORD_*` uops to the helper that converts a raw family-recorded
32+
# value to the form the specialized member consumes.
33+
_RECORD_TRANSFORM_HELPERS: dict[str, str] = {
34+
"_RECORD_TOS_TYPE": "record_trace_transform_to_type",
35+
"_RECORD_NOS_TYPE": "record_trace_transform_to_type",
36+
"_RECORD_NOS_GEN_FUNC": "record_trace_transform_gen_func",
37+
"_RECORD_3OS_GEN_FUNC": "record_trace_transform_gen_func",
38+
"_RECORD_BOUND_METHOD": "record_trace_transform_bound_method",
39+
}
40+
3141

3242
class RecorderEmitter(Emitter):
3343
def __init__(self, out: CWriter):
@@ -119,11 +129,14 @@ def generate_recorder_tables(analysis: Analysis, out: CWriter) -> None:
119129
record_function_indexes: dict[str, int] = dict()
120130
record_table: dict[str, list[str]] = {}
121131
record_consumer_table: dict[str, tuple[list[int], bool]] = {}
122-
record_slot_keys = {
123-
name: get_record_slot_kind(name)
132+
record_uop_names = {
133+
name
124134
for name, uop in analysis.uops.items()
125135
if uop.properties.records_value
126136
}
137+
record_slot_keys = {
138+
name: get_record_slot_kind(name) for name in record_uop_names
139+
}
127140
index = 1
128141
for inst in analysis.instructions.values():
129142
source_inst = inst
@@ -187,6 +200,38 @@ def generate_recorder_tables(analysis: Analysis, out: CWriter) -> None:
187200
for name in record_function_indexes:
188201
out.emit(f" [{name}_INDEX] = _PyOpcode_RecordFunction{name[7:]},\n")
189202
out.emit("};\n")
203+
generate_record_transform_dispatcher(record_uop_names, out)
204+
205+
206+
def generate_record_transform_dispatcher(
207+
record_uop_names: set[str], out: CWriter
208+
) -> None:
209+
"""Emit a switch that converts a family-recorded value for a recorder uop.
210+
211+
Only `_RECORD_*` uops that need conversion get a case; the default
212+
returns the input value unchanged. Helpers live in Python/optimizer.c.
213+
"""
214+
cases: dict[str, list[str]] = {}
215+
for record_name in record_uop_names:
216+
helper = _RECORD_TRANSFORM_HELPERS.get(record_name)
217+
if helper is None:
218+
continue
219+
cases.setdefault(helper, []).append(record_name)
220+
out.emit("\n")
221+
out.emit(
222+
"PyObject *\n"
223+
"_PyOpcode_RecordTransformValue(int uop, PyObject *value)\n"
224+
"{\n"
225+
)
226+
out.emit(" switch (uop) {\n")
227+
for helper, names in cases.items():
228+
for name in names:
229+
out.emit(f" case {name}:\n")
230+
out.emit(f" return {helper}(value);\n")
231+
out.emit(" default:\n")
232+
out.emit(" return value;\n")
233+
out.emit(" }\n")
234+
out.emit("}\n")
190235

191236

192237
arg_parser = argparse.ArgumentParser(

0 commit comments

Comments
 (0)