Пример #1
0
    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),
        )
Пример #2
0
    def generate_bytecodes(bytecode):
        # can we do this, ref the const to our own function?
        return_finally = Label()
        calculate = Label()

        yield Instr("LOAD_CONST", prefix)
        yield Instr('CALL_FUNCTION', 0)
        yield Instr('DUP_TOP')
        yield Instr('LOAD_CONST', Ellipsis) # or some noncached sinlgeton
        yield Instr('COMPARE_OP', Compare.IS)
        yield Instr('POP_JUMP_IF_TRUE', calculate)
        yield Instr('RETURN_VALUE')
        yield Instr('POP_TOP')

        # need to check the reurn value of the call, and return it directly before the finally block is setup
        yield calculate
        yield Instr('SETUP_FINALLY', return_finally)

        for instr in bytecode:
            yield instr

        # return value is now on the top of the stack, so we can cache it in postfix?
        yield return_finally
        yield Instr('DUP_TOP')              # copy the return value to the top
        yield Instr('LOAD_CONST', postfix)  # load a return function
        yield Instr('ROT_TWO')              # swap so it's a call
        yield Instr('CALL_FUNCTION', 1)     # invoke with the return value
        yield Instr('POP_TOP')
        yield Instr('END_FINALLY')
Пример #3
0
    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)
Пример #4
0
    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,
        )
Пример #5
0
    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'))
Пример #6
0
    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))
Пример #7
0
    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),
            ],
        )
Пример #8
0
 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),
         ],
     )
Пример #9
0
    def test_return_value(self):
        # return+return: remove second return
        #
        #     def func():
        #         return 4
        #         return 5
        code = Bytecode([
            Instr('LOAD_CONST', 4, lineno=2),
            Instr('RETURN_VALUE', lineno=2),
            Instr('LOAD_CONST', 5, lineno=3),
            Instr('RETURN_VALUE', lineno=3)
        ])
        code = ControlFlowGraph.from_bytecode(code)
        self.check(code, Instr('LOAD_CONST', 4, lineno=2),
                   Instr('RETURN_VALUE', lineno=2))

        # return+return + return+return: remove second and fourth return
        #
        #     def func():
        #         return 4
        #         return 5
        #         return 6
        #         return 7
        code = Bytecode([
            Instr('LOAD_CONST', 4, lineno=2),
            Instr('RETURN_VALUE', lineno=2),
            Instr('LOAD_CONST', 5, lineno=3),
            Instr('RETURN_VALUE', lineno=3),
            Instr('LOAD_CONST', 6, lineno=4),
            Instr('RETURN_VALUE', lineno=4),
            Instr('LOAD_CONST', 7, lineno=5),
            Instr('RETURN_VALUE', lineno=5)
        ])
        code = ControlFlowGraph.from_bytecode(code)
        self.check(code, Instr('LOAD_CONST', 4, lineno=2),
                   Instr('RETURN_VALUE', lineno=2))

        # return + JUMP_ABSOLUTE: remove JUMP_ABSOLUTE
        # while 1:
        #     return 7
        setup_loop = Label()
        return_label = Label()
        code = Bytecode([
            setup_loop,
            Instr('SETUP_LOOP', return_label, lineno=2),
            Instr('LOAD_CONST', 7, lineno=3),
            Instr('RETURN_VALUE', lineno=3),
            Instr('JUMP_ABSOLUTE', setup_loop, lineno=3),
            Instr('POP_BLOCK', lineno=3), return_label,
            Instr('LOAD_CONST', None, lineno=3),
            Instr('RETURN_VALUE', lineno=3)
        ])
        code = ControlFlowGraph.from_bytecode(code)

        end_loop = Label()
        self.check(code, Instr('SETUP_LOOP', end_loop, lineno=2),
                   Instr('LOAD_CONST', 7, lineno=3),
                   Instr('RETURN_VALUE', lineno=3), end_loop,
                   Instr('LOAD_CONST', None, lineno=3),
                   Instr('RETURN_VALUE', lineno=3))
Пример #10
0
 def test_invalid_types(self):
     code = ConcreteBytecode()
     code.append(Label())
     with self.assertRaises(ValueError):
         list(code)
     with self.assertRaises(ValueError):
         ConcreteBytecode([Label()])
Пример #11
0
    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)])
Пример #12
0
    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"),
        )
