-
-
Notifications
You must be signed in to change notification settings - Fork 3k
[mypyc] Refactor IR building for generator functions #19008
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
fb2d005
[mypyc] Minor refactoring
JukkaL 9fb14ea
Minor refactoring
JukkaL 90af722
Refactor more
JukkaL edbe7ca
Refactor more
JukkaL 476ebe1
Refactor
JukkaL 756e362
Refactor
JukkaL d736b9f
Refactor more
JukkaL a65ca35
Refactor
JukkaL 52c8989
Lint
JukkaL 368db2d
Refactor
JukkaL 8f3d0bf
Refactor (partial)
JukkaL 300f2aa
Refactor more
JukkaL 3f1bcc8
Refactor
JukkaL 4e60d48
Update comment and docstring
JukkaL 3091e55
Minor return type refactor
JukkaL 0c28453
Change comment to docstring
JukkaL 2c495c8
Add review
JukkaL File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,7 +25,6 @@ | |
FuncItem, | ||
LambdaExpr, | ||
OverloadedFuncDef, | ||
SymbolNode, | ||
TypeInfo, | ||
Var, | ||
) | ||
|
@@ -44,7 +43,6 @@ | |
from mypyc.ir.ops import ( | ||
BasicBlock, | ||
GetAttr, | ||
InitStatic, | ||
Integer, | ||
LoadAddress, | ||
LoadLiteral, | ||
|
@@ -62,31 +60,22 @@ | |
int_rprimitive, | ||
object_rprimitive, | ||
) | ||
from mypyc.irbuild.builder import IRBuilder, SymbolTarget, gen_arg_defaults | ||
from mypyc.irbuild.builder import IRBuilder, calculate_arg_defaults, gen_arg_defaults | ||
from mypyc.irbuild.callable_class import ( | ||
add_call_to_callable_class, | ||
add_get_to_callable_class, | ||
instantiate_callable_class, | ||
setup_callable_class, | ||
) | ||
from mypyc.irbuild.context import FuncInfo, ImplicitClass | ||
from mypyc.irbuild.context import FuncInfo | ||
from mypyc.irbuild.env_class import ( | ||
add_vars_to_env, | ||
finalize_env_class, | ||
load_env_registers, | ||
load_outer_envs, | ||
setup_env_class, | ||
setup_func_for_recursive_call, | ||
) | ||
from mypyc.irbuild.generator import ( | ||
add_methods_to_generator_class, | ||
add_raise_exception_blocks_to_generator_class, | ||
create_switch_for_generator_class, | ||
gen_generator_func, | ||
populate_switch_for_generator_class, | ||
setup_env_for_generator_class, | ||
) | ||
from mypyc.irbuild.generator import gen_generator_func, gen_generator_func_body | ||
from mypyc.irbuild.targets import AssignmentTarget | ||
from mypyc.irbuild.util import is_constant | ||
from mypyc.primitives.dict_ops import dict_get_method_with_none, dict_new_op, dict_set_item_op | ||
from mypyc.primitives.generic_ops import py_setattr_op | ||
from mypyc.primitives.misc_ops import register_function | ||
|
@@ -235,123 +224,77 @@ def c() -> None: | |
func_name = singledispatch_main_func_name(name) | ||
else: | ||
func_name = name | ||
builder.enter( | ||
FuncInfo( | ||
fitem=fitem, | ||
name=func_name, | ||
class_name=class_name, | ||
namespace=gen_func_ns(builder), | ||
is_nested=is_nested, | ||
contains_nested=contains_nested, | ||
is_decorated=is_decorated, | ||
in_non_ext=in_non_ext, | ||
add_nested_funcs_to_env=add_nested_funcs_to_env, | ||
) | ||
|
||
fn_info = FuncInfo( | ||
fitem=fitem, | ||
name=func_name, | ||
class_name=class_name, | ||
namespace=gen_func_ns(builder), | ||
is_nested=is_nested, | ||
contains_nested=contains_nested, | ||
is_decorated=is_decorated, | ||
in_non_ext=in_non_ext, | ||
add_nested_funcs_to_env=add_nested_funcs_to_env, | ||
) | ||
is_generator = fn_info.is_generator | ||
builder.enter(fn_info, ret_type=sig.ret_type) | ||
|
||
# Functions that contain nested functions need an environment class to store variables that | ||
# are free in their nested functions. Generator functions need an environment class to | ||
# store a variable denoting the next instruction to be executed when the __next__ function | ||
# is called, along with all the variables inside the function itself. | ||
if builder.fn_info.contains_nested or builder.fn_info.is_generator: | ||
if contains_nested or is_generator: | ||
setup_env_class(builder) | ||
|
||
if builder.fn_info.is_nested or builder.fn_info.in_non_ext: | ||
if is_nested or in_non_ext: | ||
setup_callable_class(builder) | ||
|
||
if builder.fn_info.is_generator: | ||
# Do a first-pass and generate a function that just returns a generator object. | ||
gen_generator_func(builder) | ||
args, _, blocks, ret_type, fn_info = builder.leave() | ||
func_ir, func_reg = gen_func_ir( | ||
builder, args, blocks, sig, fn_info, cdef, is_singledispatch | ||
if is_generator: | ||
# First generate a function that just constructs and returns a generator object. | ||
func_ir, func_reg = gen_generator_func( | ||
builder, | ||
lambda args, blocks, fn_info: gen_func_ir( | ||
builder, args, blocks, sig, fn_info, cdef, is_singledispatch | ||
), | ||
) | ||
|
||
# Re-enter the FuncItem and visit the body of the function this time. | ||
builder.enter(fn_info) | ||
setup_env_for_generator_class(builder) | ||
|
||
load_outer_envs(builder, builder.fn_info.generator_class) | ||
top_level = builder.top_level_fn_info() | ||
if ( | ||
builder.fn_info.is_nested | ||
and isinstance(fitem, FuncDef) | ||
and top_level | ||
and top_level.add_nested_funcs_to_env | ||
): | ||
setup_func_for_recursive_call(builder, fitem, builder.fn_info.generator_class) | ||
create_switch_for_generator_class(builder) | ||
add_raise_exception_blocks_to_generator_class(builder, fitem.line) | ||
gen_generator_func_body(builder, fn_info, sig, func_reg) | ||
else: | ||
load_env_registers(builder) | ||
gen_arg_defaults(builder) | ||
func_ir, func_reg = gen_func_body(builder, sig, cdef, is_singledispatch) | ||
|
||
if builder.fn_info.contains_nested and not builder.fn_info.is_generator: | ||
finalize_env_class(builder) | ||
if is_singledispatch: | ||
# add the generated main singledispatch function | ||
builder.functions.append(func_ir) | ||
# create the dispatch function | ||
assert isinstance(fitem, FuncDef) | ||
return gen_dispatch_func_ir(builder, fitem, fn_info.name, name, sig) | ||
|
||
builder.ret_types[-1] = sig.ret_type | ||
return func_ir, func_reg | ||
|
||
# Add all variables and functions that are declared/defined within this | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The following code was moved to |
||
# function and are referenced in functions nested within this one to this | ||
# function's environment class so the nested functions can reference | ||
# them even if they are declared after the nested function's definition. | ||
# Note that this is done before visiting the body of this function. | ||
|
||
env_for_func: FuncInfo | ImplicitClass = builder.fn_info | ||
if builder.fn_info.is_generator: | ||
env_for_func = builder.fn_info.generator_class | ||
elif builder.fn_info.is_nested or builder.fn_info.in_non_ext: | ||
env_for_func = builder.fn_info.callable_class | ||
|
||
if builder.fn_info.fitem in builder.free_variables: | ||
# Sort the variables to keep things deterministic | ||
for var in sorted(builder.free_variables[builder.fn_info.fitem], key=lambda x: x.name): | ||
if isinstance(var, Var): | ||
rtype = builder.type_to_rtype(var.type) | ||
builder.add_var_to_env_class(var, rtype, env_for_func, reassign=False) | ||
|
||
if builder.fn_info.fitem in builder.encapsulating_funcs: | ||
for nested_fn in builder.encapsulating_funcs[builder.fn_info.fitem]: | ||
if isinstance(nested_fn, FuncDef): | ||
# The return type is 'object' instead of an RInstance of the | ||
# callable class because differently defined functions with | ||
# the same name and signature across conditional blocks | ||
# will generate different callable classes, so the callable | ||
# class that gets instantiated must be generic. | ||
builder.add_var_to_env_class( | ||
nested_fn, object_rprimitive, env_for_func, reassign=False | ||
) | ||
|
||
builder.accept(fitem.body) | ||
def gen_func_body( | ||
builder: IRBuilder, sig: FuncSignature, cdef: ClassDef | None, is_singledispatch: bool | ||
) -> tuple[FuncIR, Value | None]: | ||
load_env_registers(builder) | ||
gen_arg_defaults(builder) | ||
if builder.fn_info.contains_nested: | ||
finalize_env_class(builder) | ||
add_vars_to_env(builder) | ||
builder.accept(builder.fn_info.fitem.body) | ||
builder.maybe_add_implicit_return() | ||
|
||
if builder.fn_info.is_generator: | ||
populate_switch_for_generator_class(builder) | ||
|
||
# Hang on to the local symbol table for a while, since we use it | ||
# to calculate argument defaults below. | ||
symtable = builder.symtables[-1] | ||
|
||
args, _, blocks, ret_type, fn_info = builder.leave() | ||
|
||
if fn_info.is_generator: | ||
add_methods_to_generator_class(builder, fn_info, sig, args, blocks, fitem.is_coroutine) | ||
else: | ||
func_ir, func_reg = gen_func_ir( | ||
builder, args, blocks, sig, fn_info, cdef, is_singledispatch | ||
) | ||
func_ir, func_reg = gen_func_ir(builder, args, blocks, sig, fn_info, cdef, is_singledispatch) | ||
|
||
# Evaluate argument defaults in the surrounding scope, since we | ||
# calculate them *once* when the function definition is evaluated. | ||
calculate_arg_defaults(builder, fn_info, func_reg, symtable) | ||
|
||
if is_singledispatch: | ||
# add the generated main singledispatch function | ||
builder.functions.append(func_ir) | ||
# create the dispatch function | ||
assert isinstance(fitem, FuncDef) | ||
return gen_dispatch_func_ir(builder, fitem, fn_info.name, name, sig) | ||
|
||
return func_ir, func_reg | ||
|
||
|
||
|
@@ -512,33 +455,6 @@ def handle_non_ext_method( | |
builder.add_to_non_ext_dict(non_ext, name, func_reg, fdef.line) | ||
|
||
|
||
def calculate_arg_defaults( | ||
builder: IRBuilder, | ||
fn_info: FuncInfo, | ||
func_reg: Value | None, | ||
symtable: dict[SymbolNode, SymbolTarget], | ||
) -> None: | ||
"""Calculate default argument values and store them. | ||
|
||
They are stored in statics for top level functions and in | ||
the function objects for nested functions (while constants are | ||
still stored computed on demand). | ||
""" | ||
fitem = fn_info.fitem | ||
for arg in fitem.arguments: | ||
# Constant values don't get stored but just recomputed | ||
if arg.initializer and not is_constant(arg.initializer): | ||
value = builder.coerce( | ||
builder.accept(arg.initializer), symtable[arg.variable].type, arg.line | ||
) | ||
if not fn_info.is_nested: | ||
name = fitem.fullname + "." + arg.variable.name | ||
builder.add(InitStatic(value, name, builder.module_name)) | ||
else: | ||
assert func_reg is not None | ||
builder.add(SetAttr(func_reg, arg.variable.name, value, arg.line)) | ||
|
||
|
||
def gen_func_ns(builder: IRBuilder) -> str: | ||
"""Generate a namespace for a nested function using its outer function names.""" | ||
return "_".join( | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Move this code here since this is used for both regular and generator functions.