| 
									
										
										
										
											2013-05-07 08:28:21 +10:00
										 |  |  | """bytecode_helper - support tools for testing correct bytecode generation""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import unittest | 
					
						
							|  |  |  | import dis | 
					
						
							|  |  |  | import io | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  | import opcode | 
					
						
							| 
									
										
										
										
											2024-03-28 09:40:37 +01:00
										 |  |  | try: | 
					
						
							|  |  |  |     import _testinternalcapi | 
					
						
							|  |  |  | except ImportError: | 
					
						
							|  |  |  |     _testinternalcapi = None | 
					
						
							| 
									
										
										
										
											2013-05-07 08:28:21 +10:00
										 |  |  | 
 | 
					
						
							|  |  |  | _UNSPECIFIED = object() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-12-13 12:00:21 +00:00
										 |  |  | def instructions_with_positions(instrs, co_positions): | 
					
						
							|  |  |  |     # Return (instr, positions) pairs from the instrs list and co_positions | 
					
						
							|  |  |  |     # iterator. The latter contains items for cache lines and the former | 
					
						
							|  |  |  |     # doesn't, so those need to be skipped. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     co_positions = co_positions or iter(()) | 
					
						
							|  |  |  |     for instr in instrs: | 
					
						
							|  |  |  |         yield instr, next(co_positions, ()) | 
					
						
							|  |  |  |         for _, size, _ in (instr.cache_info or ()): | 
					
						
							|  |  |  |             for i in range(size): | 
					
						
							|  |  |  |                 next(co_positions, ()) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-05-07 08:28:21 +10:00
										 |  |  | class BytecodeTestCase(unittest.TestCase): | 
					
						
							|  |  |  |     """Custom assertion methods for inspecting bytecode.""" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_disassembly_as_string(self, co): | 
					
						
							|  |  |  |         s = io.StringIO() | 
					
						
							|  |  |  |         dis.dis(co, file=s) | 
					
						
							|  |  |  |         return s.getvalue() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def assertInBytecode(self, x, opname, argval=_UNSPECIFIED): | 
					
						
							| 
									
										
										
										
											2019-09-12 10:02:59 +01:00
										 |  |  |         """Returns instr if opname is found, otherwise throws AssertionError""" | 
					
						
							| 
									
										
										
										
											2022-09-25 15:55:53 -04:00
										 |  |  |         self.assertIn(opname, dis.opmap) | 
					
						
							| 
									
										
										
										
											2013-05-07 08:28:21 +10:00
										 |  |  |         for instr in dis.get_instructions(x): | 
					
						
							|  |  |  |             if instr.opname == opname: | 
					
						
							|  |  |  |                 if argval is _UNSPECIFIED or instr.argval == argval: | 
					
						
							|  |  |  |                     return instr | 
					
						
							|  |  |  |         disassembly = self.get_disassembly_as_string(x) | 
					
						
							|  |  |  |         if argval is _UNSPECIFIED: | 
					
						
							|  |  |  |             msg = '%s not found in bytecode:\n%s' % (opname, disassembly) | 
					
						
							|  |  |  |         else: | 
					
						
							|  |  |  |             msg = '(%s,%r) not found in bytecode:\n%s' | 
					
						
							|  |  |  |             msg = msg % (opname, argval, disassembly) | 
					
						
							|  |  |  |         self.fail(msg) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def assertNotInBytecode(self, x, opname, argval=_UNSPECIFIED): | 
					
						
							| 
									
										
										
										
											2019-09-12 10:02:59 +01:00
										 |  |  |         """Throws AssertionError if opname is found""" | 
					
						
							| 
									
										
										
										
											2022-09-25 15:55:53 -04:00
										 |  |  |         self.assertIn(opname, dis.opmap) | 
					
						
							| 
									
										
										
										
											2013-05-07 08:28:21 +10:00
										 |  |  |         for instr in dis.get_instructions(x): | 
					
						
							|  |  |  |             if instr.opname == opname: | 
					
						
							| 
									
										
										
										
											2016-01-19 08:48:48 +01:00
										 |  |  |                 disassembly = self.get_disassembly_as_string(x) | 
					
						
							|  |  |  |                 if argval is _UNSPECIFIED: | 
					
						
							| 
									
										
										
										
											2013-05-07 08:28:21 +10:00
										 |  |  |                     msg = '%s occurs in bytecode:\n%s' % (opname, disassembly) | 
					
						
							| 
									
										
										
										
											2020-12-17 16:30:29 -08:00
										 |  |  |                     self.fail(msg) | 
					
						
							| 
									
										
										
										
											2013-05-07 08:28:21 +10:00
										 |  |  |                 elif instr.argval == argval: | 
					
						
							|  |  |  |                     msg = '(%s,%r) occurs in bytecode:\n%s' | 
					
						
							|  |  |  |                     msg = msg % (opname, argval, disassembly) | 
					
						
							| 
									
										
										
										
											2020-12-17 16:30:29 -08:00
										 |  |  |                     self.fail(msg) | 
					
						
							| 
									
										
										
										
											2022-08-24 11:02:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-14 13:56:40 +00:00
										 |  |  | class CompilationStepTestCase(unittest.TestCase): | 
					
						
							| 
									
										
										
										
											2022-08-24 11:02:53 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |     HAS_ARG = set(dis.hasarg) | 
					
						
							|  |  |  |     HAS_TARGET = set(dis.hasjrel + dis.hasjabs + dis.hasexc) | 
					
						
							|  |  |  |     HAS_ARG_OR_TARGET = HAS_ARG.union(HAS_TARGET) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-28 11:29:32 +00:00
										 |  |  |     class Label: | 
					
						
							|  |  |  |         pass | 
					
						
							| 
									
										
										
										
											2022-08-24 11:02:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |     def assertInstructionsMatch(self, actual_seq, expected): | 
					
						
							|  |  |  |         # get an InstructionSequence and an expected list, where each | 
					
						
							| 
									
										
										
										
											2024-08-12 12:16:41 +08:00
										 |  |  |         # entry is a label or an instruction tuple. Construct an expected | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |         # instruction sequence and compare with the one given. | 
					
						
							| 
									
										
										
										
											2022-11-14 13:56:40 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |         self.assertIsInstance(expected, list) | 
					
						
							|  |  |  |         actual = actual_seq.get_instructions() | 
					
						
							|  |  |  |         expected = self.seq_from_insts(expected).get_instructions() | 
					
						
							| 
									
										
										
										
											2022-11-14 13:56:40 +00:00
										 |  |  |         self.assertEqual(len(actual), len(expected)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # compare instructions | 
					
						
							|  |  |  |         for act, exp in zip(actual, expected): | 
					
						
							|  |  |  |             if isinstance(act, int): | 
					
						
							|  |  |  |                 self.assertEqual(exp, act) | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             self.assertIsInstance(exp, tuple) | 
					
						
							|  |  |  |             self.assertIsInstance(act, tuple) | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |             idx = max([p[0] for p in enumerate(exp) if p[1] != -1]) | 
					
						
							|  |  |  |             self.assertEqual(exp[:idx], act[:idx]) | 
					
						
							| 
									
										
										
										
											2022-08-24 11:02:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-28 11:29:32 +00:00
										 |  |  |     def resolveAndRemoveLabels(self, insts): | 
					
						
							|  |  |  |         idx = 0 | 
					
						
							|  |  |  |         res = [] | 
					
						
							|  |  |  |         for item in insts: | 
					
						
							|  |  |  |             assert isinstance(item, (self.Label, tuple)) | 
					
						
							|  |  |  |             if isinstance(item, self.Label): | 
					
						
							|  |  |  |                 item.value = idx | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 idx += 1 | 
					
						
							|  |  |  |                 res.append(item) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         return res | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |     def seq_from_insts(self, insts): | 
					
						
							|  |  |  |         labels = {item for item in insts if isinstance(item, self.Label)} | 
					
						
							|  |  |  |         for i, lbl in enumerate(labels): | 
					
						
							|  |  |  |             lbl.value = i | 
					
						
							| 
									
										
										
										
											2022-08-24 11:02:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |         seq = _testinternalcapi.new_instruction_sequence() | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  |         for item in insts: | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |             if isinstance(item, self.Label): | 
					
						
							|  |  |  |                 seq.use_label(item.value) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 op = item[0] | 
					
						
							|  |  |  |                 if isinstance(op, str): | 
					
						
							|  |  |  |                     op = opcode.opmap[op] | 
					
						
							|  |  |  |                 arg, *loc = item[1:] | 
					
						
							|  |  |  |                 if isinstance(arg, self.Label): | 
					
						
							|  |  |  |                     arg = arg.value | 
					
						
							|  |  |  |                 loc = loc + [-1] * (4 - len(loc)) | 
					
						
							|  |  |  |                 seq.addop(op, arg or 0, *loc) | 
					
						
							|  |  |  |         return seq | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def check_instructions(self, insts): | 
					
						
							|  |  |  |         for inst in insts: | 
					
						
							|  |  |  |             if isinstance(inst, self.Label): | 
					
						
							|  |  |  |                 continue | 
					
						
							|  |  |  |             op, arg, *loc = inst | 
					
						
							|  |  |  |             if isinstance(op, str): | 
					
						
							|  |  |  |                 op = opcode.opmap[op] | 
					
						
							|  |  |  |             self.assertEqual(op in opcode.hasarg, | 
					
						
							|  |  |  |                              arg is not None, | 
					
						
							|  |  |  |                              f"{opcode.opname[op]=} {arg=}") | 
					
						
							|  |  |  |             self.assertTrue(all(isinstance(l, int) for l in loc)) | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-08-24 11:02:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-28 09:40:37 +01:00
										 |  |  | @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") | 
					
						
							| 
									
										
										
										
											2022-11-14 13:56:40 +00:00
										 |  |  | class CodegenTestCase(CompilationStepTestCase): | 
					
						
							| 
									
										
										
										
											2022-08-24 11:02:53 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-14 13:56:40 +00:00
										 |  |  |     def generate_code(self, ast): | 
					
						
							| 
									
										
										
										
											2024-03-28 09:40:37 +01:00
										 |  |  |         insts, _ = _testinternalcapi.compiler_codegen(ast, "my_file.py", 0) | 
					
						
							| 
									
										
										
										
											2022-11-14 13:56:40 +00:00
										 |  |  |         return insts | 
					
						
							| 
									
										
										
										
											2022-08-24 11:02:53 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-28 09:40:37 +01:00
										 |  |  | @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") | 
					
						
							| 
									
										
										
										
											2022-11-14 13:56:40 +00:00
										 |  |  | class CfgOptimizationTestCase(CompilationStepTestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-04-17 16:42:04 +01:00
										 |  |  |     def get_optimized(self, seq, consts, nlocals=0): | 
					
						
							|  |  |  |         insts = _testinternalcapi.optimize_cfg(seq, consts, nlocals) | 
					
						
							| 
									
										
										
										
											2022-11-14 13:56:40 +00:00
										 |  |  |         return insts, consts | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-28 09:40:37 +01:00
										 |  |  | @unittest.skipIf(_testinternalcapi is None, "requires _testinternalcapi") | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  | class AssemblerTestCase(CompilationStepTestCase): | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     def get_code_object(self, filename, insts, metadata): | 
					
						
							| 
									
										
										
										
											2024-03-28 09:40:37 +01:00
										 |  |  |         co = _testinternalcapi.assemble_code_object(filename, insts, metadata) | 
					
						
							| 
									
										
										
										
											2023-05-01 22:29:30 +01:00
										 |  |  |         return co |