Exemplo n.º 1
0
def add_block(decincs: DecIncs, cache: BlockCache, blocks: List[BasicBlock],
              label: BasicBlock) -> BasicBlock:
    decs, incs = decincs
    if not decs and not incs:
        return label

    # TODO: be able to share *partial* results
    if (label, decincs) in cache:
        return cache[label, decincs]

    block = BasicBlock()
    blocks.append(block)
    block.ops.extend(DecRef(reg, is_xdec=xdec) for reg, xdec in decs)
    block.ops.extend(IncRef(reg) for reg in incs)
    block.ops.append(Goto(label))
    cache[label, decincs] = block
    return block
Exemplo n.º 2
0
def try_finally_body(
        builder: IRBuilder,
        finally_block: BasicBlock,
        finally_body: GenFunc,
        ret_reg: Optional[Value],
        old_exc: Value) -> Tuple[BasicBlock, FinallyNonlocalControl]:
    cleanup_block = BasicBlock()
    # Compile the finally block with the nonlocal control flow overridden to restore exc_info
    builder.builder.push_error_handler(cleanup_block)
    finally_control = FinallyNonlocalControl(
        builder.nonlocal_control[-1], ret_reg, old_exc)
    builder.nonlocal_control.append(finally_control)
    builder.activate_block(finally_block)
    finally_body()
    builder.nonlocal_control.pop()

    return cleanup_block, finally_control
Exemplo n.º 3
0
def try_finally_try(builder: IRBuilder,
                    err_handler: BasicBlock,
                    return_entry: BasicBlock,
                    main_entry: BasicBlock,
                    try_body: GenFunc) -> Optional[Register]:
    # Compile the try block with an error handler
    control = TryFinallyNonlocalControl(return_entry)
    builder.builder.push_error_handler(err_handler)

    builder.nonlocal_control.append(control)
    builder.goto_and_activate(BasicBlock())
    try_body()
    builder.goto(main_entry)
    builder.nonlocal_control.pop()
    builder.builder.pop_error_handler()

    return control.ret_reg
Exemplo n.º 4
0
def translate_next_call(builder: IRBuilder, expr: CallExpr,
                        callee: RefExpr) -> Optional[Value]:
    # Special case for calling next() on a generator expression, an
    # idiom that shows up some in mypy.
    #
    # For example, next(x for x in l if x.id == 12, None) will
    # generate code that searches l for an element where x.id == 12
    # and produce the first such object, or None if no such element
    # exists.
    if not (expr.arg_kinds in ([ARG_POS], [ARG_POS, ARG_POS])
            and isinstance(expr.args[0], GeneratorExpr)):
        return None

    gen = expr.args[0]

    retval = builder.alloc_temp(builder.node_type(expr))
    default_val = None
    if len(expr.args) > 1:
        default_val = builder.accept(expr.args[1])

    exit_block = BasicBlock()

    def gen_inner_stmts() -> None:
        # next takes the first element of the generator, so if
        # something gets produced, we are done.
        builder.assign(retval, builder.accept(gen.left_expr),
                       gen.left_expr.line)
        builder.goto(exit_block)

    loop_params = list(zip(gen.indices, gen.sequences, gen.condlists))
    comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line)

    # Now we need the case for when nothing got hit. If there was
    # a default value, we produce it, and otherwise we raise
    # StopIteration.
    if default_val:
        builder.assign(retval, default_val, gen.left_expr.line)
        builder.goto(exit_block)
    else:
        builder.add(
            RaiseStandardError(RaiseStandardError.STOP_ITERATION, None,
                               expr.line))
        builder.add(Unreachable())

    builder.activate_block(exit_block)
    return retval
Exemplo n.º 5
0
def emit_yield(builder: IRBuilder, val: Value, line: int) -> Value:
    retval = builder.coerce(val, builder.ret_types[-1], line)

    cls = builder.fn_info.generator_class
    # Create a new block for the instructions immediately following the yield expression, and
    # set the next label so that the next time '__next__' is called on the generator object,
    # the function continues at the new block.
    next_block = BasicBlock()
    next_label = len(cls.continuation_blocks)
    cls.continuation_blocks.append(next_block)
    builder.assign(cls.next_label_target, builder.add(LoadInt(next_label)), line)
    builder.add(Return(retval))
    builder.activate_block(next_block)

    add_raise_exception_blocks_to_generator_class(builder, line)

    assert cls.send_arg_reg is not None
    return cls.send_arg_reg
