| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346 |
- """Generate IR for generator functions.
- A generator function is represented by a class that implements the
- generator protocol and keeps track of the generator state, including
- local variables.
- The top-level logic for dealing with generator functions is in
- mypyc.irbuild.function.
- """
- from __future__ import annotations
- from mypy.nodes import ARG_OPT, Var
- from mypyc.common import ENV_ATTR_NAME, NEXT_LABEL_ATTR_NAME, SELF_NAME
- from mypyc.ir.class_ir import ClassIR
- from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature, RuntimeArg
- from mypyc.ir.ops import (
- NO_TRACEBACK_LINE_NO,
- BasicBlock,
- Branch,
- Call,
- Goto,
- Integer,
- MethodCall,
- RaiseStandardError,
- Register,
- Return,
- SetAttr,
- TupleSet,
- Unreachable,
- Value,
- )
- from mypyc.ir.rtypes import RInstance, int_rprimitive, object_rprimitive
- from mypyc.irbuild.builder import IRBuilder, gen_arg_defaults
- from mypyc.irbuild.context import FuncInfo, GeneratorClass
- from mypyc.irbuild.env_class import (
- add_args_to_env,
- finalize_env_class,
- load_env_registers,
- load_outer_env,
- )
- from mypyc.irbuild.nonlocalcontrol import ExceptNonlocalControl
- from mypyc.primitives.exc_ops import (
- error_catch_op,
- exc_matches_op,
- raise_exception_with_tb_op,
- reraise_exception_op,
- restore_exc_info_op,
- )
- def gen_generator_func(builder: IRBuilder) -> None:
- setup_generator_class(builder)
- load_env_registers(builder)
- gen_arg_defaults(builder)
- finalize_env_class(builder)
- builder.add(Return(instantiate_generator_class(builder)))
- def instantiate_generator_class(builder: IRBuilder) -> Value:
- fitem = builder.fn_info.fitem
- generator_reg = builder.add(Call(builder.fn_info.generator_class.ir.ctor, [], fitem.line))
- # Get the current environment register. If the current function is nested, then the
- # generator class gets instantiated from the callable class' '__call__' method, and hence
- # we use the callable class' environment register. Otherwise, we use the original
- # function's environment register.
- if builder.fn_info.is_nested:
- curr_env_reg = builder.fn_info.callable_class.curr_env_reg
- else:
- curr_env_reg = builder.fn_info.curr_env_reg
- # Set the generator class' environment attribute to point at the environment class
- # defined in the current scope.
- builder.add(SetAttr(generator_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line))
- # Set the generator class' environment class' NEXT_LABEL_ATTR_NAME attribute to 0.
- zero = Integer(0)
- builder.add(SetAttr(curr_env_reg, NEXT_LABEL_ATTR_NAME, zero, fitem.line))
- return generator_reg
- def setup_generator_class(builder: IRBuilder) -> ClassIR:
- name = f"{builder.fn_info.namespaced_name()}_gen"
- generator_class_ir = ClassIR(name, builder.module_name, is_generated=True)
- generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class)
- generator_class_ir.mro = [generator_class_ir]
- builder.classes.append(generator_class_ir)
- builder.fn_info.generator_class = GeneratorClass(generator_class_ir)
- return generator_class_ir
- 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 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, Integer(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 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 add_methods_to_generator_class(
- builder: IRBuilder,
- fn_info: FuncInfo,
- sig: FuncSignature,
- arg_regs: list[Register],
- blocks: list[BasicBlock],
- is_coroutine: bool,
- ) -> None:
- helper_fn_decl = add_helper_to_generator_class(builder, arg_regs, blocks, sig, fn_info)
- add_next_to_generator_class(builder, fn_info, helper_fn_decl, sig)
- add_send_to_generator_class(builder, fn_info, helper_fn_decl, sig)
- add_iter_to_generator_class(builder, fn_info)
- add_throw_to_generator_class(builder, fn_info, helper_fn_decl, sig)
- add_close_to_generator_class(builder, fn_info)
- if is_coroutine:
- add_await_to_generator_class(builder, fn_info)
- def add_helper_to_generator_class(
- builder: IRBuilder,
- arg_regs: list[Register],
- blocks: list[BasicBlock],
- sig: FuncSignature,
- fn_info: FuncInfo,
- ) -> FuncDecl:
- """Generates a helper method for a generator class, called by '__next__' and 'throw'."""
- sig = FuncSignature(
- (
- RuntimeArg(SELF_NAME, object_rprimitive),
- RuntimeArg("type", object_rprimitive),
- RuntimeArg("value", object_rprimitive),
- RuntimeArg("traceback", object_rprimitive),
- RuntimeArg("arg", object_rprimitive),
- ),
- sig.ret_type,
- )
- helper_fn_decl = FuncDecl(
- "__mypyc_generator_helper__", fn_info.generator_class.ir.name, builder.module_name, sig
- )
- helper_fn_ir = FuncIR(
- helper_fn_decl, arg_regs, blocks, fn_info.fitem.line, traceback_name=fn_info.fitem.name
- )
- fn_info.generator_class.ir.methods["__mypyc_generator_helper__"] = helper_fn_ir
- builder.functions.append(helper_fn_ir)
- return helper_fn_decl
- def add_iter_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
- """Generates the '__iter__' method for a generator class."""
- with builder.enter_method(fn_info.generator_class.ir, "__iter__", object_rprimitive, fn_info):
- builder.add(Return(builder.self()))
- def add_next_to_generator_class(
- builder: IRBuilder, fn_info: FuncInfo, fn_decl: FuncDecl, sig: FuncSignature
- ) -> None:
- """Generates the '__next__' method for a generator class."""
- with builder.enter_method(fn_info.generator_class.ir, "__next__", object_rprimitive, fn_info):
- none_reg = builder.none_object()
- # Call the helper function with error flags set to Py_None, and return that result.
- result = builder.add(
- Call(
- fn_decl,
- [builder.self(), none_reg, none_reg, none_reg, none_reg],
- fn_info.fitem.line,
- )
- )
- builder.add(Return(result))
- def add_send_to_generator_class(
- builder: IRBuilder, fn_info: FuncInfo, fn_decl: FuncDecl, sig: FuncSignature
- ) -> None:
- """Generates the 'send' method for a generator class."""
- with builder.enter_method(fn_info.generator_class.ir, "send", object_rprimitive, fn_info):
- arg = builder.add_argument("arg", object_rprimitive)
- none_reg = builder.none_object()
- # Call the helper function with error flags set to Py_None, and return that result.
- result = builder.add(
- Call(
- fn_decl,
- [builder.self(), none_reg, none_reg, none_reg, builder.read(arg)],
- fn_info.fitem.line,
- )
- )
- builder.add(Return(result))
- def add_throw_to_generator_class(
- builder: IRBuilder, fn_info: FuncInfo, fn_decl: FuncDecl, sig: FuncSignature
- ) -> None:
- """Generates the 'throw' method for a generator class."""
- with builder.enter_method(fn_info.generator_class.ir, "throw", object_rprimitive, fn_info):
- typ = builder.add_argument("type", object_rprimitive)
- val = builder.add_argument("value", object_rprimitive, ARG_OPT)
- tb = builder.add_argument("traceback", object_rprimitive, ARG_OPT)
- # Because the value and traceback arguments are optional and hence
- # can be NULL if not passed in, we have to assign them Py_None if
- # they are not passed in.
- none_reg = builder.none_object()
- builder.assign_if_null(val, lambda: none_reg, builder.fn_info.fitem.line)
- builder.assign_if_null(tb, lambda: none_reg, builder.fn_info.fitem.line)
- # Call the helper function using the arguments passed in, and return that result.
- result = builder.add(
- Call(
- fn_decl,
- [builder.self(), builder.read(typ), builder.read(val), builder.read(tb), none_reg],
- fn_info.fitem.line,
- )
- )
- builder.add(Return(result))
- 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_await_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
- """Generates the '__await__' method for a generator class."""
- with builder.enter_method(fn_info.generator_class.ir, "__await__", object_rprimitive, fn_info):
- builder.add(Return(builder.self()))
- def setup_env_for_generator_class(builder: IRBuilder) -> None:
- """Populates the environment for a generator class."""
- fitem = builder.fn_info.fitem
- cls = builder.fn_info.generator_class
- self_target = builder.add_self_to_env(cls.ir)
- # Add the type, value, and traceback variables to the environment.
- exc_type = builder.add_local(Var("type"), object_rprimitive, is_arg=True)
- exc_val = builder.add_local(Var("value"), object_rprimitive, is_arg=True)
- exc_tb = builder.add_local(Var("traceback"), object_rprimitive, is_arg=True)
- # TODO: Use the right type here instead of object?
- exc_arg = builder.add_local(Var("arg"), object_rprimitive, is_arg=True)
- cls.exc_regs = (exc_type, exc_val, exc_tb)
- cls.send_arg_reg = exc_arg
- cls.self_reg = builder.read(self_target, fitem.line)
- cls.curr_env_reg = load_outer_env(builder, cls.self_reg, builder.symtables[-1])
- # Define a variable representing the label to go to the next time
- # the '__next__' function of the generator is called, and add it
- # as an attribute to the environment class.
- cls.next_label_target = builder.add_var_to_env_class(
- Var(NEXT_LABEL_ATTR_NAME), int_rprimitive, cls, reassign=False
- )
- # Add arguments from the original generator function to the
- # environment of the generator class.
- add_args_to_env(builder, local=False, base=cls, reassign=False)
- # Set the next label register for the generator class.
- cls.next_label_reg = builder.read(cls.next_label_target, fitem.line)
|