generator.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. """Generate IR for generator functions.
  2. A generator function is represented by a class that implements the
  3. generator protocol and keeps track of the generator state, including
  4. local variables.
  5. The top-level logic for dealing with generator functions is in
  6. mypyc.irbuild.function.
  7. """
  8. from __future__ import annotations
  9. from mypy.nodes import ARG_OPT, Var
  10. from mypyc.common import ENV_ATTR_NAME, NEXT_LABEL_ATTR_NAME, SELF_NAME
  11. from mypyc.ir.class_ir import ClassIR
  12. from mypyc.ir.func_ir import FuncDecl, FuncIR, FuncSignature, RuntimeArg
  13. from mypyc.ir.ops import (
  14. NO_TRACEBACK_LINE_NO,
  15. BasicBlock,
  16. Branch,
  17. Call,
  18. Goto,
  19. Integer,
  20. MethodCall,
  21. RaiseStandardError,
  22. Register,
  23. Return,
  24. SetAttr,
  25. TupleSet,
  26. Unreachable,
  27. Value,
  28. )
  29. from mypyc.ir.rtypes import RInstance, int_rprimitive, object_rprimitive
  30. from mypyc.irbuild.builder import IRBuilder, gen_arg_defaults
  31. from mypyc.irbuild.context import FuncInfo, GeneratorClass
  32. from mypyc.irbuild.env_class import (
  33. add_args_to_env,
  34. finalize_env_class,
  35. load_env_registers,
  36. load_outer_env,
  37. )
  38. from mypyc.irbuild.nonlocalcontrol import ExceptNonlocalControl
  39. from mypyc.primitives.exc_ops import (
  40. error_catch_op,
  41. exc_matches_op,
  42. raise_exception_with_tb_op,
  43. reraise_exception_op,
  44. restore_exc_info_op,
  45. )
  46. def gen_generator_func(builder: IRBuilder) -> None:
  47. setup_generator_class(builder)
  48. load_env_registers(builder)
  49. gen_arg_defaults(builder)
  50. finalize_env_class(builder)
  51. builder.add(Return(instantiate_generator_class(builder)))
  52. def instantiate_generator_class(builder: IRBuilder) -> Value:
  53. fitem = builder.fn_info.fitem
  54. generator_reg = builder.add(Call(builder.fn_info.generator_class.ir.ctor, [], fitem.line))
  55. # Get the current environment register. If the current function is nested, then the
  56. # generator class gets instantiated from the callable class' '__call__' method, and hence
  57. # we use the callable class' environment register. Otherwise, we use the original
  58. # function's environment register.
  59. if builder.fn_info.is_nested:
  60. curr_env_reg = builder.fn_info.callable_class.curr_env_reg
  61. else:
  62. curr_env_reg = builder.fn_info.curr_env_reg
  63. # Set the generator class' environment attribute to point at the environment class
  64. # defined in the current scope.
  65. builder.add(SetAttr(generator_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line))
  66. # Set the generator class' environment class' NEXT_LABEL_ATTR_NAME attribute to 0.
  67. zero = Integer(0)
  68. builder.add(SetAttr(curr_env_reg, NEXT_LABEL_ATTR_NAME, zero, fitem.line))
  69. return generator_reg
  70. def setup_generator_class(builder: IRBuilder) -> ClassIR:
  71. name = f"{builder.fn_info.namespaced_name()}_gen"
  72. generator_class_ir = ClassIR(name, builder.module_name, is_generated=True)
  73. generator_class_ir.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_info.env_class)
  74. generator_class_ir.mro = [generator_class_ir]
  75. builder.classes.append(generator_class_ir)
  76. builder.fn_info.generator_class = GeneratorClass(generator_class_ir)
  77. return generator_class_ir
  78. def create_switch_for_generator_class(builder: IRBuilder) -> None:
  79. builder.add(Goto(builder.fn_info.generator_class.switch_block))
  80. block = BasicBlock()
  81. builder.fn_info.generator_class.continuation_blocks.append(block)
  82. builder.activate_block(block)
  83. def populate_switch_for_generator_class(builder: IRBuilder) -> None:
  84. cls = builder.fn_info.generator_class
  85. line = builder.fn_info.fitem.line
  86. builder.activate_block(cls.switch_block)
  87. for label, true_block in enumerate(cls.continuation_blocks):
  88. false_block = BasicBlock()
  89. comparison = builder.binary_op(cls.next_label_reg, Integer(label), "==", line)
  90. builder.add_bool_branch(comparison, true_block, false_block)
  91. builder.activate_block(false_block)
  92. builder.add(RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, line))
  93. builder.add(Unreachable())
  94. def add_raise_exception_blocks_to_generator_class(builder: IRBuilder, line: int) -> None:
  95. """Add error handling blocks to a generator class.
  96. Generates blocks to check if error flags are set while calling the
  97. helper method for generator functions, and raises an exception if
  98. those flags are set.
  99. """
  100. cls = builder.fn_info.generator_class
  101. assert cls.exc_regs is not None
  102. exc_type, exc_val, exc_tb = cls.exc_regs
  103. # Check to see if an exception was raised.
  104. error_block = BasicBlock()
  105. ok_block = BasicBlock()
  106. comparison = builder.translate_is_op(exc_type, builder.none_object(), "is not", line)
  107. builder.add_bool_branch(comparison, error_block, ok_block)
  108. builder.activate_block(error_block)
  109. builder.call_c(raise_exception_with_tb_op, [exc_type, exc_val, exc_tb], line)
  110. builder.add(Unreachable())
  111. builder.goto_and_activate(ok_block)
  112. def add_methods_to_generator_class(
  113. builder: IRBuilder,
  114. fn_info: FuncInfo,
  115. sig: FuncSignature,
  116. arg_regs: list[Register],
  117. blocks: list[BasicBlock],
  118. is_coroutine: bool,
  119. ) -> None:
  120. helper_fn_decl = add_helper_to_generator_class(builder, arg_regs, blocks, sig, fn_info)
  121. add_next_to_generator_class(builder, fn_info, helper_fn_decl, sig)
  122. add_send_to_generator_class(builder, fn_info, helper_fn_decl, sig)
  123. add_iter_to_generator_class(builder, fn_info)
  124. add_throw_to_generator_class(builder, fn_info, helper_fn_decl, sig)
  125. add_close_to_generator_class(builder, fn_info)
  126. if is_coroutine:
  127. add_await_to_generator_class(builder, fn_info)
  128. def add_helper_to_generator_class(
  129. builder: IRBuilder,
  130. arg_regs: list[Register],
  131. blocks: list[BasicBlock],
  132. sig: FuncSignature,
  133. fn_info: FuncInfo,
  134. ) -> FuncDecl:
  135. """Generates a helper method for a generator class, called by '__next__' and 'throw'."""
  136. sig = FuncSignature(
  137. (
  138. RuntimeArg(SELF_NAME, object_rprimitive),
  139. RuntimeArg("type", object_rprimitive),
  140. RuntimeArg("value", object_rprimitive),
  141. RuntimeArg("traceback", object_rprimitive),
  142. RuntimeArg("arg", object_rprimitive),
  143. ),
  144. sig.ret_type,
  145. )
  146. helper_fn_decl = FuncDecl(
  147. "__mypyc_generator_helper__", fn_info.generator_class.ir.name, builder.module_name, sig
  148. )
  149. helper_fn_ir = FuncIR(
  150. helper_fn_decl, arg_regs, blocks, fn_info.fitem.line, traceback_name=fn_info.fitem.name
  151. )
  152. fn_info.generator_class.ir.methods["__mypyc_generator_helper__"] = helper_fn_ir
  153. builder.functions.append(helper_fn_ir)
  154. return helper_fn_decl
  155. def add_iter_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
  156. """Generates the '__iter__' method for a generator class."""
  157. with builder.enter_method(fn_info.generator_class.ir, "__iter__", object_rprimitive, fn_info):
  158. builder.add(Return(builder.self()))
  159. def add_next_to_generator_class(
  160. builder: IRBuilder, fn_info: FuncInfo, fn_decl: FuncDecl, sig: FuncSignature
  161. ) -> None:
  162. """Generates the '__next__' method for a generator class."""
  163. with builder.enter_method(fn_info.generator_class.ir, "__next__", object_rprimitive, fn_info):
  164. none_reg = builder.none_object()
  165. # Call the helper function with error flags set to Py_None, and return that result.
  166. result = builder.add(
  167. Call(
  168. fn_decl,
  169. [builder.self(), none_reg, none_reg, none_reg, none_reg],
  170. fn_info.fitem.line,
  171. )
  172. )
  173. builder.add(Return(result))
  174. def add_send_to_generator_class(
  175. builder: IRBuilder, fn_info: FuncInfo, fn_decl: FuncDecl, sig: FuncSignature
  176. ) -> None:
  177. """Generates the 'send' method for a generator class."""
  178. with builder.enter_method(fn_info.generator_class.ir, "send", object_rprimitive, fn_info):
  179. arg = builder.add_argument("arg", object_rprimitive)
  180. none_reg = builder.none_object()
  181. # Call the helper function with error flags set to Py_None, and return that result.
  182. result = builder.add(
  183. Call(
  184. fn_decl,
  185. [builder.self(), none_reg, none_reg, none_reg, builder.read(arg)],
  186. fn_info.fitem.line,
  187. )
  188. )
  189. builder.add(Return(result))
  190. def add_throw_to_generator_class(
  191. builder: IRBuilder, fn_info: FuncInfo, fn_decl: FuncDecl, sig: FuncSignature
  192. ) -> None:
  193. """Generates the 'throw' method for a generator class."""
  194. with builder.enter_method(fn_info.generator_class.ir, "throw", object_rprimitive, fn_info):
  195. typ = builder.add_argument("type", object_rprimitive)
  196. val = builder.add_argument("value", object_rprimitive, ARG_OPT)
  197. tb = builder.add_argument("traceback", object_rprimitive, ARG_OPT)
  198. # Because the value and traceback arguments are optional and hence
  199. # can be NULL if not passed in, we have to assign them Py_None if
  200. # they are not passed in.
  201. none_reg = builder.none_object()
  202. builder.assign_if_null(val, lambda: none_reg, builder.fn_info.fitem.line)
  203. builder.assign_if_null(tb, lambda: none_reg, builder.fn_info.fitem.line)
  204. # Call the helper function using the arguments passed in, and return that result.
  205. result = builder.add(
  206. Call(
  207. fn_decl,
  208. [builder.self(), builder.read(typ), builder.read(val), builder.read(tb), none_reg],
  209. fn_info.fitem.line,
  210. )
  211. )
  212. builder.add(Return(result))
  213. def add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
  214. """Generates the '__close__' method for a generator class."""
  215. with builder.enter_method(fn_info.generator_class.ir, "close", object_rprimitive, fn_info):
  216. except_block, else_block = BasicBlock(), BasicBlock()
  217. builder.builder.push_error_handler(except_block)
  218. builder.goto_and_activate(BasicBlock())
  219. generator_exit = builder.load_module_attr_by_fullname(
  220. "builtins.GeneratorExit", fn_info.fitem.line
  221. )
  222. builder.add(
  223. MethodCall(
  224. builder.self(),
  225. "throw",
  226. [generator_exit, builder.none_object(), builder.none_object()],
  227. )
  228. )
  229. builder.goto(else_block)
  230. builder.builder.pop_error_handler()
  231. builder.activate_block(except_block)
  232. old_exc = builder.call_c(error_catch_op, [], fn_info.fitem.line)
  233. builder.nonlocal_control.append(
  234. ExceptNonlocalControl(builder.nonlocal_control[-1], old_exc)
  235. )
  236. stop_iteration = builder.load_module_attr_by_fullname(
  237. "builtins.StopIteration", fn_info.fitem.line
  238. )
  239. exceptions = builder.add(TupleSet([generator_exit, stop_iteration], fn_info.fitem.line))
  240. matches = builder.call_c(exc_matches_op, [exceptions], fn_info.fitem.line)
  241. match_block, non_match_block = BasicBlock(), BasicBlock()
  242. builder.add(Branch(matches, match_block, non_match_block, Branch.BOOL))
  243. builder.activate_block(match_block)
  244. builder.call_c(restore_exc_info_op, [builder.read(old_exc)], fn_info.fitem.line)
  245. builder.add(Return(builder.none_object()))
  246. builder.activate_block(non_match_block)
  247. builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO)
  248. builder.add(Unreachable())
  249. builder.nonlocal_control.pop()
  250. builder.activate_block(else_block)
  251. builder.add(
  252. RaiseStandardError(
  253. RaiseStandardError.RUNTIME_ERROR,
  254. "generator ignored GeneratorExit",
  255. fn_info.fitem.line,
  256. )
  257. )
  258. builder.add(Unreachable())
  259. def add_await_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None:
  260. """Generates the '__await__' method for a generator class."""
  261. with builder.enter_method(fn_info.generator_class.ir, "__await__", object_rprimitive, fn_info):
  262. builder.add(Return(builder.self()))
  263. def setup_env_for_generator_class(builder: IRBuilder) -> None:
  264. """Populates the environment for a generator class."""
  265. fitem = builder.fn_info.fitem
  266. cls = builder.fn_info.generator_class
  267. self_target = builder.add_self_to_env(cls.ir)
  268. # Add the type, value, and traceback variables to the environment.
  269. exc_type = builder.add_local(Var("type"), object_rprimitive, is_arg=True)
  270. exc_val = builder.add_local(Var("value"), object_rprimitive, is_arg=True)
  271. exc_tb = builder.add_local(Var("traceback"), object_rprimitive, is_arg=True)
  272. # TODO: Use the right type here instead of object?
  273. exc_arg = builder.add_local(Var("arg"), object_rprimitive, is_arg=True)
  274. cls.exc_regs = (exc_type, exc_val, exc_tb)
  275. cls.send_arg_reg = exc_arg
  276. cls.self_reg = builder.read(self_target, fitem.line)
  277. cls.curr_env_reg = load_outer_env(builder, cls.self_reg, builder.symtables[-1])
  278. # Define a variable representing the label to go to the next time
  279. # the '__next__' function of the generator is called, and add it
  280. # as an attribute to the environment class.
  281. cls.next_label_target = builder.add_var_to_env_class(
  282. Var(NEXT_LABEL_ATTR_NAME), int_rprimitive, cls, reassign=False
  283. )
  284. # Add arguments from the original generator function to the
  285. # environment of the generator class.
  286. add_args_to_env(builder, local=False, base=cls, reassign=False)
  287. # Set the next label register for the generator class.
  288. cls.next_label_reg = builder.read(cls.next_label_target, fitem.line)