Exemplo n.º 6
0
 def gen_condition(self) -> None:
     builder = self.builder
     line = self.line
     if self.reverse:
         # If we are iterating in reverse order, we obviously need
         # to check that the index is still positive. Somewhat less
         # obviously we still need to check against the length,
         # since it could shrink out from under us.
         comparison = builder.binary_op(builder.read(self.index_target, line),
                                        builder.add(LoadInt(0)), '>=', line)
         second_check = BasicBlock()
         builder.add_bool_branch(comparison, second_check, self.loop_exit)
         builder.activate_block(second_check)
     # For compatibility with python semantics we recalculate the length
     # at every iteration.
     len_reg = self.load_len()
     comparison = builder.binary_op(builder.read(self.index_target, line), len_reg, '<', line)
     builder.add_bool_branch(comparison, self.body_block, self.loop_exit)
Exemplo n.º 7
0
    def __init__(self, ir: ClassIR) -> None:
        super().__init__(ir)
        # This register holds the label number that the '__next__' function should go to the next
        # time it is called.
        self._next_label_reg = None  # type: Optional[Value]
        self._next_label_target = None  # type: Optional[AssignmentTarget]

        # These registers hold the error values for the generator object for the case that the
        # 'throw' function is called.
        self.exc_regs = None  # type: Optional[Tuple[Value, Value, Value]]

        # Holds the arg passed to send
        self.send_arg_reg = None  # type: Optional[Value]

        # The switch block is used to decide which instruction to go using the value held in the
        # next-label register.
        self.switch_block = BasicBlock()
        self.continuation_blocks = []  # type: List[BasicBlock]
Exemplo n.º 8
0
 def __init__(self, builder: IRBuilder, index: Lvalue,
              body_block: BasicBlock, loop_exit: BasicBlock, line: int,
              nested: bool) -> None:
     self.builder = builder
     self.index = index
     self.body_block = body_block
     self.line = line
     # Some for loops need a cleanup block that we execute at exit. We
     # create a cleanup block if needed. However, if we are generating a for
     # loop for a nested iterator, such as "e" in "enumerate(e)", the
     # outermost generator should generate the cleanup block -- we don't
     # need to do it here.
     if self.need_cleanup() and not nested:
         # Create a new block to handle cleanup after loop exit.
         self.loop_exit = BasicBlock()
     else:
         # Just use the existing loop exit block.
         self.loop_exit = loop_exit
Exemplo n.º 9
0
def transform_block(block: BasicBlock,
                    pre_live: 'AnalysisDict[Value]',
                    post_live: 'AnalysisDict[Value]',
                    pre_borrow: 'AnalysisDict[Value]',
                    post_must_defined: 'AnalysisDict[Value]') -> None:
    old_ops = block.ops
    ops = []  # type: List[Op]
    for i, op in enumerate(old_ops):
        key = (block, i)

        assert op not in pre_live[key]
        dest = op.dest if isinstance(op, Assign) else op
        stolen = op.stolen()

        # Incref any references that are being stolen that stay live, were borrowed,
        # or are stolen more than once by this operation.
        for i, src in enumerate(stolen):
            if src in post_live[key] or src in pre_borrow[key] or src in stolen[:i]:
                maybe_append_inc_ref(ops, src)
                # For assignments to registers that were already live,
                # decref the old value.
                if (dest not in pre_borrow[key] and dest in pre_live[key]):
                    assert isinstance(op, Assign)
                    maybe_append_dec_ref(ops, dest, post_must_defined, key)

        # Strip KeepAlive. Its only purpose is to help with this transform.
        if not isinstance(op, KeepAlive):
            ops.append(op)

        # Control ops don't have any space to insert ops after them, so
        # their inc/decrefs get inserted by insert_branch_inc_and_decrefs.
        if isinstance(op, ControlOp):
            continue

        for src in op.unique_sources():
            # Decrement source that won't be live afterwards.
            if src not in post_live[key] and src not in pre_borrow[key] and src not in stolen:
                maybe_append_dec_ref(ops, src, post_must_defined, key)
        # Decrement the destination if it is dead after the op and
        # wasn't a borrowed RegisterOp
        if (not dest.is_void and dest not in post_live[key]
                and not (isinstance(op, RegisterOp) and dest.is_borrowed)):
            maybe_append_dec_ref(ops, dest, post_must_defined, key)
    block.ops = ops
