@@ -29,12 +29,13 @@ def skip_if_different_mount_drives():
2929
3030test_tools .skip_if_missing ("cases_generator" )
3131with test_tools .imports_under_tool ("cases_generator" ):
32- from analyzer import StackItem
32+ from analyzer import StackItem , analyze_files
3333 from cwriter import CWriter
3434 import parser
3535 from stack import Local , Stack
3636 import tier1_generator
3737 import optimizer_generator
38+ import record_function_generator
3839
3940
4041def handle_stderr ():
@@ -1948,6 +1949,202 @@ def test_recording_after_non_specializing(self):
19481949 with self .assertRaisesRegex (SyntaxError , "Recording uop" ):
19491950 self .run_cases_test (input , "" )
19501951
1952+ def test_multiple_consecutive_recording_uops (self ):
1953+ """Multiple consecutive recording uops at the start of a macro are legal."""
1954+ input = """
1955+ tier2 op(_RECORD_A, (a, b -- a, b)) {
1956+ RECORD_VALUE(a);
1957+ }
1958+ tier2 op(_RECORD_B, (a, b -- a, b)) {
1959+ RECORD_VALUE(b);
1960+ }
1961+ op(_DO_STUFF, (a, b -- res)) {
1962+ res = a;
1963+ INPUTS_DEAD();
1964+ }
1965+ macro(OP) = _RECORD_A + _RECORD_B + _DO_STUFF;
1966+ """
1967+ output = """
1968+ TARGET(OP) {
1969+ #if _Py_TAIL_CALL_INTERP
1970+ int opcode = OP;
1971+ (void)(opcode);
1972+ #endif
1973+ frame->instr_ptr = next_instr;
1974+ next_instr += 1;
1975+ INSTRUCTION_STATS(OP);
1976+ _PyStackRef a;
1977+ _PyStackRef res;
1978+ // _DO_STUFF
1979+ {
1980+ a = stack_pointer[-2];
1981+ res = a;
1982+ }
1983+ stack_pointer[-2] = res;
1984+ stack_pointer += -1;
1985+ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
1986+ DISPATCH();
1987+ }
1988+ """
1989+ self .run_cases_test (input , output )
1990+
1991+ def test_multiple_recording_uops_after_specializing (self ):
1992+ """Multiple recording uops after a specializing uop are legal."""
1993+ input = """
1994+ specializing op(_SPECIALIZE_OP, (counter/1, a, b -- a, b)) {
1995+ SPAM();
1996+ }
1997+ tier2 op(_RECORD_A, (a, b -- a, b)) {
1998+ RECORD_VALUE(a);
1999+ }
2000+ tier2 op(_RECORD_B, (a, b -- a, b)) {
2001+ RECORD_VALUE(b);
2002+ }
2003+ op(_DO_STUFF, (a, b -- res)) {
2004+ res = a;
2005+ INPUTS_DEAD();
2006+ }
2007+ macro(OP) = _SPECIALIZE_OP + _RECORD_A + _RECORD_B + unused/2 + _DO_STUFF;
2008+ """
2009+ output = """
2010+ TARGET(OP) {
2011+ #if _Py_TAIL_CALL_INTERP
2012+ int opcode = OP;
2013+ (void)(opcode);
2014+ #endif
2015+ _Py_CODEUNIT* const this_instr = next_instr;
2016+ (void)this_instr;
2017+ frame->instr_ptr = next_instr;
2018+ next_instr += 4;
2019+ INSTRUCTION_STATS(OP);
2020+ _PyStackRef a;
2021+ _PyStackRef res;
2022+ // _SPECIALIZE_OP
2023+ {
2024+ uint16_t counter = read_u16(&this_instr[1].cache);
2025+ (void)counter;
2026+ SPAM();
2027+ }
2028+ /* Skip 2 cache entries */
2029+ // _DO_STUFF
2030+ {
2031+ a = stack_pointer[-2];
2032+ res = a;
2033+ }
2034+ stack_pointer[-2] = res;
2035+ stack_pointer += -1;
2036+ ASSERT_WITHIN_STACK_BOUNDS(__FILE__, __LINE__);
2037+ DISPATCH();
2038+ }
2039+ """
2040+ self .run_cases_test (input , output )
2041+
2042+ def test_recording_uop_between_real_uops_rejected (self ):
2043+ """A recording uop sandwiched between real uops is rejected."""
2044+ input = """
2045+ tier2 op(_RECORD_A, (a, b -- a, b)) {
2046+ RECORD_VALUE(a);
2047+ }
2048+ op(_FIRST, (a, b -- a, b)) {
2049+ first(a);
2050+ }
2051+ tier2 op(_RECORD_B, (a, b -- a, b)) {
2052+ RECORD_VALUE(b);
2053+ }
2054+ macro(OP) = _RECORD_A + _FIRST + _RECORD_B;
2055+ """
2056+ with self .assertRaisesRegex (SyntaxError ,
2057+ "must precede all "
2058+ "non-recording, non-specializing uops" ):
2059+ self .run_cases_test (input , "" )
2060+
2061+
2062+ class TestRecorderTableGeneration (unittest .TestCase ):
2063+
2064+ def setUp (self ) -> None :
2065+ super ().setUp ()
2066+ self .maxDiff = None
2067+ self .temp_dir = tempfile .gettempdir ()
2068+ self .temp_input_filename = os .path .join (self .temp_dir , "input.txt" )
2069+
2070+ def tearDown (self ) -> None :
2071+ try :
2072+ os .remove (self .temp_input_filename )
2073+ except FileNotFoundError :
2074+ pass
2075+ super ().tearDown ()
2076+
2077+ def generate_tables (self , input : str ) -> str :
2078+ import io
2079+ with open (self .temp_input_filename , "w+" ) as f :
2080+ f .write (parser .BEGIN_MARKER )
2081+ f .write (input )
2082+ f .write (parser .END_MARKER )
2083+ with handle_stderr ():
2084+ analysis = analyze_files ([self .temp_input_filename ])
2085+ buf = io .StringIO ()
2086+ out = CWriter (buf , 0 , False )
2087+ record_function_generator .generate_recorder_tables (analysis , out )
2088+ return buf .getvalue ()
2089+
2090+ def test_single_recording_uop_generates_count (self ):
2091+ input = """
2092+ tier2 op(_RECORD_TOS, (value -- value)) {
2093+ RECORD_VALUE(value);
2094+ }
2095+ op(_DO_STUFF, (value -- res)) {
2096+ res = value;
2097+ }
2098+ macro(OP) = _RECORD_TOS + _DO_STUFF;
2099+ """
2100+ output = self .generate_tables (input )
2101+ self .assertIn ("_RECORD_TOS_INDEX" , output )
2102+ self .assertIn ("[OP] = {1, {_RECORD_TOS_INDEX}}" , output )
2103+
2104+ def test_three_recording_uops_generate_count_3_in_order (self ):
2105+ input = """
2106+ tier2 op(_RECORD_X, (a, b, c -- a, b, c)) {
2107+ RECORD_VALUE(a);
2108+ }
2109+ tier2 op(_RECORD_Y, (a, b, c -- a, b, c)) {
2110+ RECORD_VALUE(b);
2111+ }
2112+ tier2 op(_RECORD_Z, (a, b, c -- a, b, c)) {
2113+ RECORD_VALUE(c);
2114+ }
2115+ op(_DO_STUFF, (a, b, c -- res)) {
2116+ res = a;
2117+ }
2118+ macro(OP) = _RECORD_X + _RECORD_Y + _RECORD_Z + _DO_STUFF;
2119+ """
2120+ output = self .generate_tables (input )
2121+ self .assertIn (
2122+ "[OP] = {3, {_RECORD_X_INDEX, _RECORD_Y_INDEX, _RECORD_Z_INDEX}}" ,
2123+ output ,
2124+ )
2125+
2126+ def test_four_recording_uops_rejected (self ):
2127+ input = """
2128+ tier2 op(_RECORD_A, (a, b, c, d -- a, b, c, d)) {
2129+ RECORD_VALUE(a);
2130+ }
2131+ tier2 op(_RECORD_B, (a, b, c, d -- a, b, c, d)) {
2132+ RECORD_VALUE(b);
2133+ }
2134+ tier2 op(_RECORD_C, (a, b, c, d -- a, b, c, d)) {
2135+ RECORD_VALUE(c);
2136+ }
2137+ tier2 op(_RECORD_D, (a, b, c, d -- a, b, c, d)) {
2138+ RECORD_VALUE(d);
2139+ }
2140+ op(_DO_STUFF, (a, b, c, d -- res)) {
2141+ res = a;
2142+ }
2143+ macro(OP) = _RECORD_A + _RECORD_B + _RECORD_C + _RECORD_D + _DO_STUFF;
2144+ """
2145+ with self .assertRaisesRegex (ValueError , "exceeds MAX_RECORDED_VALUES" ):
2146+ self .generate_tables (input )
2147+
19512148
19522149class TestGeneratedAbstractCases (unittest .TestCase ):
19532150 def setUp (self ) -> None :
0 commit comments