Пример #13
0
    def test_from_bytecode_loop(self):
        # for x in (1, 2, 3):
        #     if x == 2:
        #         break
        #     continue

        label_loop_start = Label()
        label_loop_exit = Label()
        label_loop_end = Label()

        code = Bytecode()
        code.extend((
            Instr("SETUP_LOOP", label_loop_end, lineno=1),
            Instr("LOAD_CONST", (1, 2, 3), lineno=1),
            Instr("GET_ITER", lineno=1),
            label_loop_start,
            Instr("FOR_ITER", label_loop_exit, lineno=1),
            Instr("STORE_NAME", "x", lineno=1),
            Instr("LOAD_NAME", "x", lineno=2),
            Instr("LOAD_CONST", 2, lineno=2),
            Instr("COMPARE_OP", Compare.EQ, lineno=2),
            Instr("POP_JUMP_IF_FALSE", label_loop_start, lineno=2),
            Instr("BREAK_LOOP", lineno=3),
            Instr("JUMP_ABSOLUTE", label_loop_start, lineno=4),
            Instr("JUMP_ABSOLUTE", label_loop_start, lineno=4),
            label_loop_exit,
            Instr("POP_BLOCK", lineno=4),
            label_loop_end,
            Instr("LOAD_CONST", None, lineno=4),
            Instr("RETURN_VALUE", lineno=4),
        ))
        blocks = ControlFlowGraph.from_bytecode(code)

        expected = [
            [Instr("SETUP_LOOP", blocks[8], lineno=1)],
            [
                Instr("LOAD_CONST", (1, 2, 3), lineno=1),
                Instr("GET_ITER", lineno=1)
            ],
            [Instr("FOR_ITER", blocks[7], lineno=1)],
            [
                Instr("STORE_NAME", "x", lineno=1),
                Instr("LOAD_NAME", "x", lineno=2),
                Instr("LOAD_CONST", 2, lineno=2),
                Instr("COMPARE_OP", Compare.EQ, lineno=2),
                Instr("POP_JUMP_IF_FALSE", blocks[2], lineno=2),
            ],
            [Instr("BREAK_LOOP", lineno=3)],
            [Instr("JUMP_ABSOLUTE", blocks[2], lineno=4)],
            [Instr("JUMP_ABSOLUTE", blocks[2], lineno=4)],
            [Instr("POP_BLOCK", lineno=4)],
            [
                Instr("LOAD_CONST", None, lineno=4),
                Instr("RETURN_VALUE", lineno=4)
            ],
        ]
        self.assertBlocksEqual(blocks, *expected)
Пример #14
0
def _(ast: If, ctx: Ctx):
    label1 = Label()
    label2 = Label()
    ctx.visit(ast.cond)
    ctx.bc.append(
        Instr("POP_JUMP_IF_FALSE", arg=label1, lineno=ast.cond.lineno + 1))
    ctx.visit(ast.iftrue)
    ctx.bc.append(
        Instr('JUMP_FORWARD', arg=label2, lineno=ast.iftrue.lineno + 1))
    ctx.bc.append(label1)
    ctx.visit(ast.iffalse)
    ctx.bc.append(label2)
Пример #15
0
    def test_from_bytecode_loop(self):
        # for x in (1, 2, 3):
        #     if x == 2:
        #         break
        #     continue

        label_loop_start = Label()
        label_loop_exit = Label()
        label_loop_end = Label()

        code = Bytecode()
        code.extend((
            Instr('SETUP_LOOP', label_loop_end, lineno=1),
            Instr('LOAD_CONST', (1, 2, 3), lineno=1),
            Instr('GET_ITER', lineno=1),
            label_loop_start,
            Instr('FOR_ITER', label_loop_exit, lineno=1),
            Instr('STORE_NAME', 'x', lineno=1),
            Instr('LOAD_NAME', 'x', lineno=2),
            Instr('LOAD_CONST', 2, lineno=2),
            Instr('COMPARE_OP', Compare.EQ, lineno=2),
            Instr('POP_JUMP_IF_FALSE', label_loop_start, lineno=2),
            Instr('BREAK_LOOP', lineno=3),
            Instr('JUMP_ABSOLUTE', label_loop_start, lineno=4),
            Instr('JUMP_ABSOLUTE', label_loop_start, lineno=4),
            label_loop_exit,
            Instr('POP_BLOCK', lineno=4),
            label_loop_end,
            Instr('LOAD_CONST', None, lineno=4),
            Instr('RETURN_VALUE', lineno=4),
        ))
        blocks = ControlFlowGraph.from_bytecode(code)

        expected = [[Instr('SETUP_LOOP', blocks[8], lineno=1)],
                    [
                        Instr('LOAD_CONST', (1, 2, 3), lineno=1),
                        Instr('GET_ITER', lineno=1)
                    ], [Instr('FOR_ITER', blocks[7], lineno=1)],
                    [
                        Instr('STORE_NAME', 'x', lineno=1),
                        Instr('LOAD_NAME', 'x', lineno=2),
                        Instr('LOAD_CONST', 2, lineno=2),
                        Instr('COMPARE_OP', Compare.EQ, lineno=2),
                        Instr('POP_JUMP_IF_FALSE', blocks[2], lineno=2)
                    ], [Instr('BREAK_LOOP', lineno=3)],
                    [Instr('JUMP_ABSOLUTE', blocks[2], lineno=4)],
                    [Instr('JUMP_ABSOLUTE', blocks[2], lineno=4)],
                    [Instr('POP_BLOCK', lineno=4)],
                    [
                        Instr('LOAD_CONST', None, lineno=4),
                        Instr('RETURN_VALUE', lineno=4)
                    ]]
        self.assertBlocksEqual(blocks, *expected)
