env_class.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. """Generate classes representing function environments (+ related operations).
  2. If we have a nested function that has non-local (free) variables, access to the
  3. non-locals is via an instance of an environment class. Example:
  4. def f() -> int:
  5. x = 0 # Make 'x' an attribute of an environment class instance
  6. def g() -> int:
  7. # We have access to the environment class instance to
  8. # allow accessing 'x'
  9. return x + 2
  10. x = x + 1 # Modify the attribute
  11. return g()
  12. """
  13. from __future__ import annotations
  14. from mypy.nodes import Argument, FuncDef, SymbolNode, Var
  15. from mypyc.common import BITMAP_BITS, ENV_ATTR_NAME, SELF_NAME, bitmap_name
  16. from mypyc.ir.class_ir import ClassIR
  17. from mypyc.ir.ops import Call, GetAttr, SetAttr, Value
  18. from mypyc.ir.rtypes import RInstance, bitmap_rprimitive, object_rprimitive
  19. from mypyc.irbuild.builder import IRBuilder, SymbolTarget
  20. from mypyc.irbuild.context import FuncInfo, GeneratorClass, ImplicitClass
  21. from mypyc.irbuild.targets import AssignmentTargetAttr
  22. def setup_env_class(builder: IRBuilder) -> ClassIR:
  23. """Generate a class representing a function environment.
  24. Note that the variables in the function environment are not
  25. actually populated here. This is because when the environment
  26. class is generated, the function environment has not yet been
  27. visited. This behavior is allowed so that when the compiler visits
  28. nested functions, it can use the returned ClassIR instance to
  29. figure out free variables it needs to access. The remaining
  30. attributes of the environment class are populated when the
  31. environment registers are loaded.
  32. Return a ClassIR representing an environment for a function
  33. containing a nested function.
  34. """
  35. env_class = ClassIR(
  36. f"{builder.fn_info.namespaced_name()}_env", builder.module_name, is_generated=True
  37. )
  38. env_class.attributes[SELF_NAME] = RInstance(env_class)
  39. if builder.fn_info.is_nested:
  40. # If the function is nested, its environment class must contain an environment
  41. # attribute pointing to its encapsulating functions' environment class.
  42. env_class.attributes[ENV_ATTR_NAME] = RInstance(builder.fn_infos[-2].env_class)
  43. env_class.mro = [env_class]
  44. builder.fn_info.env_class = env_class
  45. builder.classes.append(env_class)
  46. return env_class
  47. def finalize_env_class(builder: IRBuilder) -> None:
  48. """Generate, instantiate, and set up the environment of an environment class."""
  49. instantiate_env_class(builder)
  50. # Iterate through the function arguments and replace local definitions (using registers)
  51. # that were previously added to the environment with references to the function's
  52. # environment class.
  53. if builder.fn_info.is_nested:
  54. add_args_to_env(builder, local=False, base=builder.fn_info.callable_class)
  55. else:
  56. add_args_to_env(builder, local=False, base=builder.fn_info)
  57. def instantiate_env_class(builder: IRBuilder) -> Value:
  58. """Assign an environment class to a register named after the given function definition."""
  59. curr_env_reg = builder.add(
  60. Call(builder.fn_info.env_class.ctor, [], builder.fn_info.fitem.line)
  61. )
  62. if builder.fn_info.is_nested:
  63. builder.fn_info.callable_class._curr_env_reg = curr_env_reg
  64. builder.add(
  65. SetAttr(
  66. curr_env_reg,
  67. ENV_ATTR_NAME,
  68. builder.fn_info.callable_class.prev_env_reg,
  69. builder.fn_info.fitem.line,
  70. )
  71. )
  72. else:
  73. builder.fn_info._curr_env_reg = curr_env_reg
  74. return curr_env_reg
  75. def load_env_registers(builder: IRBuilder) -> None:
  76. """Load the registers for the current FuncItem being visited.
  77. Adds the arguments of the FuncItem to the environment. If the
  78. FuncItem is nested inside of another function, then this also
  79. loads all of the outer environments of the FuncItem into registers
  80. so that they can be used when accessing free variables.
  81. """
  82. add_args_to_env(builder, local=True)
  83. fn_info = builder.fn_info
  84. fitem = fn_info.fitem
  85. if fn_info.is_nested:
  86. load_outer_envs(builder, fn_info.callable_class)
  87. # If this is a FuncDef, then make sure to load the FuncDef into its own environment
  88. # class so that the function can be called recursively.
  89. if isinstance(fitem, FuncDef):
  90. setup_func_for_recursive_call(builder, fitem, fn_info.callable_class)
  91. def load_outer_env(
  92. builder: IRBuilder, base: Value, outer_env: dict[SymbolNode, SymbolTarget]
  93. ) -> Value:
  94. """Load the environment class for a given base into a register.
  95. Additionally, iterates through all of the SymbolNode and
  96. AssignmentTarget instances of the environment at the given index's
  97. symtable, and adds those instances to the environment of the
  98. current environment. This is done so that the current environment
  99. can access outer environment variables without having to reload
  100. all of the environment registers.
  101. Returns the register where the environment class was loaded.
  102. """
  103. env = builder.add(GetAttr(base, ENV_ATTR_NAME, builder.fn_info.fitem.line))
  104. assert isinstance(env.type, RInstance), f"{env} must be of type RInstance"
  105. for symbol, target in outer_env.items():
  106. env.type.class_ir.attributes[symbol.name] = target.type
  107. symbol_target = AssignmentTargetAttr(env, symbol.name)
  108. builder.add_target(symbol, symbol_target)
  109. return env
  110. def load_outer_envs(builder: IRBuilder, base: ImplicitClass) -> None:
  111. index = len(builder.builders) - 2
  112. # Load the first outer environment. This one is special because it gets saved in the
  113. # FuncInfo instance's prev_env_reg field.
  114. if index > 1:
  115. # outer_env = builder.fn_infos[index].environment
  116. outer_env = builder.symtables[index]
  117. if isinstance(base, GeneratorClass):
  118. base.prev_env_reg = load_outer_env(builder, base.curr_env_reg, outer_env)
  119. else:
  120. base.prev_env_reg = load_outer_env(builder, base.self_reg, outer_env)
  121. env_reg = base.prev_env_reg
  122. index -= 1
  123. # Load the remaining outer environments into registers.
  124. while index > 1:
  125. # outer_env = builder.fn_infos[index].environment
  126. outer_env = builder.symtables[index]
  127. env_reg = load_outer_env(builder, env_reg, outer_env)
  128. index -= 1
  129. def num_bitmap_args(builder: IRBuilder, args: list[Argument]) -> int:
  130. n = 0
  131. for arg in args:
  132. t = builder.type_to_rtype(arg.variable.type)
  133. if t.error_overlap and arg.kind.is_optional():
  134. n += 1
  135. return (n + (BITMAP_BITS - 1)) // BITMAP_BITS
  136. def add_args_to_env(
  137. builder: IRBuilder,
  138. local: bool = True,
  139. base: FuncInfo | ImplicitClass | None = None,
  140. reassign: bool = True,
  141. ) -> None:
  142. fn_info = builder.fn_info
  143. args = fn_info.fitem.arguments
  144. nb = num_bitmap_args(builder, args)
  145. if local:
  146. for arg in args:
  147. rtype = builder.type_to_rtype(arg.variable.type)
  148. builder.add_local_reg(arg.variable, rtype, is_arg=True)
  149. for i in reversed(range(nb)):
  150. builder.add_local_reg(Var(bitmap_name(i)), bitmap_rprimitive, is_arg=True)
  151. else:
  152. for arg in args:
  153. if is_free_variable(builder, arg.variable) or fn_info.is_generator:
  154. rtype = builder.type_to_rtype(arg.variable.type)
  155. assert base is not None, "base cannot be None for adding nonlocal args"
  156. builder.add_var_to_env_class(arg.variable, rtype, base, reassign=reassign)
  157. def setup_func_for_recursive_call(builder: IRBuilder, fdef: FuncDef, base: ImplicitClass) -> None:
  158. """Enable calling a nested function (with a callable class) recursively.
  159. Adds the instance of the callable class representing the given
  160. FuncDef to a register in the environment so that the function can
  161. be called recursively. Note that this needs to be done only for
  162. nested functions.
  163. """
  164. # First, set the attribute of the environment class so that GetAttr can be called on it.
  165. prev_env = builder.fn_infos[-2].env_class
  166. prev_env.attributes[fdef.name] = builder.type_to_rtype(fdef.type)
  167. if isinstance(base, GeneratorClass):
  168. # If we are dealing with a generator class, then we need to first get the register
  169. # holding the current environment class, and load the previous environment class from
  170. # there.
  171. prev_env_reg = builder.add(GetAttr(base.curr_env_reg, ENV_ATTR_NAME, -1))
  172. else:
  173. prev_env_reg = base.prev_env_reg
  174. # Obtain the instance of the callable class representing the FuncDef, and add it to the
  175. # current environment.
  176. val = builder.add(GetAttr(prev_env_reg, fdef.name, -1))
  177. target = builder.add_local_reg(fdef, object_rprimitive)
  178. builder.assign(target, val, -1)
  179. def is_free_variable(builder: IRBuilder, symbol: SymbolNode) -> bool:
  180. fitem = builder.fn_info.fitem
  181. return fitem in builder.free_variables and symbol in builder.free_variables[fitem]