@@ -2187,6 +2187,75 @@ def test_family_member_needs_transform_only_when_shape_changes(self):
21872187 "[OP_TYPED_SPECIALIZED] = {1, 0, {0}}" ,
21882188 )
21892189
2190+ def test_record_transform_generated_from_recording_uop (self ):
2191+ input = """
2192+ tier2 op(_RECORD_TOS, (tos -- tos)) {
2193+ RECORD_VALUE(PyStackRef_AsPyObjectBorrow(tos));
2194+ }
2195+ tier2 op(_RECORD_TOS_TYPE, (tos -- tos)) {
2196+ RECORD_VALUE(Py_TYPE(PyStackRef_AsPyObjectBorrow(tos)));
2197+ }
2198+ op(_DO_STUFF, (tos -- res)) {
2199+ res = tos;
2200+ }
2201+ macro(OP) = _RECORD_TOS + _DO_STUFF;
2202+ macro(OP_SPECIALIZED) = _RECORD_TOS_TYPE + _DO_STUFF;
2203+ family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED };
2204+ """
2205+ output = self .generate_tables (input )
2206+ self .assertIn ("_PyOpcode_RecordTransform_TOS_TYPE" , output )
2207+ self .assertIn ("tos = PyStackRef_FromPyObjectBorrow(recorded_value);" , output )
2208+ self .assertIn (
2209+ "transformed_value = (PyObject *)Py_TYPE(PyStackRef_AsPyObjectBorrow(tos));" ,
2210+ output ,
2211+ )
2212+ self .assertIn ("return _PyOpcode_RecordTransform_TOS_TYPE(value);" , output )
2213+ self .assertNotIn ("record_trace_transform_to_type" , output )
2214+
2215+ def test_record_transform_generated_when_only_specialization_records (self ):
2216+ input = """
2217+ tier2 op(_RECORD_TOS_TYPE, (tos -- tos)) {
2218+ RECORD_VALUE(Py_TYPE(PyStackRef_AsPyObjectBorrow(tos)));
2219+ }
2220+ op(_DO_STUFF, (tos -- res)) {
2221+ res = tos;
2222+ }
2223+ macro(OP) = _DO_STUFF;
2224+ macro(OP_SPECIALIZED) = _RECORD_TOS_TYPE + _DO_STUFF;
2225+ family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED };
2226+ """
2227+ output = self .generate_tables (input )
2228+ # Family head must adopt the specialization's recorder.
2229+ self .assertIn ("[OP] = {1, {_RECORD_TOS_TYPE_INDEX}}" , output )
2230+ self .assertIn ("[OP_SPECIALIZED] = {1, {_RECORD_TOS_TYPE_INDEX}}" , output )
2231+ # Specialization consumes the slot directly (mask 0), no transform.
2232+ self .assert_slot_map_lines (output , "[OP_SPECIALIZED] = {1, 0, {0}}" )
2233+ self .assertNotIn ("_PyOpcode_RecordTransform_TOS_TYPE(" , output )
2234+
2235+ def test_no_record_transform_when_only_base_records (self ):
2236+ input = """
2237+ tier2 op(_RECORD_TOS, (tos -- tos)) {
2238+ RECORD_VALUE(PyStackRef_AsPyObjectBorrow(tos));
2239+ }
2240+ op(_DO_STUFF, (tos -- res)) {
2241+ res = tos;
2242+ }
2243+ macro(OP) = _RECORD_TOS + _DO_STUFF;
2244+ macro(OP_SPECIALIZED) = _DO_STUFF;
2245+ family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED };
2246+ """
2247+ output = self .generate_tables (input )
2248+ # Family head records via _RECORD_TOS.
2249+ self .assertIn ("[OP] = {1, {_RECORD_TOS_INDEX}}" , output )
2250+ self .assertIn ("[OP_SPECIALIZED] = {1, {_RECORD_TOS_INDEX}}" , output )
2251+ # Specialization has no consumer slot map entry (it doesn't read it).
2252+ self .assertNotIn (
2253+ "[OP_SPECIALIZED] = {1," , self .get_slot_map_section (output )
2254+ )
2255+ # No transform helpers are generated.
2256+ self .assertNotIn ("_PyOpcode_RecordTransform_TOS(" , output )
2257+ self .assertNotIn ("_PyOpcode_RecordTransform_TOS_TYPE" , output )
2258+
21902259 def test_family_member_maps_positional_recorders_to_family_slots (self ):
21912260 input = """
21922261 tier2 op(_RECORD_TOS, (sub -- sub)) {
@@ -2243,7 +2312,12 @@ def test_family_head_records_union_of_member_recorders(self):
22432312 macro(OP_SPECIALIZED) = _RECORD_TOS + _DO_STUFF;
22442313 family(OP, INLINE_CACHE_ENTRIES_OP) = { OP_SPECIALIZED };
22452314 """
2315+ analysis = self .analyze_input (input )
22462316 output = self .generate_tables (input )
2317+ self .assertEqual (
2318+ analysis .families ["OP" ].get_member_record_names (),
2319+ ("_RECORD_TOS" ,),
2320+ )
22472321 self .assertIn ("[OP] = {1, {_RECORD_TOS_INDEX}}" , output )
22482322 self .assertIn ("[OP_SPECIALIZED] = {1, {_RECORD_TOS_INDEX}}" , output )
22492323 self .assert_slot_map_lines (output , "[OP_SPECIALIZED] = {1, 0, {0}}" )
0 commit comments