def transform_try_finally_stmt(builder: IRBuilder, try_body: GenFunc, finally_body: GenFunc) -> None: """Generalized try/finally handling that takes functions to gen the bodies. The point of this is to also be able to support with.""" # Finally is a big pain, because there are so many ways that # exits can occur. We emit 10+ basic blocks for every finally! err_handler, main_entry, return_entry, finally_block = (BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock()) # Compile the body of the try ret_reg = try_finally_try(builder, err_handler, return_entry, main_entry, try_body) # Set up the entry blocks for the finally statement old_exc = try_finally_entry_blocks(builder, err_handler, return_entry, main_entry, finally_block, ret_reg) # Compile the body of the finally cleanup_block, finally_control = try_finally_body(builder, finally_block, finally_body, ret_reg, old_exc) # Resolve the control flow out of the finally block out_block = try_finally_resolve_control(builder, cleanup_block, finally_control, old_exc, ret_reg) builder.activate_block(out_block)
def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generate the '__get__' method for a callable class.""" line = fn_info.fitem.line with builder.enter_method(fn_info.callable_class.ir, '__get__', object_rprimitive, fn_info, self_type=object_rprimitive): instance = builder.add_argument('instance', object_rprimitive) builder.add_argument('owner', object_rprimitive) # If accessed through the class, just return the callable # object. If accessed through an object, create a new bound # instance method object. instance_block, class_block = BasicBlock(), BasicBlock() comparison = builder.translate_is_op(builder.read(instance), builder.none_object(), 'is', line) builder.add_bool_branch(comparison, class_block, instance_block) builder.activate_block(class_block) builder.add(Return(builder.self())) builder.activate_block(instance_block) builder.add( Return( builder.call_c( method_new_op, [builder.self(), builder.read(instance)], line)))
def transform_assert_stmt(builder: IRBuilder, a: AssertStmt) -> None: if builder.options.strip_asserts: return cond = builder.accept(a.expr) ok_block, error_block = BasicBlock(), BasicBlock() builder.add_bool_branch(cond, ok_block, error_block) builder.activate_block(error_block) if a.msg is None: # Special case (for simpler generated code) builder.add( RaiseStandardError(RaiseStandardError.ASSERTION_ERROR, None, a.line)) elif isinstance(a.msg, StrExpr): # Another special case builder.add( RaiseStandardError(RaiseStandardError.ASSERTION_ERROR, a.msg.value, a.line)) else: # The general case -- explicitly construct an exception instance message = builder.accept(a.msg) exc_type = builder.load_module_attr_by_fullname( 'builtins.AssertionError', a.line) exc = builder.py_call(exc_type, [message], a.line) builder.call_c(raise_exception_op, [exc], a.line) builder.add(Unreachable()) builder.activate_block(ok_block)
def gen_glue_ne_method(builder: IRBuilder, cls: ClassIR, line: int) -> None: """Generate a "__ne__" method from a "__eq__" method. """ builder.enter_method(cls, '__ne__', object_rprimitive) rhs_arg = builder.add_argument('rhs', object_rprimitive) # If __eq__ returns NotImplemented, then __ne__ should also not_implemented_block, regular_block = BasicBlock(), BasicBlock() eqval = builder.add(MethodCall(builder.self(), '__eq__', [rhs_arg], line)) not_implemented = builder.add(LoadAddress(not_implemented_op.type, not_implemented_op.src, line)) builder.add(Branch( builder.translate_is_op(eqval, not_implemented, 'is', line), not_implemented_block, regular_block, Branch.BOOL)) builder.activate_block(regular_block) retval = builder.coerce( builder.unary_op(eqval, 'not', line), object_rprimitive, line ) builder.add(Return(retval)) builder.activate_block(not_implemented_block) builder.add(Return(not_implemented)) builder.leave_method()
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
def populate_switch_for_generator_class(builder: IRBuilder) -> None: cls = builder.fn_info.generator_class line = builder.fn_info.fitem.line builder.activate_block(cls.switch_block) for label, true_block in enumerate(cls.continuation_blocks): false_block = BasicBlock() comparison = builder.binary_op(cls.next_label_reg, builder.add(LoadInt(label)), '==', line) builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(false_block) builder.add( RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, line)) builder.add(Unreachable())
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 = Register(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
def gen_calls_to_correct_impl( builder: IRBuilder, impl_to_use: Value, arg_info: ArgInfo, fitem: FuncDef, line: int, ) -> None: current_func_decl = builder.mapper.func_to_decl[fitem] def gen_native_func_call_and_return(fdef: FuncDef) -> None: 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.add(Return(coerced)) typ, src = builtin_names['builtins.int'] int_type_obj = builder.add(LoadAddress(typ, src, line)) is_int = builder.builder.type_is_op(impl_to_use, int_type_obj, line) native_call, non_native_call = BasicBlock(), BasicBlock() builder.add_bool_branch(is_int, native_call, non_native_call) builder.activate_block(native_call) passed_id = builder.add(Unbox(impl_to_use, int_rprimitive, line)) native_ids = get_native_impl_ids(builder, fitem) for impl, i in native_ids.items(): call_impl, next_impl = BasicBlock(), BasicBlock() current_id = builder.load_int(i) builder.builder.compare_tagged_condition( passed_id, current_id, '==', call_impl, next_impl, line, ) # Call the registered implementation builder.activate_block(call_impl) gen_native_func_call_and_return(impl) builder.activate_block(next_impl) # We've already handled all the possible integer IDs, so we should never get here builder.add(Unreachable()) builder.activate_block(non_native_call) ret_val = builder.py_call(impl_to_use, arg_info.args, line, arg_info.arg_kinds, arg_info.arg_names) coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line) builder.add(Return(coerced))
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
def gen_glue_ne_method(builder: IRBuilder, cls: ClassIR, line: int) -> FuncIR: """Generate a "__ne__" method from a "__eq__" method. """ 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 = [ builder.read( builder.environment.add_local_reg( var, type, is_arg=True ), line ) for var, type in fake_vars ] # type: List[Value] builder.ret_types[-1] = object_rprimitive # If __eq__ returns NotImplemented, then __ne__ should also not_implemented_block, regular_block = BasicBlock(), BasicBlock() eqval = builder.add(MethodCall(args[0], '__eq__', [args[1]], line)) not_implemented = builder.add(LoadAddress(not_implemented_op.type, not_implemented_op.src, line)) builder.add(Branch( builder.translate_is_op(eqval, not_implemented, 'is', line), not_implemented_block, regular_block, Branch.BOOL_EXPR)) builder.activate_block(regular_block) retval = builder.coerce( builder.unary_op(eqval, 'not', line), object_rprimitive, line ) builder.add(Return(retval)) builder.activate_block(not_implemented_block) builder.add(Return(not_implemented)) blocks, env, ret_type, _ = builder.leave() return FuncIR( FuncDecl('__ne__', cls.name, builder.module_name, FuncSignature(rt_args, ret_type)), blocks, env)
def for_loop_helper_with_index(builder: IRBuilder, index: Lvalue, expr: Expression, body_insts: Callable[[Value], None], line: int) -> None: """Generate IR for a sequence iteration. This function only works for sequence type. Compared to for_loop_helper, it would feed iteration index to body_insts. Args: index: the loop index Lvalue expr: the expression to iterate over body_insts: a function that generates the body of the loop. It needs a index as parameter. """ expr_reg = builder.accept(expr) assert is_sequence_rprimitive(expr_reg.type) target_type = builder.get_sequence_type(expr) body_block = BasicBlock() step_block = BasicBlock() exit_block = BasicBlock() condition_block = BasicBlock() for_gen = ForSequence(builder, index, body_block, exit_block, line, False) for_gen.init(expr_reg, target_type, reverse=False) builder.push_loop_stack(step_block, exit_block) builder.goto_and_activate(condition_block) for_gen.gen_condition() builder.activate_block(body_block) for_gen.begin_body() body_insts(builder.read(for_gen.index_target)) builder.goto_and_activate(step_block) for_gen.gen_step() builder.goto(condition_block) for_gen.add_cleanup(exit_block) builder.pop_loop_stack() builder.activate_block(exit_block)
def add_raise_exception_blocks_to_generator_class(builder: IRBuilder, line: int) -> None: """Add error handling blocks to a generator class. Generates blocks to check if error flags are set while calling the helper method for generator functions, and raises an exception if those flags are set. """ cls = builder.fn_info.generator_class assert cls.exc_regs is not None exc_type, exc_val, exc_tb = cls.exc_regs # Check to see if an exception was raised. error_block = BasicBlock() ok_block = BasicBlock() comparison = builder.translate_is_op(exc_type, builder.none_object(), 'is not', line) builder.add_bool_branch(comparison, error_block, ok_block) builder.activate_block(error_block) builder.call_c(raise_exception_with_tb_op, [exc_type, exc_val, exc_tb], line) builder.add(Unreachable()) builder.goto_and_activate(ok_block)
def setup_non_ext_dict(builder: IRBuilder, cdef: ClassDef, metaclass: Value, bases: Value) -> Value: """Initialize the class dictionary for a non-extension class. This class dictionary is passed to the metaclass constructor. """ # Check if the metaclass defines a __prepare__ method, and if so, call it. has_prepare = builder.call_c(py_hasattr_op, [metaclass, builder.load_str('__prepare__')], cdef.line) non_ext_dict = Register(dict_rprimitive) true_block, false_block, exit_block, = BasicBlock(), BasicBlock(), BasicBlock() builder.add_bool_branch(has_prepare, true_block, false_block) builder.activate_block(true_block) cls_name = builder.load_str(cdef.name) prepare_meth = builder.py_get_attr(metaclass, '__prepare__', cdef.line) prepare_dict = builder.py_call(prepare_meth, [cls_name, bases], cdef.line) builder.assign(non_ext_dict, prepare_dict, cdef.line) builder.goto(exit_block) builder.activate_block(false_block) builder.assign(non_ext_dict, builder.call_c(dict_new_op, [], cdef.line), cdef.line) builder.goto(exit_block) builder.activate_block(exit_block) return non_ext_dict
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.call_c(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.call_c(keep_propagating_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) return out_block
def faster_min_max(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: if expr.arg_kinds == [ARG_POS, ARG_POS]: x, y = builder.accept(expr.args[0]), builder.accept(expr.args[1]) result = Register(builder.node_type(expr)) # CPython evaluates arguments reversely when calling min(...) or max(...) if callee.fullname == 'builtins.min': comparison = builder.binary_op(y, x, '<', expr.line) else: comparison = builder.binary_op(y, x, '>', expr.line) true_block, false_block, next_block = BasicBlock(), BasicBlock( ), BasicBlock() builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(true_block) builder.assign(result, builder.coerce(y, result.type, expr.line), expr.line) builder.goto(next_block) builder.activate_block(false_block) builder.assign(result, builder.coerce(x, result.type, expr.line), expr.line) builder.goto(next_block) builder.activate_block(next_block) return result return None
def add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generates the '__close__' method for a generator class.""" with builder.enter_method(fn_info.generator_class.ir, 'close', object_rprimitive, fn_info): except_block, else_block = BasicBlock(), BasicBlock() builder.builder.push_error_handler(except_block) builder.goto_and_activate(BasicBlock()) generator_exit = builder.load_module_attr_by_fullname( 'builtins.GeneratorExit', fn_info.fitem.line) builder.add( MethodCall( builder.self(), 'throw', [generator_exit, builder.none_object(), builder.none_object()])) builder.goto(else_block) builder.builder.pop_error_handler() builder.activate_block(except_block) old_exc = builder.call_c(error_catch_op, [], fn_info.fitem.line) builder.nonlocal_control.append( ExceptNonlocalControl(builder.nonlocal_control[-1], old_exc)) stop_iteration = builder.load_module_attr_by_fullname( 'builtins.StopIteration', fn_info.fitem.line) exceptions = builder.add( TupleSet([generator_exit, stop_iteration], fn_info.fitem.line)) matches = builder.call_c(exc_matches_op, [exceptions], fn_info.fitem.line) match_block, non_match_block = BasicBlock(), BasicBlock() builder.add(Branch(matches, match_block, non_match_block, Branch.BOOL)) builder.activate_block(match_block) builder.call_c(restore_exc_info_op, [builder.read(old_exc)], fn_info.fitem.line) builder.add(Return(builder.none_object())) builder.activate_block(non_match_block) builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.nonlocal_control.pop() builder.activate_block(else_block) builder.add( RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, 'generator ignored GeneratorExit', fn_info.fitem.line)) builder.add(Unreachable())
def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None: """Generate the '__get__' method for a callable class.""" line = fn_info.fitem.line builder.enter(fn_info) vself = builder.read( builder.environment.add_local_reg(Var(SELF_NAME), object_rprimitive, True)) instance = builder.environment.add_local_reg(Var('instance'), object_rprimitive, True) builder.environment.add_local_reg(Var('owner'), object_rprimitive, True) # If accessed through the class, just return the callable # object. If accessed through an object, create a new bound # instance method object. instance_block, class_block = BasicBlock(), BasicBlock() comparison = builder.translate_is_op(builder.read(instance), builder.none_object(), 'is', line) builder.add_bool_branch(comparison, class_block, instance_block) builder.activate_block(class_block) builder.add(Return(vself)) builder.activate_block(instance_block) builder.add( Return( builder.call_c(method_new_op, [vself, builder.read(instance)], line))) blocks, env, _, fn_info = builder.leave() sig = FuncSignature((RuntimeArg(SELF_NAME, object_rprimitive), RuntimeArg('instance', object_rprimitive), RuntimeArg('owner', object_rprimitive)), object_rprimitive) get_fn_decl = FuncDecl('__get__', fn_info.callable_class.ir.name, builder.module_name, sig) get_fn_ir = FuncIR(get_fn_decl, blocks, env) fn_info.callable_class.ir.methods['__get__'] = get_fn_ir builder.functions.append(get_fn_ir)
def try_finally_entry_blocks(builder: IRBuilder, err_handler: BasicBlock, return_entry: BasicBlock, main_entry: BasicBlock, finally_block: BasicBlock, ret_reg: Optional[Register]) -> Value: old_exc = builder.alloc_temp(exc_rtuple) # Entry block for non-exceptional flow builder.activate_block(main_entry) if ret_reg: builder.add( Assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])))) builder.goto(return_entry) builder.activate_block(return_entry) builder.add(Assign(old_exc, builder.add(LoadErrorValue(exc_rtuple)))) builder.goto(finally_block) # Entry block for errors builder.activate_block(err_handler) if ret_reg: builder.add( Assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])))) builder.add(Assign(old_exc, builder.call_c(error_catch_op, [], -1))) builder.goto(finally_block) return old_exc
def for_loop_helper(builder: IRBuilder, index: Lvalue, expr: Expression, body_insts: GenFunc, else_insts: Optional[GenFunc], line: int) -> None: """Generate IR for a loop. Args: index: the loop index Lvalue expr: the expression to iterate over body_insts: a function that generates the body of the loop else_insts: a function that generates the else block instructions """ # Body of the loop body_block = BasicBlock() # Block that steps to the next item step_block = BasicBlock() # Block for the else clause, if we need it else_block = BasicBlock() # Block executed after the loop exit_block = BasicBlock() # Determine where we want to exit, if our condition check fails. normal_loop_exit = else_block if else_insts is not None else exit_block for_gen = make_for_loop_generator(builder, index, expr, body_block, normal_loop_exit, line) builder.push_loop_stack(step_block, exit_block) condition_block = BasicBlock() builder.goto_and_activate(condition_block) # Add loop condition check. for_gen.gen_condition() # Generate loop body. builder.activate_block(body_block) for_gen.begin_body() body_insts() # We generate a separate step block (which might be empty). builder.goto_and_activate(step_block) for_gen.gen_step() # Go back to loop condition. builder.goto(condition_block) for_gen.add_cleanup(normal_loop_exit) builder.pop_loop_stack() if else_insts is not None: builder.activate_block(else_block) else_insts() builder.goto(exit_block) builder.activate_block(exit_block)
def transform_if_stmt(builder: IRBuilder, stmt: IfStmt) -> None: if_body, next = BasicBlock(), BasicBlock() else_body = BasicBlock() if stmt.else_body else next # If statements are normalized assert len(stmt.expr) == 1 builder.process_conditional(stmt.expr[0], if_body, else_body) builder.activate_block(if_body) builder.accept(stmt.body[0]) builder.goto(next) if stmt.else_body: builder.activate_block(else_body) builder.accept(stmt.else_body) builder.goto(next) builder.activate_block(next)
def generate_singledispatch_dispatch_function( builder: IRBuilder, main_singledispatch_function_name: str, fitem: FuncDef, ) -> None: line = fitem.line current_func_decl = builder.mapper.func_to_decl[fitem] arg_info = get_args(builder, current_func_decl.sig.args, line) dispatch_func_obj = builder.self() arg_type = builder.builder.get_type_of_obj(arg_info.args[0], line) dispatch_cache = builder.builder.get_attr(dispatch_func_obj, 'dispatch_cache', dict_rprimitive, line) call_find_impl, use_cache, call_func = BasicBlock(), BasicBlock( ), BasicBlock() get_result = builder.call_c(dict_get_method_with_none, [dispatch_cache, arg_type], line) is_not_none = builder.translate_is_op(get_result, builder.none_object(), 'is not', line) impl_to_use = Register(object_rprimitive) builder.add_bool_branch(is_not_none, use_cache, call_find_impl) builder.activate_block(use_cache) builder.assign(impl_to_use, get_result, line) builder.goto(call_func) builder.activate_block(call_find_impl) find_impl = builder.load_module_attr_by_fullname('functools._find_impl', line) registry = load_singledispatch_registry(builder, dispatch_func_obj, line) uncached_impl = builder.py_call(find_impl, [arg_type, registry], line) builder.call_c(dict_set_item_op, [dispatch_cache, arg_type, uncached_impl], line) builder.assign(impl_to_use, uncached_impl, line) builder.goto(call_func) builder.activate_block(call_func) gen_calls_to_correct_impl(builder, impl_to_use, arg_info, fitem, line)
def transform_while_stmt(builder: IRBuilder, s: WhileStmt) -> None: body, next, top, else_block = BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock() normal_loop_exit = else_block if s.else_body is not None else next builder.push_loop_stack(top, next) # Split block so that we get a handle to the top of the loop. builder.goto_and_activate(top) process_conditional(builder, s.expr, body, normal_loop_exit) builder.activate_block(body) builder.accept(s.body) # Add branch to the top at the end of the body. builder.goto(top) builder.pop_loop_stack() if s.else_body is not None: builder.activate_block(else_block) builder.accept(s.else_body) builder.goto(next) builder.activate_block(next)
def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Value: if_body, else_body, next_block = BasicBlock(), BasicBlock(), BasicBlock() builder.process_conditional(expr.cond, if_body, else_body) expr_type = builder.node_type(expr) # Having actual Phi nodes would be really nice here! target = Register(expr_type) builder.activate_block(if_body) true_value = builder.accept(expr.if_expr) true_value = builder.coerce(true_value, expr_type, expr.line) builder.add(Assign(target, true_value)) builder.goto(next_block) builder.activate_block(else_body) false_value = builder.accept(expr.else_expr) false_value = builder.coerce(false_value, expr_type, expr.line) builder.add(Assign(target, false_value)) builder.goto(next_block) builder.activate_block(next_block) return target
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)
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)
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)
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)