Exemplo n.º 10
0
    def assert_emit(self,
                    op: Op,
                    expected: str,
                    next_block: Optional[BasicBlock] = None,
                    *,
                    rare: bool = False,
                    next_branch: Optional[Branch] = None,
                    skip_next: bool = False) -> None:
        block = BasicBlock(0)
        block.ops.append(op)
        value_names = generate_names_for_ir(self.registers, [block])
        emitter = Emitter(self.context, value_names)
        declarations = Emitter(self.context, value_names)
        emitter.fragments = []
        declarations.fragments = []

        visitor = FunctionEmitterVisitor(emitter, declarations, 'prog.py',
                                         'prog')
        visitor.next_block = next_block
        visitor.rare = rare
        if next_branch:
            visitor.ops = [op, next_branch]
        else:
            visitor.ops = [op]
        visitor.op_index = 0

        op.accept(visitor)
        frags = declarations.fragments + emitter.fragments
        actual_lines = [line.strip(' ') for line in frags]
        assert all(line.endswith('\n') for line in actual_lines)
        actual_lines = [line.rstrip('\n') for line in actual_lines]
        if not expected.strip():
            expected_lines = []
        else:
            expected_lines = expected.rstrip().split('\n')
        expected_lines = [line.strip(' ') for line in expected_lines]
        assert_string_arrays_equal(expected_lines,
                                   actual_lines,
                                   msg='Generated code unexpected')
        if skip_next:
            assert visitor.op_index == 1
        else:
            assert visitor.op_index == 0
Exemplo n.º 11
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))
Exemplo n.º 12
0
 def gen_return(self, builder: 'IRBuilder', value: Value,
                line: int) -> None:
     # Assign an invalid next label number so that the next time __next__ is called, we jump to
     # the case in which StopIteration is raised.
     builder.assign(builder.fn_info.generator_class.next_label_target,
                    builder.add(LoadInt(-1)), line)
     # Raise a StopIteration containing a field for the value that should be returned. Before
     # doing so, create a new block without an error handler set so that the implicitly thrown
     # StopIteration isn't caught by except blocks inside of the generator function.
     builder.builder.push_error_handler(None)
     builder.goto_and_activate(BasicBlock())
     # Skip creating a traceback frame when we raise here, because
     # we don't care about the traceback frame and it is kind of
     # expensive since raising StopIteration is an extremely common case.
     # Also we call a special internal function to set StopIteration instead of
     # using RaiseStandardError because the obvious thing doesn't work if the
     # value is a tuple (???).
     builder.primitive_op(set_stop_iteration_value, [value],
                          NO_TRACEBACK_LINE_NO)
     builder.add(Unreachable())
     builder.builder.pop_error_handler()
Exemplo n.º 13
0
    def assert_emit(self, op: Op, expected: str) -> None:
        block = BasicBlock(0)
        block.ops.append(op)
        value_names = generate_names_for_ir(self.registers, [block])
        emitter = Emitter(self.context, value_names)
        declarations = Emitter(self.context, value_names)
        emitter.fragments = []
        declarations.fragments = []

        visitor = FunctionEmitterVisitor(emitter, declarations, 'prog.py',
                                         'prog')

        op.accept(visitor)
        frags = declarations.fragments + emitter.fragments
        actual_lines = [line.strip(' ') for line in frags]
        assert all(line.endswith('\n') for line in actual_lines)
        actual_lines = [line.rstrip('\n') for line in actual_lines]
        expected_lines = expected.rstrip().split('\n')
        expected_lines = [line.strip(' ') for line in expected_lines]
        assert_string_arrays_equal(expected_lines,
                                   actual_lines,
                                   msg='Generated code unexpected')
Exemplo n.º 14
0
def create_switch_for_generator_class(builder: IRBuilder) -> None:
    builder.add(Goto(builder.fn_info.generator_class.switch_block))
    block = BasicBlock()
    builder.fn_info.generator_class.continuation_blocks.append(block)
    builder.activate_block(block)
Exemplo n.º 15
0
 def test_goto(self) -> None:
     self.assert_emit(Goto(BasicBlock(2)), "goto CPyL2;")
Exemplo n.º 16
0
 def setUp(self) -> None:
     self.arg = RuntimeArg('arg', int_rprimitive)
     self.reg = Register(int_rprimitive, 'arg')
     self.block = BasicBlock(0)
Exemplo n.º 17
0
 def setUp(self) -> None:
     self.var = Var('arg')
     self.arg = RuntimeArg('arg', int_rprimitive)
     self.env = Environment()
     self.reg = self.env.add_local(self.var, int_rprimitive)
     self.block = BasicBlock(0)
