Ejemplo n.º 1
0
def split_blocks_at_errors(blocks: List[BasicBlock],
                           default_error_handler: BasicBlock,
                           func: str) -> List[BasicBlock]:
    new_blocks = []  # type: List[BasicBlock]

    # First split blocks on ops that may raise.
    for block in blocks:
        ops = block.ops
        block.ops = []
        cur_block = block
        new_blocks.append(cur_block)

        # If the block has an error handler specified, use it. Otherwise
        # fall back to the default.
        error_label = block.error_handler or default_error_handler
        block.error_handler = None

        for op in ops:
            cur_block.ops.append(op)
            if isinstance(op, RegisterOp) and op.error_kind != ERR_NEVER:
                # Split
                new_block = BasicBlock()
                new_blocks.append(new_block)

                if op.error_kind == ERR_MAGIC:
                    # Op returns an error value on error that depends on result RType.
                    variant = Branch.IS_ERROR
                    negated = False
                elif op.error_kind == ERR_FALSE:
                    # Op returns a C false value on error.
                    variant = Branch.BOOL_EXPR
                    negated = True
                else:
                    assert False, 'unknown error kind %d' % op.error_kind

                # Void ops can't generate errors since error is always
                # indicated by a special value stored in a register.
                assert not op.is_void, "void op generating errors?"

                branch = Branch(op,
                                true_label=error_label,
                                false_label=new_block,
                                op=variant,
                                line=op.line)
                branch.negated = negated
                if op.line != NO_TRACEBACK_LINE_NO:
                    branch.traceback_entry = (func, op.line)
                cur_block.ops.append(branch)
                cur_block = new_block

    return new_blocks
Ejemplo n.º 2
0
    def gen_cleanup(self, builder: 'IRBuilder', line: int) -> None:
        # Do an error branch on the return value register, which
        # may be undefined. This will allow it to be properly
        # decrefed if it is not null. This is kind of a hack.
        if self.ret_reg:
            target = BasicBlock()
            builder.add(Branch(self.ret_reg, target, target, Branch.IS_ERROR))
            builder.activate_block(target)

        # Restore the old exc_info
        target, cleanup = BasicBlock(), BasicBlock()
        builder.add(Branch(self.saved, target, cleanup, Branch.IS_ERROR))
        builder.activate_block(cleanup)
        builder.primitive_op(restore_exc_info_op, [self.saved], line)
        builder.goto_and_activate(target)
Ejemplo n.º 3
0
 def test_branch(self) -> None:
     self.assert_emit(Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL_EXPR),
                      """if (cpy_r_b) {
                             goto CPyL8;
                         } else
                             goto CPyL9;
                      """)
     b = Branch(self.b, BasicBlock(8), BasicBlock(9), Branch.BOOL_EXPR)
     b.negated = True
     self.assert_emit(b,
                      """if (!cpy_r_b) {
                             goto CPyL8;
                         } else
                             goto CPyL9;
                      """)
Ejemplo n.º 4
0
 def test_branch_eq(self) -> None:
     self.assert_emit(
         Branch(self.n, self.m, Label(8), Label(9), Branch.INT_EQ),
         """if (CPyTagged_IsEq(cpy_r_n, cpy_r_m))
                             goto CPyL8;
                         else
                             goto CPyL9;
                      """)
     b = Branch(self.n, self.m, Label(8), Label(9), Branch.INT_LT)
     b.negated = True
     self.assert_emit(
         b, """if (!CPyTagged_IsLt(cpy_r_n, cpy_r_m))
                             goto CPyL8;
                         else
                             goto CPyL9;
                      """)
Ejemplo n.º 5
0
 def gen_condition(self) -> None:
     # We call __next__ on the iterator and check to see if the return value
     # is NULL, which signals either the end of the Iterable being traversed
     # or an exception being raised. Note that Branch.IS_ERROR checks only
     # for NULL (an exception does not necessarily have to be raised).
     builder = self.builder
     line = self.line
     self.next_reg = builder.primitive_op(next_op, [builder.read(self.iter_target, line)], line)
     builder.add(Branch(self.next_reg, self.loop_exit, self.body_block, Branch.IS_ERROR))
