|
3 | 3 | import unittest
|
4 | 4 | import dis
|
5 | 5 | import io
|
| 6 | +from _testinternalcapi import optimize_cfg |
6 | 7 |
|
7 | 8 | _UNSPECIFIED = object()
|
8 | 9 |
|
@@ -40,3 +41,95 @@ def assertNotInBytecode(self, x, opname, argval=_UNSPECIFIED):
|
40 | 41 | msg = '(%s,%r) occurs in bytecode:\n%s'
|
41 | 42 | msg = msg % (opname, argval, disassembly)
|
42 | 43 | self.fail(msg)
|
| 44 | + |
| 45 | + |
| 46 | +class CfgOptimizationTestCase(unittest.TestCase): |
| 47 | + |
| 48 | + HAS_ARG = set(dis.hasarg) |
| 49 | + HAS_TARGET = set(dis.hasjrel + dis.hasjabs + dis.hasexc) |
| 50 | + HAS_ARG_OR_TARGET = HAS_ARG.union(HAS_TARGET) |
| 51 | + |
| 52 | + def setUp(self): |
| 53 | + self.last_label = 0 |
| 54 | + |
| 55 | + def Label(self): |
| 56 | + self.last_label += 1 |
| 57 | + return self.last_label |
| 58 | + |
| 59 | + def complete_insts_info(self, insts): |
| 60 | + # fill in omitted fields in location, and oparg 0 for ops with no arg. |
| 61 | + instructions = [] |
| 62 | + for item in insts: |
| 63 | + if isinstance(item, int): |
| 64 | + instructions.append(item) |
| 65 | + else: |
| 66 | + assert isinstance(item, tuple) |
| 67 | + inst = list(reversed(item)) |
| 68 | + opcode = dis.opmap[inst.pop()] |
| 69 | + oparg = inst.pop() if opcode in self.HAS_ARG_OR_TARGET else 0 |
| 70 | + loc = inst + [-1] * (4 - len(inst)) |
| 71 | + instructions.append((opcode, oparg, *loc)) |
| 72 | + return instructions |
| 73 | + |
| 74 | + def normalize_insts(self, insts): |
| 75 | + """ Map labels to instruction index. |
| 76 | + Remove labels which are not used as jump targets. |
| 77 | + """ |
| 78 | + labels_map = {} |
| 79 | + targets = set() |
| 80 | + idx = 1 |
| 81 | + for item in insts: |
| 82 | + assert isinstance(item, (int, tuple)) |
| 83 | + if isinstance(item, tuple): |
| 84 | + opcode, oparg, *_ = item |
| 85 | + if dis.opmap.get(opcode, opcode) in self.HAS_TARGET: |
| 86 | + targets.add(oparg) |
| 87 | + idx += 1 |
| 88 | + elif isinstance(item, int): |
| 89 | + assert item not in labels_map, "label reused" |
| 90 | + labels_map[item] = idx |
| 91 | + |
| 92 | + res = [] |
| 93 | + for item in insts: |
| 94 | + if isinstance(item, int) and item in targets: |
| 95 | + if not res or labels_map[item] != res[-1]: |
| 96 | + res.append(labels_map[item]) |
| 97 | + elif isinstance(item, tuple): |
| 98 | + opcode, oparg, *loc = item |
| 99 | + opcode = dis.opmap.get(opcode, opcode) |
| 100 | + if opcode in self.HAS_TARGET: |
| 101 | + arg = labels_map[oparg] |
| 102 | + else: |
| 103 | + arg = oparg if opcode in self.HAS_TARGET else None |
| 104 | + opcode = dis.opname[opcode] |
| 105 | + res.append((opcode, arg, *loc)) |
| 106 | + return res |
| 107 | + |
| 108 | + def get_optimized(self, insts, consts): |
| 109 | + insts = self.complete_insts_info(insts) |
| 110 | + insts = optimize_cfg(insts, consts) |
| 111 | + return insts, consts |
| 112 | + |
| 113 | + def compareInstructions(self, actual_, expected_): |
| 114 | + # get two lists where each entry is a label or |
| 115 | + # an instruction tuple. Compare them, while mapping |
| 116 | + # each actual label to a corresponding expected label |
| 117 | + # based on their locations. |
| 118 | + |
| 119 | + self.assertIsInstance(actual_, list) |
| 120 | + self.assertIsInstance(expected_, list) |
| 121 | + |
| 122 | + actual = self.normalize_insts(actual_) |
| 123 | + expected = self.normalize_insts(expected_) |
| 124 | + self.assertEqual(len(actual), len(expected)) |
| 125 | + |
| 126 | + # compare instructions |
| 127 | + for act, exp in zip(actual, expected): |
| 128 | + if isinstance(act, int): |
| 129 | + self.assertEqual(exp, act) |
| 130 | + continue |
| 131 | + self.assertIsInstance(exp, tuple) |
| 132 | + self.assertIsInstance(act, tuple) |
| 133 | + # pad exp with -1's (if location info is incomplete) |
| 134 | + exp += (-1,) * (len(act) - len(exp)) |
| 135 | + self.assertEqual(exp, act) |
0 commit comments