def test_jump_to_return(self): # def func(condition): # return 'yes' if condition else 'no' label_instr4 = Label() label_instr6 = Label() code = Bytecode([ Instr("LOAD_FAST", "condition"), Instr("POP_JUMP_IF_FALSE", label_instr4), Instr("LOAD_CONST", "yes"), Instr("JUMP_FORWARD", label_instr6), label_instr4, Instr("LOAD_CONST", "no"), label_instr6, Instr("RETURN_VALUE"), ]) label = Label() self.check( code, Instr("LOAD_FAST", "condition"), Instr("POP_JUMP_IF_FALSE", label), Instr("LOAD_CONST", "yes"), Instr("RETURN_VALUE"), label, Instr("LOAD_CONST", "no"), Instr("RETURN_VALUE"), )
def test_from_code(self): code = get_code(""" if test: x = 1 else: x = 2 """) bytecode = Bytecode.from_code(code) label_else = Label() label_exit = Label() self.assertEqual( bytecode, [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label_else, lineno=1), Instr("LOAD_CONST", 1, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("JUMP_FORWARD", label_exit, lineno=2), label_else, Instr("LOAD_CONST", 2, lineno=4), Instr("STORE_NAME", "x", lineno=4), label_exit, Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), ], )
def test_unconditional_jumps(self): # def func(): # if x: # if y: # func() label_instr7 = Label() code = Bytecode([ Instr("LOAD_GLOBAL", "x", lineno=2), Instr("POP_JUMP_IF_FALSE", label_instr7, lineno=2), Instr("LOAD_GLOBAL", "y", lineno=3), Instr("POP_JUMP_IF_FALSE", label_instr7, lineno=3), Instr("LOAD_GLOBAL", "func", lineno=4), Instr("CALL_FUNCTION", 0, lineno=4), Instr("POP_TOP", lineno=4), label_instr7, Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), ]) label_return = Label() self.check( code, Instr("LOAD_GLOBAL", "x", lineno=2), Instr("POP_JUMP_IF_FALSE", label_return, lineno=2), Instr("LOAD_GLOBAL", "y", lineno=3), Instr("POP_JUMP_IF_FALSE", label_return, lineno=3), Instr("LOAD_GLOBAL", "func", lineno=4), Instr("CALL_FUNCTION", 0, lineno=4), Instr("POP_TOP", lineno=4), label_return, Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), )
def test_not_jump_if_false(self): # Replace UNARY_NOT+POP_JUMP_IF_FALSE with POP_JUMP_IF_TRUE # # if not x: # y = 9 label = Label() code = Bytecode([ Instr("LOAD_NAME", "x"), Instr("UNARY_NOT"), Instr("POP_JUMP_IF_FALSE", label), Instr("LOAD_CONST", 9), Instr("STORE_NAME", "y"), label, ]) code = self.optimize_blocks(code) label = Label() self.check( code, Instr("LOAD_NAME", "x"), Instr("POP_JUMP_IF_TRUE", label), Instr("LOAD_CONST", 9), Instr("STORE_NAME", "y"), label, )
def test_iter_invalid_types(self): # Labels are not allowed in basic blocks block = BasicBlock() block.append(Label()) with self.assertRaises(ValueError): list(block) with self.assertRaises(ValueError): block.legalize(1) # Only one jump allowed and only at the end block = BasicBlock() block2 = BasicBlock() block.extend([Instr("JUMP_ABSOLUTE", block2), Instr("NOP")]) with self.assertRaises(ValueError): list(block) with self.assertRaises(ValueError): block.legalize(1) # jump target must be a BasicBlock block = BasicBlock() label = Label() block.extend([Instr("JUMP_ABSOLUTE", label)]) with self.assertRaises(ValueError): list(block) with self.assertRaises(ValueError): block.legalize(1)
def test_legalize(self): code = Bytecode() code.first_lineno = 3 code.extend([ Instr("LOAD_CONST", 7), Instr("STORE_NAME", "x"), Instr("LOAD_CONST", 8, lineno=4), Instr("STORE_NAME", "y"), Label(), SetLineno(5), Instr("LOAD_CONST", 9, lineno=6), Instr("STORE_NAME", "z"), ]) code.legalize() self.assertListEqual( code, [ Instr("LOAD_CONST", 7, lineno=3), Instr("STORE_NAME", "x", lineno=3), Instr("LOAD_CONST", 8, lineno=4), Instr("STORE_NAME", "y", lineno=4), Label(), Instr("LOAD_CONST", 9, lineno=5), Instr("STORE_NAME", "z", lineno=5), ], )
def test_invalid_types(self): code = ConcreteBytecode() code.append(Label()) with self.assertRaises(ValueError): list(code) with self.assertRaises(ValueError): code.legalize() with self.assertRaises(ValueError): ConcreteBytecode([Label()])
def test_compute_jumps_convergence(self): # Consider the following sequence of instructions: # # JUMP_ABSOLUTE Label1 # JUMP_ABSOLUTE Label2 # ...126 instructions... # Label1: Offset 254 on first pass, 256 second pass # NOP # ... many more instructions ... # Label2: Offset > 256 on first pass # # On first pass of compute_jumps(), Label2 will be at address 254, so # that value encodes into the single byte arg of JUMP_ABSOLUTE. # # On second pass compute_jumps() the instr at Label1 will have offset # of 256 so will also be given an EXTENDED_ARG. # # Thus we need to make an additional pass. This test only verifies # case where 2 passes is insufficient but three is enough. # # On Python > 3.10 we need to double the number since the offset is now # in term of instructions and not bytes. # Create code from comment above. code = Bytecode() label1 = Label() label2 = Label() nop = "NOP" code.append(Instr("JUMP_ABSOLUTE", label1)) code.append(Instr("JUMP_ABSOLUTE", label2)) # Need 254 * 2 + 2 since the arg will change by 1 instruction rather than 2 # bytes. for x in range(4, 510 if OFFSET_AS_INSTRUCTION else 254, 2): code.append(Instr(nop)) code.append(label1) code.append(Instr(nop)) for x in range( 514 if OFFSET_AS_INSTRUCTION else 256, 600 if OFFSET_AS_INSTRUCTION else 300, 2, ): code.append(Instr(nop)) code.append(label2) code.append(Instr(nop)) # This should pass by default. code.to_code() # Try with max of two passes: it should raise with self.assertRaises(RuntimeError): code.to_code(compute_jumps_passes=2)
def test_has_jump(self): label = Label() jump = Instr("JUMP_ABSOLUTE", label) self.assertTrue(jump.has_jump()) instr = Instr("LOAD_FAST", "x") self.assertFalse(instr.has_jump())
def test_is_cond_jump(self): label = Label() jump = Instr("POP_JUMP_IF_TRUE", label) self.assertTrue(jump.is_cond_jump()) instr = Instr("LOAD_FAST", "x") self.assertFalse(instr.is_cond_jump())
def test_label2(self): bytecode = Bytecode() label = Label() bytecode.extend([ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label), Instr("LOAD_CONST", 5, lineno=2), Instr("STORE_NAME", "x"), Instr("JUMP_FORWARD", label), Instr("LOAD_CONST", 7, lineno=4), Instr("STORE_NAME", "x"), label, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), ]) concrete = bytecode.to_concrete_bytecode() expected = [ ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("POP_JUMP_IF_FALSE", 14 if WORDCODE else 21, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=2), ConcreteInstr("STORE_NAME", 1, lineno=2), ConcreteInstr("JUMP_FORWARD", 4 if WORDCODE else 6, lineno=2), ConcreteInstr("LOAD_CONST", 1, lineno=4), ConcreteInstr("STORE_NAME", 1, lineno=4), ConcreteInstr("LOAD_CONST", 2, lineno=4), ConcreteInstr("RETURN_VALUE", lineno=4), ] self.assertListEqual(list(concrete), expected) self.assertListEqual(concrete.consts, [5, 7, None]) self.assertListEqual(concrete.names, ["test", "x"]) self.assertListEqual(concrete.varnames, [])
def test_extreme_compute_jumps_convergence(self): """Test of compute_jumps() requiring absurd number of passes. NOTE: This test also serves to demonstrate that there is no worst case: the number of passes can be unlimited (or, actually, limited by the size of the provided code). This is an extension of test_compute_jumps_convergence. Instead of two jumps, where the earlier gets extended after the latter, we instead generate a series of many jumps. Each pass of compute_jumps() extends one more instruction, which in turn causes the one behind it to be extended on the next pass. """ if not WORDCODE: return # N: the number of unextended instructions that can be squeezed into a # set of bytes adressable by the arg of an unextended instruction. # The answer is "128", but here's how we arrive at it (and it also # hints at how to make this work for pre-WORDCODE). max_unextended_offset = 1 << 8 unextended_branch_instr_size = 2 N = max_unextended_offset // unextended_branch_instr_size nop = "UNARY_POSITIVE" # don't use NOP, dis.stack_effect will raise # The number of jumps will be equal to the number of labels. The # number of passes of compute_jumps() required will be one greater # than this. labels = [Label() for x in range(0, 3 * N)] code = Bytecode() code.extend( Instr("JUMP_FORWARD", labels[len(labels) - x - 1]) for x in range(0, len(labels)) ) end_of_jumps = len(code) code.extend(Instr(nop) for x in range(0, N)) # Now insert the labels. The first is N instructions (i.e. 256 # bytes) after the last jump. Then they proceed to earlier positions # 4 bytes at a time. While the targets are in the range of the nop # instructions, 4 bytes is two instructions. When the targets are in # the range of JUMP_FORWARD instructions we have to allow for the fact # that the instructions will have been extended to four bytes each, so # working backwards 4 bytes per label means just one instruction per # label. offset = end_of_jumps + N for index in range(0, len(labels)): code.insert(offset, labels[index]) if offset <= end_of_jumps: offset -= 1 else: offset -= 2 code.insert(0, Instr("LOAD_CONST", 0)) del end_of_jumps code.append(Instr("RETURN_VALUE")) code.to_code(compute_jumps_passes=(len(labels) + 1))
def test_is_uncond_jump(self): label = Label() jump = Instr("JUMP_ABSOLUTE", label) self.assertTrue(jump.is_uncond_jump()) instr = Instr("POP_JUMP_IF_TRUE", label) self.assertFalse(instr.is_uncond_jump())
def test_extended_jump(self): NOP = bytes((opcode.opmap["NOP"], )) class BigInstr(ConcreteInstr): def __init__(self, size): super().__init__("NOP") self._size = size def copy(self): return self def assemble(self): return NOP * self._size # (invalid) code using jumps > 0xffff to test extended arg label = Label() nb_nop = 2**16 code = Bytecode([ Instr("JUMP_ABSOLUTE", label), BigInstr(nb_nop), label, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), ]) code_obj = code.to_code() if OFFSET_AS_INSTRUCTION: expected = b"\x90\x80q\x02" + NOP * nb_nop + b"d\x00S\x00" else: expected = b"\x90\x01\x90\x00q\x06" + NOP * nb_nop + b"d\x00S\x00" self.assertEqual(code_obj.co_code, expected)
def test_compute_jumps_convergence(self): # Consider the following sequence of instructions: # # JUMP_ABSOLUTE Label1 # JUMP_ABSOLUTE Label2 # ...126 instructions... # Label1: Offset 254 on first pass, 256 second pass # NOP # ... many more instructions ... # Label2: Offset > 256 on first pass # # On first pass of compute_jumps(), Label2 will be at address 254, so # that value encodes into the single byte arg of JUMP_ABSOLUTE. # # On second pass compute_jumps() the instr at Label1 will have offset # of 256 so will also be given an EXTENDED_ARG. # # Thus we need to make an additional pass. This test only verifies # case where 2 passes is insufficient but three is enough. if not WORDCODE: # Could be done pre-WORDCODE, but that requires 2**16 bytes of # code. return # Create code from comment above. code = Bytecode() label1 = Label() label2 = Label() nop = "NOP" code.append(Instr("JUMP_ABSOLUTE", label1)) code.append(Instr("JUMP_ABSOLUTE", label2)) for x in range(4, 254, 2): code.append(Instr(nop)) code.append(label1) code.append(Instr(nop)) for x in range(256, 300, 2): code.append(Instr(nop)) code.append(label2) code.append(Instr(nop)) # This should pass by default. code.to_code() # Try with max of two passes: it should raise with self.assertRaises(RuntimeError): code.to_code(compute_jumps_passes=2)
def test_from_code(self): code = get_code( """ if test: x = 1 else: x = 2 """ ) bytecode = Bytecode.from_code(code) label_else = Label() label_exit = Label() if sys.version_info < (3, 10): self.assertEqual( bytecode, [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label_else, lineno=1), Instr("LOAD_CONST", 1, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("JUMP_FORWARD", label_exit, lineno=2), label_else, Instr("LOAD_CONST", 2, lineno=4), Instr("STORE_NAME", "x", lineno=4), label_exit, Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), ], ) # Control flow handling appears to have changed under Python 3.10 else: self.assertEqual( bytecode, [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label_else, lineno=1), Instr("LOAD_CONST", 1, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("LOAD_CONST", None, lineno=2), Instr("RETURN_VALUE", lineno=2), label_else, Instr("LOAD_CONST", 2, lineno=4), Instr("STORE_NAME", "x", lineno=4), Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), ], )
def test_for_iter_stack_effect_computation(self): with self.subTest(): code = Bytecode() code.first_lineno = 1 lab1 = Label() lab2 = Label() code.extend([ lab1, Instr("FOR_ITER", lab2), Instr("STORE_FAST", "i"), Instr("JUMP_ABSOLUTE", lab1), lab2, ]) with self.assertRaises(RuntimeError): # Use compute_stacksize since the code is so broken that conversion # to from concrete is actually broken code.compute_stacksize(check_pre_and_post=False)
def test_jumps(self): # if test: # x = 12 # else: # x = 37 code = Bytecode() label_else = Label() label_return = Label() code.extend([ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label_else), Instr("LOAD_CONST", 12, lineno=2), Instr("STORE_NAME", "x"), Instr("JUMP_FORWARD", label_return), label_else, Instr("LOAD_CONST", 37, lineno=4), Instr("STORE_NAME", "x"), label_return, Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE"), ]) code = code.to_concrete_bytecode() expected = [ ConcreteInstr("LOAD_NAME", 0, lineno=1), ConcreteInstr("POP_JUMP_IF_FALSE", 5 if OFFSET_AS_INSTRUCTION else 10, lineno=1), ConcreteInstr("LOAD_CONST", 0, lineno=2), ConcreteInstr("STORE_NAME", 1, lineno=2), ConcreteInstr("JUMP_FORWARD", 2 if OFFSET_AS_INSTRUCTION else 4, lineno=2), ConcreteInstr("LOAD_CONST", 1, lineno=4), ConcreteInstr("STORE_NAME", 1, lineno=4), ConcreteInstr("LOAD_CONST", 2, lineno=4), ConcreteInstr("RETURN_VALUE", lineno=4), ] self.assertListEqual(list(code), expected) self.assertListEqual(code.consts, [12, 37, None]) self.assertListEqual(code.names, ["test", "x"]) self.assertListEqual(code.varnames, [])
def test_from_bytecode(self): bytecode = Bytecode() label = Label() bytecode.extend([ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label, lineno=1), Instr("LOAD_CONST", 5, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("JUMP_FORWARD", label, lineno=2), # dead code! Instr("LOAD_CONST", 7, lineno=4), Instr("STORE_NAME", "x", lineno=4), Label(), # unused label label, Label(), # unused label Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4), ]) blocks = ControlFlowGraph.from_bytecode(bytecode) label2 = blocks[3] self.assertBlocksEqual( blocks, [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label2, lineno=1), ], [ Instr("LOAD_CONST", 5, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("JUMP_FORWARD", label2, lineno=2), ], [ Instr("LOAD_CONST", 7, lineno=4), Instr("STORE_NAME", "x", lineno=4) ], [ Instr("LOAD_CONST", None, lineno=4), Instr("RETURN_VALUE", lineno=4) ], )
def test_dead_code_jump(self): label = Label() code = Bytecode([ Instr("LOAD_NAME", "x"), Instr("JUMP_ABSOLUTE", label), # dead code Instr("LOAD_NAME", "y"), Instr("STORE_NAME", "test"), label, Instr("STORE_NAME", "test"), ]) self.check(code, Instr("LOAD_NAME", "x"), Instr("STORE_NAME", "test"))
def test_invalid_arg(self): label = Label() block = BasicBlock() # EXTENDED_ARG self.assertRaises(ValueError, Instr, "EXTENDED_ARG", 0) # has_jump() self.assertRaises(TypeError, Instr, "JUMP_ABSOLUTE", 1) self.assertRaises(TypeError, Instr, "JUMP_ABSOLUTE", 1.0) Instr("JUMP_ABSOLUTE", label) Instr("JUMP_ABSOLUTE", block) # hasfree self.assertRaises(TypeError, Instr, "LOAD_DEREF", "x") Instr("LOAD_DEREF", CellVar("x")) Instr("LOAD_DEREF", FreeVar("x")) # haslocal self.assertRaises(TypeError, Instr, "LOAD_FAST", 1) Instr("LOAD_FAST", "x") # hasname self.assertRaises(TypeError, Instr, "LOAD_NAME", 1) Instr("LOAD_NAME", "x") # hasconst self.assertRaises(ValueError, Instr, "LOAD_CONST") # UNSET self.assertRaises(ValueError, Instr, "LOAD_CONST", label) self.assertRaises(ValueError, Instr, "LOAD_CONST", block) Instr("LOAD_CONST", 1.0) Instr("LOAD_CONST", object()) # hascompare self.assertRaises(TypeError, Instr, "COMPARE_OP", 1) Instr("COMPARE_OP", Compare.EQ) # HAVE_ARGUMENT self.assertRaises(ValueError, Instr, "CALL_FUNCTION", -1) self.assertRaises(TypeError, Instr, "CALL_FUNCTION", 3.0) Instr("CALL_FUNCTION", 3) # test maximum argument self.assertRaises(ValueError, Instr, "CALL_FUNCTION", 2147483647 + 1) instr = Instr("CALL_FUNCTION", 2147483647) self.assertEqual(instr.arg, 2147483647) # not HAVE_ARGUMENT self.assertRaises(ValueError, Instr, "NOP", 0) Instr("NOP")
def test_jump_if_true_to_jump_if_false(self): # Replace JUMP_IF_TRUE_OR_POP jumping to POP_JUMP_IF_FALSE <target> # with POP_JUMP_IF_TRUE <offset after the second POP_JUMP_IF_FALSE> # # if x or y: # z = 1 label_instr3 = Label() label_instr7 = Label() code = Bytecode([ Instr("LOAD_NAME", "x"), Instr("JUMP_IF_TRUE_OR_POP", label_instr3), Instr("LOAD_NAME", "y"), label_instr3, Instr("POP_JUMP_IF_FALSE", label_instr7), Instr("LOAD_CONST", 1), Instr("STORE_NAME", "z"), label_instr7, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), ]) label_instr4 = Label() label_instr7 = Label() self.check( code, Instr("LOAD_NAME", "x"), Instr("POP_JUMP_IF_TRUE", label_instr4), Instr("LOAD_NAME", "y"), Instr("POP_JUMP_IF_FALSE", label_instr7), label_instr4, Instr("LOAD_CONST", 1), Instr("STORE_NAME", "z"), label_instr7, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), )
def mk_if_then_else(depth): instructions = [] for i in range(depth): label_else = Label() instructions.extend([ Instr("LOAD_FAST", "x"), Instr("POP_JUMP_IF_FALSE", label_else), Instr("LOAD_GLOBAL", "f{}".format(i)), Instr("RETURN_VALUE"), label_else, ]) instructions.extend( [Instr("LOAD_CONST", None), Instr("RETURN_VALUE")]) return instructions
def test_unconditional_jump_to_return(self): # def func(): # if test: # if test2: # x = 10 # else: # x = 20 # else: # x = 30 label_instr11 = Label() label_instr14 = Label() label_instr7 = Label() code = Bytecode([ Instr("LOAD_GLOBAL", "test", lineno=2), Instr("POP_JUMP_IF_FALSE", label_instr11, lineno=2), Instr("LOAD_GLOBAL", "test2", lineno=3), Instr("POP_JUMP_IF_FALSE", label_instr7, lineno=3), Instr("LOAD_CONST", 10, lineno=4), Instr("STORE_FAST", "x", lineno=4), Instr("JUMP_ABSOLUTE", label_instr14, lineno=4), label_instr7, Instr("LOAD_CONST", 20, lineno=6), Instr("STORE_FAST", "x", lineno=6), Instr("JUMP_FORWARD", label_instr14, lineno=6), label_instr11, Instr("LOAD_CONST", 30, lineno=8), Instr("STORE_FAST", "x", lineno=8), label_instr14, Instr("LOAD_CONST", None, lineno=8), Instr("RETURN_VALUE", lineno=8), ]) label1 = Label() label3 = Label() label4 = Label() self.check( code, Instr("LOAD_GLOBAL", "test", lineno=2), Instr("POP_JUMP_IF_FALSE", label3, lineno=2), Instr("LOAD_GLOBAL", "test2", lineno=3), Instr("POP_JUMP_IF_FALSE", label1, lineno=3), Instr("LOAD_CONST", 10, lineno=4), Instr("STORE_FAST", "x", lineno=4), Instr("JUMP_ABSOLUTE", label4, lineno=4), label1, Instr("LOAD_CONST", 20, lineno=6), Instr("STORE_FAST", "x", lineno=6), Instr("JUMP_FORWARD", label4, lineno=6), label3, Instr("LOAD_CONST", 30, lineno=8), Instr("STORE_FAST", "x", lineno=8), label4, Instr("LOAD_CONST", None, lineno=8), Instr("RETURN_VALUE", lineno=8), )
def test_to_bytecode(self): # if test: # x = 2 # x = 5 blocks = ControlFlowGraph() blocks.add_block() blocks.add_block() blocks[0].extend( [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", blocks[2], lineno=1), ] ) blocks[1].extend( [ Instr("LOAD_CONST", 5, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("JUMP_FORWARD", blocks[2], lineno=2), ] ) blocks[2].extend( [ Instr("LOAD_CONST", 7, lineno=3), Instr("STORE_NAME", "x", lineno=3), Instr("LOAD_CONST", None, lineno=3), Instr("RETURN_VALUE", lineno=3), ] ) bytecode = blocks.to_bytecode() label = Label() self.assertEqual( bytecode, [ Instr("LOAD_NAME", "test", lineno=1), Instr("POP_JUMP_IF_FALSE", label, lineno=1), Instr("LOAD_CONST", 5, lineno=2), Instr("STORE_NAME", "x", lineno=2), Instr("JUMP_FORWARD", label, lineno=2), label, Instr("LOAD_CONST", 7, lineno=3), Instr("STORE_NAME", "x", lineno=3), Instr("LOAD_CONST", None, lineno=3), Instr("RETURN_VALUE", lineno=3), ], )
def test_uncond_jump_to_uncond_jump(self): # Replace JUMP_FORWARD t1 jumping to JUMP_FORWARD t2 # with JUMP_ABSOLUTE t2 label = Label() label2 = Label() label3 = Label() label4 = Label() code = Bytecode([ Instr("LOAD_NAME", "test"), Instr("POP_JUMP_IF_TRUE", label), # redundant jump Instr("JUMP_FORWARD", label2), label, Instr("LOAD_CONST", 1), Instr("STORE_NAME", "x"), Instr("LOAD_NAME", "test"), Instr("POP_JUMP_IF_TRUE", label3), label2, Instr("JUMP_FORWARD", label4), label3, Instr("LOAD_CONST", 1), Instr("STORE_NAME", "x"), label4, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), ]) label = Label() label3 = Label() label4 = Label() self.check( code, Instr("LOAD_NAME", "test"), Instr("POP_JUMP_IF_TRUE", label), # JUMP_FORWARD label2 was replaced with JUMP_ABSOLUTE label4 Instr("JUMP_ABSOLUTE", label4), label, Instr("LOAD_CONST", 1), Instr("STORE_NAME", "x"), Instr("LOAD_NAME", "test"), Instr("POP_JUMP_IF_TRUE", label3), Instr("JUMP_FORWARD", label4), label3, Instr("LOAD_CONST", 1), Instr("STORE_NAME", "x"), label4, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), )
def test_label(self): code = Bytecode() label = Label() code.extend([ Instr("LOAD_CONST", "hello", lineno=1), Instr("JUMP_FORWARD", label, lineno=1), label, Instr("POP_TOP", lineno=1), ]) code = code.to_concrete_bytecode() expected = [ ConcreteInstr("LOAD_CONST", 0, lineno=1), ConcreteInstr("JUMP_FORWARD", 0, lineno=1), ConcreteInstr("POP_TOP", lineno=1), ] self.assertListEqual(list(code), expected) self.assertListEqual(code.consts, ["hello"])
def test_compare_op_unary_not(self): for op, not_op in ( (Compare.IN, Compare.NOT_IN), # in => not in (Compare.NOT_IN, Compare.IN), # not in => in (Compare.IS, Compare.IS_NOT), # is => is not (Compare.IS_NOT, Compare.IS), # is not => is ): code = Bytecode([ Instr("LOAD_NAME", "a"), Instr("LOAD_NAME", "b"), Instr("COMPARE_OP", op), Instr("UNARY_NOT"), Instr("STORE_NAME", "x"), ]) self.check( code, Instr("LOAD_NAME", "a"), Instr("LOAD_NAME", "b"), Instr("COMPARE_OP", not_op), Instr("STORE_NAME", "x"), ) # don't optimize: # x = not (a and b is True) label_instr5 = Label() code = Bytecode([ Instr("LOAD_NAME", "a"), Instr("JUMP_IF_FALSE_OR_POP", label_instr5), Instr("LOAD_NAME", "b"), Instr("LOAD_CONST", True), Instr("COMPARE_OP", Compare.IS), label_instr5, Instr("UNARY_NOT"), Instr("STORE_NAME", "x"), Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), ]) self.check_dont_optimize(code)
def test_label_at_the_end(self): label = Label() code = Bytecode([ Instr("LOAD_NAME", "x"), Instr("UNARY_NOT"), Instr("POP_JUMP_IF_FALSE", label), Instr("LOAD_CONST", 9), Instr("STORE_NAME", "y"), label, ]) cfg = ControlFlowGraph.from_bytecode(code) self.assertBlocksEqual( cfg, [ Instr("LOAD_NAME", "x"), Instr("UNARY_NOT"), Instr("POP_JUMP_IF_FALSE", cfg[2]), ], [Instr("LOAD_CONST", 9), Instr("STORE_NAME", "y")], [], )
def test_jump_if_false_to_jump_if_false(self): # Replace JUMP_IF_FALSE_OR_POP jumping to POP_JUMP_IF_FALSE <label> # with POP_JUMP_IF_FALSE <label> # # while n > 0 and start > 3: # func() if sys.version_info < (3, 8): label_instr1 = Label() label_instr15 = Label() label_instr17 = Label() label_instr9 = Label() code = Bytecode([ Instr("SETUP_LOOP", label_instr17), label_instr1, Instr("LOAD_NAME", "n"), Instr("LOAD_CONST", 0), Instr("COMPARE_OP", Compare.GT), # JUMP_IF_FALSE_OR_POP jumps to POP_JUMP_IF_FALSE # which jumps to label_instr15 Instr("JUMP_IF_FALSE_OR_POP", label_instr9), Instr("LOAD_NAME", "start"), Instr("LOAD_CONST", 3), Instr("COMPARE_OP", Compare.GT), label_instr9, Instr("POP_JUMP_IF_FALSE", label_instr15), Instr("LOAD_NAME", "func"), Instr("CALL_FUNCTION", 0), Instr("POP_TOP"), Instr("JUMP_ABSOLUTE", label_instr1), label_instr15, Instr("POP_BLOCK"), label_instr17, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), ]) label_instr1 = Label() label_instr14 = Label() label_instr16 = Label() self.check( code, Instr("SETUP_LOOP", label_instr16), label_instr1, Instr("LOAD_NAME", "n"), Instr("LOAD_CONST", 0), Instr("COMPARE_OP", Compare.GT), Instr("POP_JUMP_IF_FALSE", label_instr14), Instr("LOAD_NAME", "start"), Instr("LOAD_CONST", 3), Instr("COMPARE_OP", Compare.GT), Instr("POP_JUMP_IF_FALSE", label_instr14), Instr("LOAD_NAME", "func"), Instr("CALL_FUNCTION", 0), Instr("POP_TOP"), Instr("JUMP_ABSOLUTE", label_instr1), label_instr14, Instr("POP_BLOCK"), label_instr16, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), ) else: label_instr1 = Label() label_instr15 = Label() label_instr9 = Label() code = Bytecode([ label_instr1, Instr("LOAD_NAME", "n"), Instr("LOAD_CONST", 0), Instr("COMPARE_OP", Compare.GT), # JUMP_IF_FALSE_OR_POP jumps to POP_JUMP_IF_FALSE # which jumps to label_instr15 Instr("JUMP_IF_FALSE_OR_POP", label_instr9), Instr("LOAD_NAME", "start"), Instr("LOAD_CONST", 3), Instr("COMPARE_OP", Compare.GT), label_instr9, Instr("POP_JUMP_IF_FALSE", label_instr15), Instr("LOAD_NAME", "func"), Instr("CALL_FUNCTION", 0), Instr("POP_TOP"), Instr("JUMP_ABSOLUTE", label_instr1), label_instr15, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), ]) label_instr1 = Label() label_instr14 = Label() self.check( code, label_instr1, Instr("LOAD_NAME", "n"), Instr("LOAD_CONST", 0), Instr("COMPARE_OP", Compare.GT), Instr("POP_JUMP_IF_FALSE", label_instr14), Instr("LOAD_NAME", "start"), Instr("LOAD_CONST", 3), Instr("COMPARE_OP", Compare.GT), Instr("POP_JUMP_IF_FALSE", label_instr14), Instr("LOAD_NAME", "func"), Instr("CALL_FUNCTION", 0), Instr("POP_TOP"), Instr("JUMP_ABSOLUTE", label_instr1), label_instr14, Instr("LOAD_CONST", None), Instr("RETURN_VALUE"), )