Exemplo n.º 18
0
    def activate_block(self, block: BasicBlock) -> None:
        if self.blocks:
            assert self.blocks[-1].terminated

        block.error_handler = self.error_handlers[-1]
        self.blocks.append(block)
Exemplo n.º 19
0
    def process_iterator_tuple_assignment(self, target: AssignmentTargetTuple,
                                          rvalue_reg: Value,
                                          line: int) -> None:

        iterator = self.primitive_op(iter_op, [rvalue_reg], line)

        # This may be the whole lvalue list if there is no starred value
        split_idx = target.star_idx if target.star_idx is not None else len(
            target.items)

        # Assign values before the first starred value
        for litem in target.items[:split_idx]:
            ritem = self.primitive_op(next_op, [iterator], line)
            error_block, ok_block = BasicBlock(), BasicBlock()
            self.add(Branch(ritem, error_block, ok_block, Branch.IS_ERROR))

            self.activate_block(error_block)
            self.add(
                RaiseStandardError(RaiseStandardError.VALUE_ERROR,
                                   'not enough values to unpack', line))
            self.add(Unreachable())

            self.activate_block(ok_block)

            self.assign(litem, ritem, line)

        # Assign the starred value and all values after it
        if target.star_idx is not None:
            post_star_vals = target.items[split_idx + 1:]
            iter_list = self.primitive_op(to_list, [iterator], line)
            iter_list_len = self.primitive_op(list_len_op, [iter_list], line)
            post_star_len = self.add(LoadInt(len(post_star_vals)))
            condition = self.binary_op(post_star_len, iter_list_len, '<=',
                                       line)

            error_block, ok_block = BasicBlock(), BasicBlock()
            self.add(Branch(condition, ok_block, error_block,
                            Branch.BOOL_EXPR))

            self.activate_block(error_block)
            self.add(
                RaiseStandardError(RaiseStandardError.VALUE_ERROR,
                                   'not enough values to unpack', line))
            self.add(Unreachable())

            self.activate_block(ok_block)

            for litem in reversed(post_star_vals):
                ritem = self.primitive_op(list_pop_last, [iter_list], line)
                self.assign(litem, ritem, line)

            # Assign the starred value
            self.assign(target.items[target.star_idx], iter_list, line)

        # There is no starred value, so check if there are extra values in rhs that
        # have not been assigned.
        else:
            extra = self.primitive_op(next_op, [iterator], line)
            error_block, ok_block = BasicBlock(), BasicBlock()
            self.add(Branch(extra, ok_block, error_block, Branch.IS_ERROR))

            self.activate_block(error_block)
            self.add(
                RaiseStandardError(RaiseStandardError.VALUE_ERROR,
                                   'too many values to unpack', line))
            self.add(Unreachable())

            self.activate_block(ok_block)