Ejemplo n.º 6
0
    def add_bool_branch(self, value: Value, true: BasicBlock,
                        false: BasicBlock) -> None:
        if is_runtime_subtype(value.type, int_rprimitive):
            zero = self.add(LoadInt(0))
            value = self.binary_op(value, zero, '!=', value.line)
        elif is_same_type(value.type, list_rprimitive):
            length = self.primitive_op(list_len_op, [value], value.line)
            zero = self.add(LoadInt(0))
            value = self.binary_op(length, zero, '!=', value.line)
        elif (isinstance(value.type, RInstance)
              and value.type.class_ir.is_ext_class
              and value.type.class_ir.has_method('__bool__')):
            # Directly call the __bool__ method on classes that have it.
            value = self.gen_method_call(value, '__bool__', [],
                                         bool_rprimitive, value.line)
        else:
            value_type = optional_value_type(value.type)
            if value_type is not None:
                is_none = self.binary_op(value, self.none_object(), 'is not',
                                         value.line)
                branch = Branch(is_none, true, false, Branch.BOOL_EXPR)
                self.add(branch)
                always_truthy = False
                if isinstance(value_type, RInstance):
                    # check whether X.__bool__ is always just the default (object.__bool__)
                    if (not value_type.class_ir.has_method('__bool__') and
                            value_type.class_ir.is_method_final('__bool__')):
                        always_truthy = True

                if not always_truthy:
                    # Optional[X] where X may be falsey and requires a check
                    branch.true = BasicBlock()
                    self.activate_block(branch.true)
                    # unbox_or_cast instead of coerce because we want the
                    # type to change even if it is a subtype.
                    remaining = self.unbox_or_cast(value, value_type,
                                                   value.line)
                    self.add_bool_branch(remaining, true, false)
                return
            elif not is_same_type(value.type, bool_rprimitive):
                value = self.primitive_op(bool_op, [value], value.line)
        self.add(Branch(value, true, false, Branch.BOOL_EXPR))
Ejemplo n.º 7
0
 def finally_body() -> None:
     out_block, exit_block = BasicBlock(), BasicBlock()
     builder.add(
         Branch(builder.read(exc), exit_block, out_block, Branch.BOOL_EXPR)
     )
     builder.activate_block(exit_block)
     none = builder.none_object()
     builder.py_call(
         builder.read(exit_), [builder.read(mgr), none, none, none], line
     )
     builder.goto_and_activate(out_block)