Пример #16
0
    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)
Пример #17
0
    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, lineno=1),
            ConcreteInstr("LOAD_CONST", 0, lineno=2),
            ConcreteInstr("STORE_NAME", 1, lineno=2),
            ConcreteInstr("JUMP_FORWARD", 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(concrete), expected)
        self.assertListEqual(concrete.consts, [5, 7, None])
        self.assertListEqual(concrete.names, ["test", "x"])
        self.assertListEqual(concrete.varnames, [])
Пример #18
0
    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()
        expected = b"\x90\x01\x90\x00q\x06" + NOP * nb_nop + b"d\x00S\x00"
        self.assertEqual(code_obj.co_code, expected)
Пример #19
0
    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)])
Пример #20
0
    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 WORDCODE:
            expected = b'\x90\x01\x90\x00q\x06' + NOP * nb_nop + b'd\x00S\x00'
        else:
            expected = b'\x90\x01\x00q\x06\x00' + NOP * nb_nop + b'd\x00\x00S'
        self.assertEqual(code_obj.co_code, expected)
Пример #21
0
    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, [])
Пример #22
0
    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)
Пример #23
0
    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())
Пример #24
0
    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())
Пример #25
0
    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())
Пример #26
0
def _update_stack(code):
    yield Instr('LOAD_CONST', _local)
    yield Instr('LOAD_ATTR', ATTR_STACK)
    yield Instr('LOAD_ATTR', 'append')

    yield Instr('LOAD_FAST', LOCAL_KEY)
    yield Instr('BUILD_LIST', 0)
    yield Instr('DUP_TOP')
    yield Instr('STORE_FAST', LOCAL_CHILDREN)

    yield Instr('BUILD_TUPLE', 2)

    yield Instr('CALL_FUNCTION', 1)
    yield Instr('POP_TOP')

    label = Label()
    yield Instr('SETUP_FINALLY', label)

    yield from code

    yield label

    yield Instr('LOAD_CONST', _local)
    yield Instr('LOAD_ATTR', ATTR_STACK)
    yield Instr('LOAD_ATTR', 'pop')
    yield Instr('CALL_FUNCTION', 0)
    yield Instr('POP_TOP')

    yield Instr('END_FINALLY')
    yield Instr('LOAD_CONST', None)
    yield Instr('RETURN_VALUE')
Пример #27
0
    def make_bytecode(self, symbol_table):
        catch_block = Label()
        start_loop = Label()
        end_loop = Label()
        end_for = Label()
        end_of_function = Label()

        inner = [
            Instr("SETUP_EXCEPT", catch_block),
            Instr("SETUP_LOOP", end_for),
            Instr("LOAD_NAME", self.collection),
            Instr("GET_ITER"), start_loop,
            Instr("FOR_ITER", end_loop),
            Instr("STORE_NAME", self.variable)
        ]

        for element in self.sequence.elements:
            inner += element.make_bytecode(symbol_table)

        inner += [
            Instr("JUMP_ABSOLUTE", start_loop), end_loop,
            Instr("POP_BLOCK"), end_for,
            Instr("POP_BLOCK"),
            Instr("JUMP_FORWARD", end_of_function)
        ]

        inner += [
            catch_block,
            # In the catch block, we catch everything
            # unconditionally (this is wrong, we should filter
            # out just StopException, which is the only thing we
            # have any business catching here).
            #
            # We pop (I think?) the exception and the stack
            # frame data.
            Instr("POP_TOP"),
            Instr("POP_TOP"),
            Instr("POP_TOP"),

            # Pop the exception frame from the block stack.
            Instr("POP_EXCEPT"),
            Instr("JUMP_FORWARD", end_of_function),
            Instr("END_FINALLY"),
            end_of_function
        ]

        return inner
Пример #28
0
    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 = 'UNARY_POSITIVE'  # don't use NOP, dis.stack_effect will raise
        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)
Пример #29
0
    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 l in range(0, len(labels)):
            code.insert(offset, labels[l])
            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))
Пример #30
0
def conditional_jump_example_bytecode() -> Bytecode:
    label_else = Label()
    label_print = Label()
    byte_code = Bytecode([
        Instr("LOAD_NAME", "print"),
        Instr("LOAD_NAME", "test"),
        Instr("POP_JUMP_IF_FALSE", label_else),
        Instr("LOAD_CONST", "yes"),
        Instr("JUMP_FORWARD", label_print),
        label_else,
        Instr("LOAD_CONST", "no"),
        label_print,
        Instr("CALL_FUNCTION", 1),
        Instr("LOAD_CONST", None),
        Instr("RETURN_VALUE"),
    ])
    return byte_code