Exemplo n.º 20
0
def split_blocks_at_errors(blocks: List[BasicBlock],
                           default_error_handler: BasicBlock,
                           func_name: Optional[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:
            target = op
            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
                    negated = True
                elif op.error_kind == ERR_ALWAYS:
                    variant = Branch.BOOL
                    negated = True
                    # this is a hack to represent the always fail
                    # semantics, using a temporary bool with value false
                    tmp = LoadInt(0, rtype=bool_rprimitive)
                    cur_block.ops.append(tmp)
                    target = tmp
                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.
                if op.error_kind != ERR_ALWAYS:
                    assert not op.is_void, "void op generating errors?"

                branch = Branch(target,
                                true_label=error_label,
                                false_label=new_block,
                                op=variant,
                                line=op.line)
                branch.negated = negated
                if op.line != NO_TRACEBACK_LINE_NO and func_name is not None:
                    branch.traceback_entry = (func_name, op.line)
                cur_block.ops.append(branch)
                cur_block = new_block

    return new_blocks
Exemplo n.º 21
0
 def test_goto_next_block(self) -> None:
     next_block = BasicBlock(2)
     self.assert_emit(Goto(next_block), "", next_block=next_block)
Exemplo n.º 22
0
def split_blocks_at_uninits(
        blocks: List[BasicBlock],
        pre_must_defined: 'AnalysisDict[Value]') -> List[BasicBlock]:
    new_blocks = []  # type: List[BasicBlock]

    init_registers = []
    init_registers_set = set()

    # 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 guaranteed to be
                # initialized is an operand to something other than a
                # check that it is defined, insert a check.

                # Note that for register operand in a LoadAddress op,
                # we should be able to use it without initialization
                # as we may need to use its address to update itself
                if (isinstance(src, Register) and src not in defined
                        and not (isinstance(op, Branch)
                                 and op.op == Branch.IS_ERROR)
                        and not isinstance(op, LoadAddress)):
                    new_block, error_block = BasicBlock(), BasicBlock()
                    new_block.error_handler = error_block.error_handler = cur_block.error_handler
                    new_blocks += [error_block, new_block]

                    if src not in init_registers_set:
                        init_registers.append(src)
                        init_registers_set.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)
                    error_block.ops.append(raise_std)
                    error_block.ops.append(Unreachable())
                    cur_block = new_block
            cur_block.ops.append(op)

    if init_registers:
        new_ops = []  # type: List[Op]
        for reg in init_registers:
            err = LoadErrorValue(reg.type, undefines=True)
            new_ops.append(err)
            new_ops.append(Assign(reg, err))
        new_blocks[0].ops[0:0] = new_ops

    return new_blocks
Exemplo n.º 23
0
def make_block(ops: List[Op]) -> BasicBlock:
    block = BasicBlock()
    block.ops.extend(ops)
    return block
Exemplo n.º 24
0
 def test_label(self) -> None:
     emitter = Emitter(self.context, {})
     assert emitter.label(BasicBlock(4)) == 'CPyL4'
Exemplo n.º 25
0
 def test_label(self) -> None:
     assert self.emitter.label(BasicBlock(4)) == 'CPyL4'
Exemplo n.º 26
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.call_c(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.call_c(exc_matches_op, [builder.accept(type)],
                                     type.line)
            builder.add(Branch(matches, body_block, next_block, Branch.BOOL))
            builder.activate_block(body_block)
        if var:
            target = builder.get_assignment_target(var)
            builder.assign(target,
                           builder.call_c(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.call_c(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.call_c(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.call_c(restore_exc_info_op, [builder.read(old_exc)], line)
    builder.call_c(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)
Exemplo n.º 27
0
 def basic_block(self, ops: List[Op]) -> BasicBlock:
     self.label += 1
     block = BasicBlock(self.label)
     block.ops = ops
     return block
Exemplo n.º 28
0
def handle_yield_from_and_await(builder: IRBuilder, o: Union[YieldFromExpr, AwaitExpr]) -> Value:
    # This is basically an implementation of the code in PEP 380.

    # TODO: do we want to use the right types here?
    result = builder.alloc_temp(object_rprimitive)
    to_yield_reg = builder.alloc_temp(object_rprimitive)
    received_reg = builder.alloc_temp(object_rprimitive)

    if isinstance(o, YieldFromExpr):
        iter_val = builder.primitive_op(iter_op, [builder.accept(o.expr)], o.line)
    else:
        iter_val = builder.primitive_op(coro_op, [builder.accept(o.expr)], o.line)

    iter_reg = builder.maybe_spill_assignable(iter_val)

    stop_block, main_block, done_block = BasicBlock(), BasicBlock(), BasicBlock()
    _y_init = builder.primitive_op(next_raw_op, [builder.read(iter_reg)], o.line)
    builder.add(Branch(_y_init, stop_block, main_block, Branch.IS_ERROR))

    # Try extracting a return value from a StopIteration and return it.
    # If it wasn't, this reraises the exception.
    builder.activate_block(stop_block)
    builder.assign(result, builder.primitive_op(check_stop_op, [], o.line), o.line)
    builder.goto(done_block)

    builder.activate_block(main_block)
    builder.assign(to_yield_reg, _y_init, o.line)

    # OK Now the main loop!
    loop_block = BasicBlock()
    builder.goto_and_activate(loop_block)

    def try_body() -> None:
        builder.assign(
            received_reg, emit_yield(builder, builder.read(to_yield_reg), o.line), o.line
        )

    def except_body() -> None:
        # The body of the except is all implemented in a C function to
        # reduce how much code we need to generate. It returns a value
        # indicating whether to break or yield (or raise an exception).
        res = builder.primitive_op(yield_from_except_op, [builder.read(iter_reg)], o.line)
        to_stop = builder.add(TupleGet(res, 0, o.line))
        val = builder.add(TupleGet(res, 1, o.line))

        ok, stop = BasicBlock(), BasicBlock()
        builder.add(Branch(to_stop, stop, ok, Branch.BOOL_EXPR))

        # The exception got swallowed. Continue, yielding the returned value
        builder.activate_block(ok)
        builder.assign(to_yield_reg, val, o.line)
        builder.nonlocal_control[-1].gen_continue(builder, o.line)

        # The exception was a StopIteration. Stop iterating.
        builder.activate_block(stop)
        builder.assign(result, val, o.line)
        builder.nonlocal_control[-1].gen_break(builder, o.line)

    def else_body() -> None:
        # Do a next() or a .send(). It will return NULL on exception
        # but it won't automatically propagate.
        _y = builder.primitive_op(
            send_op, [builder.read(iter_reg), builder.read(received_reg)], o.line
        )
        ok, stop = BasicBlock(), BasicBlock()
        builder.add(Branch(_y, stop, ok, Branch.IS_ERROR))

        # Everything's fine. Yield it.
        builder.activate_block(ok)
        builder.assign(to_yield_reg, _y, o.line)
        builder.nonlocal_control[-1].gen_continue(builder, o.line)

        # Try extracting a return value from a StopIteration and return it.
        # If it wasn't, this rereaises the exception.
        builder.activate_block(stop)
        builder.assign(result, builder.primitive_op(check_stop_op, [], o.line), o.line)
        builder.nonlocal_control[-1].gen_break(builder, o.line)

    builder.push_loop_stack(loop_block, done_block)
    transform_try_except(
        builder, try_body, [(None, None, except_body)], else_body, o.line
    )
    builder.pop_loop_stack()

    builder.goto_and_activate(done_block)
    return builder.read(result)
Exemplo n.º 29
0
def generate_singledispatch_dispatch_function(
    builder: IRBuilder,
    main_singledispatch_function_name: str,
    fitem: FuncDef,
) -> None:
    impls = builder.singledispatch_impls[fitem]
    line = fitem.line
    current_func_decl = builder.mapper.func_to_decl[fitem]
    arg_info = get_args(builder, current_func_decl.sig.args, line)

    def gen_func_call_and_return(
        func_name: str,
        fdef: FuncDef,
        fullname: Optional[str] = None
    ) -> None:
        if is_decorated(builder, fdef):
            func = load_func(builder, func_name, fullname, line)
            ret_val = builder.builder.py_call(
                func, arg_info.args, line, arg_info.arg_kinds, arg_info.arg_names
            )
        else:
            func_decl = builder.mapper.func_to_decl[fdef]
            ret_val = builder.builder.call(
                func_decl, arg_info.args, arg_info.arg_kinds, arg_info.arg_names, line
            )
        coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line)
        builder.nonlocal_control[-1].gen_return(builder, coerced, line)

    # Add all necessary imports of other modules that have registered functions in other modules
    # We're doing this in a separate pass over the implementations because that avoids the
    # complexity and code size implications of generating this import before every call to a
    # registered implementation that might need this imported
    for _, impl in impls:
        if not is_decorated(builder, impl):
            continue
        module_name = impl.fullname.rsplit('.')[0]
        if module_name not in builder.imports:
            # We need to generate an import here because the module needs to be imported before we
            # try loading the function from it
            builder.gen_import(module_name, line)

    # Sort the list of implementations so that we check any subclasses before we check the classes
    # they inherit from, to better match singledispatch's behavior of going through the argument's
    # MRO, and using the first implementation it finds
    for dispatch_type, impl in sort_with_subclasses_first(impls):
        call_impl, next_impl = BasicBlock(), BasicBlock()
        should_call_impl = check_if_isinstance(builder, arg_info.args[0], dispatch_type, line)
        builder.add_bool_branch(should_call_impl, call_impl, next_impl)

        # Call the registered implementation
        builder.activate_block(call_impl)

        # The shortname of a function is just '{class}.{func_name}', and we don't support
        # singledispatchmethod yet, so that is always the same as the function name
        name = short_id_from_name(impl.name, impl.name, impl.line)
        gen_func_call_and_return(name, impl, fullname=impl.fullname)
        builder.activate_block(next_impl)

    # We don't pass fullname here because getting the fullname of the main generated singledispatch
    # function isn't easy, and we don't need it because the fullname is only needed for making sure
    # we load the function from another module instead of the globals dict if it's defined in
    # another module, which will never be true for the main singledispatch function (it's always
    # generated in the same module as the dispatch function)
    gen_func_call_and_return(main_singledispatch_function_name, fitem)