Ejemplo n.º 8
0
def try_finally_resolve_control(builder: IRBuilder,
                                cleanup_block: BasicBlock,
                                finally_control: FinallyNonlocalControl,
                                old_exc: Value,
                                ret_reg: Optional[Value]) -> BasicBlock:
    """Resolve the control flow out of a finally block.

    This means returning if there was a return, propagating
    exceptions, break/continue (soon), or just continuing on.
    """
    reraise, rest = BasicBlock(), BasicBlock()
    builder.add(Branch(old_exc, rest, reraise, Branch.IS_ERROR))

    # Reraise the exception if there was one
    builder.activate_block(reraise)
    builder.primitive_op(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
    builder.add(Unreachable())
    builder.builder.pop_error_handler()

    # If there was a return, keep returning
    if ret_reg:
        builder.activate_block(rest)
        return_block, rest = BasicBlock(), BasicBlock()
        builder.add(Branch(ret_reg, rest, return_block, Branch.IS_ERROR))

        builder.activate_block(return_block)
        builder.nonlocal_control[-1].gen_return(builder, ret_reg, -1)

    # TODO: handle break/continue
    builder.activate_block(rest)
    out_block = BasicBlock()
    builder.goto(out_block)

    # If there was an exception, restore again
    builder.activate_block(cleanup_block)
    finally_control.gen_cleanup(builder, -1)
    builder.primitive_op(keep_propagating_op, [], NO_TRACEBACK_LINE_NO)
    builder.add(Unreachable())

    return out_block
Ejemplo n.º 9
0
def split_blocks_at_uninits(
        env: Environment, blocks: List[BasicBlock],
        pre_must_defined: 'AnalysisDict[Value]') -> List[BasicBlock]:
    new_blocks = []  # type: List[BasicBlock]

    # First split blocks on ops that may raise.
    for block in blocks:
        ops = block.ops
        block.ops = []
        cur_block = block
        new_blocks.append(cur_block)

        for i, op in enumerate(ops):
            defined = pre_must_defined[block, i]
            for src in op.unique_sources():
                # If a register operand is not guarenteed to be
                # initialized is an operand to something other than a
                # check that it is defined, insert a check.
                if (isinstance(src, Register) and src not in defined
                        and not (isinstance(op, Branch)
                                 and op.op == Branch.IS_ERROR)):
                    new_block, error_block = BasicBlock(), BasicBlock()
                    new_block.error_handler = error_block.error_handler = cur_block.error_handler
                    new_blocks += [error_block, new_block]

                    env.vars_needing_init.add(src)

                    cur_block.ops.append(
                        Branch(src,
                               true_label=error_block,
                               false_label=new_block,
                               op=Branch.IS_ERROR,
                               line=op.line))
                    raise_std = RaiseStandardError(
                        RaiseStandardError.UNBOUND_LOCAL_ERROR,
                        "local variable '{}' referenced before assignment".
                        format(src.name), op.line)
                    env.add_op(raise_std)
                    error_block.ops.append(raise_std)
                    error_block.ops.append(Unreachable())
                    cur_block = new_block
            cur_block.ops.append(op)

    return new_blocks
Ejemplo n.º 10
0
    def gen_glue_ne_method(self, cls: ClassIR, line: int) -> FuncIR:
        """Generate a __ne__ method from a __eq__ method. """
        self.builder.enter()

        rt_args = (RuntimeArg("self", RInstance(cls)), RuntimeArg("rhs", object_rprimitive))

        # The environment operates on Vars, so we make some up
        fake_vars = [(Var(arg.name), arg.type) for arg in rt_args]
        args = [
            self.builder.read(
                self.builder.environment.add_local_reg(
                    var, type, is_arg=True
                ),
                line
            )
            for var, type in fake_vars
        ]  # type: List[Value]
        self.builder.ret_types[-1] = object_rprimitive

        # If __eq__ returns NotImplemented, then __ne__ should also
        not_implemented_block, regular_block = BasicBlock(), BasicBlock()
        eqval = self.add(MethodCall(args[0], '__eq__', [args[1]], line))
        not_implemented = self.primitive_op(not_implemented_op, [], line)
        self.add(Branch(
            self.builder.binary_op(eqval, not_implemented, 'is', line),
            not_implemented_block,
            regular_block,
            Branch.BOOL_EXPR))

        self.builder.activate_block(regular_block)
        retval = self.builder.coerce(
            self.builder.unary_op(eqval, 'not', line), object_rprimitive, line
        )
        self.add(Return(retval))

        self.builder.activate_block(not_implemented_block)
        self.add(Return(not_implemented))

        blocks, env, ret_type, _ = self.builder.leave()
        return FuncIR(
            FuncDecl('__ne__', cls.name, self.module_name,
                     FuncSignature(rt_args, ret_type)),
            blocks, env)
Ejemplo n.º 11
0
 def visit_branch(self, op: Branch) -> GenAndKill:
     return set(op.sources()), set()
Ejemplo n.º 12
0
def split_blocks_at_errors(blocks: List[BasicBlock],
                           default_error_handler: BasicBlock,
                           func: str) -> List[BasicBlock]:
    new_blocks = []  # type: List[BasicBlock]
    mapping = {}
    partial_ops = set()
    # First split blocks on ops that may raise.
    for block in blocks:
        ops = block.ops
        i0 = 0
        i = 0
        next_block = BasicBlock()
        while i < len(ops) - 1:
            op = ops[i]
            if isinstance(op, RegisterOp) and op.error_kind != ERR_NEVER:
                # Split
                new_blocks.append(next_block)
                new_block = next_block
                next_block = BasicBlock()
                new_block.ops.extend(ops[i0:i + 1])

                if op.error_kind == ERR_MAGIC:
                    # Op returns an error value on error that depends on result RType.
                    variant = Branch.IS_ERROR
                    negated = False
                elif op.error_kind == ERR_FALSE:
                    # Op returns a C false value on error.
                    variant = Branch.BOOL_EXPR
                    negated = True
                else:
                    assert False, 'unknown error kind %d' % op.error_kind

                # Void ops can't generate errors since error is always
                # indicated by a special value stored in a register.
                assert not op.is_void, "void op generating errors?"

                # If the block has an error handler specified, use it. Otherwise
                # fall back to the default.
                error_label = block.error_handler or default_error_handler
                branch = Branch(op,
                                true_label=error_label,
                                false_label=next_block,
                                op=variant,
                                line=op.line)
                branch.negated = negated
                if op.line != NO_TRACEBACK_LINE_NO:
                    branch.traceback_entry = (func, op.line)
                partial_ops.add(branch)  # Only tweak true label of these
                new_block.ops.append(branch)
                if i0 == 0:
                    mapping[block] = new_block
                i += 1
                i0 = i
            else:
                i += 1
        new_blocks.append(next_block)
        next_block.ops.extend(ops[i0:i + 1])
        if i0 == 0:
            mapping[block] = next_block
    # Adjust all labels to reflect the new blocks.
    for block in new_blocks:
        for op in block.ops:
            if isinstance(op, Goto):
                op.label = mapping[op.label]
            elif isinstance(op, Branch):
                if op not in partial_ops:
                    op.false = mapping[op.false]
                op.true = mapping[op.true]
    return new_blocks
Ejemplo n.º 13
0
def transform_try_except(builder: IRBuilder,
                         body: GenFunc,
                         handlers: Sequence[
                             Tuple[Optional[Expression], Optional[Expression], GenFunc]],
                         else_body: Optional[GenFunc],
                         line: int) -> None:
    """Generalized try/except/else handling that takes functions to gen the bodies.

    The point of this is to also be able to support with."""
    assert handlers, "try needs except"

    except_entry, exit_block, cleanup_block = BasicBlock(), BasicBlock(), BasicBlock()
    double_except_block = BasicBlock()
    # If there is an else block, jump there after the try, otherwise just leave
    else_block = BasicBlock() if else_body else exit_block

    # Compile the try block with an error handler
    builder.builder.push_error_handler(except_entry)
    builder.goto_and_activate(BasicBlock())
    body()
    builder.goto(else_block)
    builder.builder.pop_error_handler()

    # The error handler catches the error and then checks it
    # against the except clauses. We compile the error handler
    # itself with an error handler so that it can properly restore
    # the *old* exc_info if an exception occurs.
    # The exception chaining will be done automatically when the
    # exception is raised, based on the exception in exc_info.
    builder.builder.push_error_handler(double_except_block)
    builder.activate_block(except_entry)
    old_exc = builder.maybe_spill(builder.primitive_op(error_catch_op, [], line))
    # Compile the except blocks with the nonlocal control flow overridden to clear exc_info
    builder.nonlocal_control.append(
        ExceptNonlocalControl(builder.nonlocal_control[-1], old_exc))

    # Process the bodies
    for type, var, handler_body in handlers:
        next_block = None
        if type:
            next_block, body_block = BasicBlock(), BasicBlock()
            matches = builder.primitive_op(
                exc_matches_op, [builder.accept(type)], type.line
            )
            builder.add(Branch(matches, body_block, next_block, Branch.BOOL_EXPR))
            builder.activate_block(body_block)
        if var:
            target = builder.get_assignment_target(var)
            builder.assign(
                target,
                builder.primitive_op(get_exc_value_op, [], var.line),
                var.line
            )
        handler_body()
        builder.goto(cleanup_block)
        if next_block:
            builder.activate_block(next_block)

    # Reraise the exception if needed
    if next_block:
        builder.primitive_op(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
        builder.add(Unreachable())

    builder.nonlocal_control.pop()
    builder.builder.pop_error_handler()

    # Cleanup for if we leave except through normal control flow:
    # restore the saved exc_info information and continue propagating
    # the exception if it exists.
    builder.activate_block(cleanup_block)
    builder.primitive_op(restore_exc_info_op, [builder.read(old_exc)], line)
    builder.goto(exit_block)

    # Cleanup for if we leave except through a raised exception:
    # restore the saved exc_info information and continue propagating
    # the exception.
    builder.activate_block(double_except_block)
    builder.primitive_op(restore_exc_info_op, [builder.read(old_exc)], line)
    builder.primitive_op(keep_propagating_op, [], NO_TRACEBACK_LINE_NO)
    builder.add(Unreachable())

    # If present, compile the else body in the obvious way
    if else_body:
        builder.activate_block(else_block)
        else_body()
        builder.goto(exit_block)

    builder.activate_block(exit_block)
Ejemplo n.º 14
0
 def process_conditional(self, e: Node) -> List[Branch]:
     if isinstance(e, ComparisonExpr):
         # TODO: Verify operand types.
         assert len(e.operators) == 1, 'more than 1 operator not supported'
         op = e.operators[0]
         if op in ['==', '!=', '<', '<=', '>', '>=']:
             # TODO: check operand types
             left = self.accept(e.operands[0])
             right = self.accept(e.operands[1])
             opcode = self.int_relative_ops[op]
             branch = Branch(left, right, INVALID_LABEL, INVALID_LABEL,
                             opcode)
         elif op in ['is', 'is not']:
             # TODO: check if right operand is None
             left = self.accept(e.operands[0])
             branch = Branch(left, INVALID_REGISTER, INVALID_LABEL,
                             INVALID_LABEL, Branch.IS_NONE)
             if op == 'is not':
                 branch.negated = True
         elif op in ['in', 'not in']:
             left = self.accept(e.operands[0])
             ltype = self.node_type(e.operands[0])
             right = self.accept(e.operands[1])
             rtype = self.node_type(e.operands[1])
             target = self.alloc_temp(self.node_type(e))
             self.binary_op(ltype, left, rtype, right, 'in', target=target)
             branch = Branch(target, INVALID_REGISTER, INVALID_LABEL,
                             INVALID_LABEL, Branch.BOOL_EXPR)
             if op == 'not in':
                 branch.negated = True
         else:
             assert False, "unsupported comparison epxression"
         self.add(branch)
         return [branch]
     elif isinstance(e, OpExpr) and e.op in ['and', 'or']:
         if e.op == 'and':
             # Short circuit 'and' in a conditional context.
             lbranches = self.process_conditional(e.left)
             new = self.new_block()
             self.set_branches(lbranches, True, new)
             rbranches = self.process_conditional(e.right)
             return lbranches + rbranches
         else:
             # Short circuit 'or' in a conditional context.
             lbranches = self.process_conditional(e.left)
             new = self.new_block()
             self.set_branches(lbranches, False, new)
             rbranches = self.process_conditional(e.right)
             return lbranches + rbranches
     elif isinstance(e, UnaryExpr) and e.op == 'not':
         branches = self.process_conditional(e.expr)
         for b in branches:
             b.invert()
         return branches
     # Catch-all for arbitrary expressions.
     else:
         reg = self.accept(e)
         branch = Branch(reg, INVALID_REGISTER, INVALID_LABEL,
                         INVALID_LABEL, Branch.BOOL_EXPR)
         self.add(branch)
         return [branch]
Ejemplo n.º 15
0
    def visit_for_stmt(self, s: ForStmt) -> Register:
        if (isinstance(s.expr, CallExpr)
                and isinstance(s.expr.callee, RefExpr)
                and s.expr.callee.fullname == 'builtins.range'):
            self.push_loop_stack()

            # Special case for x in range(...)
            # TODO: Check argument counts and kinds; check the lvalue
            end = s.expr.args[0]
            end_reg = self.accept(end)

            # Initialize loop index to 0.
            index_reg = self.assign(s.index,
                                    IntExpr(0),
                                    IntRType(),
                                    IntRType(),
                                    declare_new=True)
            goto = Goto(INVALID_LABEL)
            self.add(goto)

            # Add loop condition check.
            top = self.new_block()
            goto.label = top.label
            branch = Branch(index_reg, end_reg, INVALID_LABEL, INVALID_LABEL,
                            Branch.INT_LT)
            self.add(branch)
            branches = [branch]

            body = self.new_block()
            self.set_branches(branches, True, body)
            s.body.accept(self)

            end_goto = Goto(INVALID_LABEL)
            self.add(end_goto)
            end_block = self.new_block()
            end_goto.label = end_block.label

            # Increment index register.
            one_reg = self.alloc_temp(IntRType())
            self.add(LoadInt(one_reg, 1))
            self.add(
                PrimitiveOp(index_reg, PrimitiveOp.INT_ADD, index_reg,
                            one_reg))

            # Go back to loop condition check.
            self.add(Goto(top.label))
            next = self.new_block()
            self.set_branches(branches, False, next)

            self.pop_loop_stack(end_block, next)
            return INVALID_REGISTER

        if self.node_type(s.expr).name == 'list':
            self.push_loop_stack()

            expr_reg = self.accept(s.expr)

            index_reg = self.alloc_temp(IntRType())
            self.add(LoadInt(index_reg, 0))

            one_reg = self.alloc_temp(IntRType())
            self.add(LoadInt(one_reg, 1))

            assert isinstance(s.index, NameExpr)
            assert isinstance(s.index.node, Var)
            lvalue_reg = self.environment.add_local(s.index.node,
                                                    self.node_type(s.index))

            condition_block = self.goto_new_block()

            # For compatibility with python semantics we recalculate the length
            # at every iteration.
            len_reg = self.alloc_temp(IntRType())
            self.add(PrimitiveOp(len_reg, PrimitiveOp.LIST_LEN, expr_reg))

            branch = Branch(index_reg, len_reg, INVALID_LABEL, INVALID_LABEL,
                            Branch.INT_LT)
            self.add(branch)
            branches = [branch]

            body_block = self.new_block()
            self.set_branches(branches, True, body_block)

            target_list_type = self.types[s.expr]
            assert isinstance(target_list_type, Instance)
            target_type = self.type_to_rtype(target_list_type.args[0])
            value_box = self.alloc_temp(ObjectRType())
            self.add(
                PrimitiveOp(value_box, PrimitiveOp.LIST_GET, expr_reg,
                            index_reg))

            self.unbox_or_cast(value_box, target_type, target=lvalue_reg)

            s.body.accept(self)

            end_block = self.goto_new_block()
            self.add(
                PrimitiveOp(index_reg, PrimitiveOp.INT_ADD, index_reg,
                            one_reg))
            self.add(Goto(condition_block.label))

            next_block = self.new_block()
            self.set_branches(branches, False, next_block)

            self.pop_loop_stack(end_block, next_block)

            return INVALID_REGISTER

        assert False, 'for not supported'