gh-151907: Avoid creating temporary objects in some comprehensions#151908
gh-151907: Avoid creating temporary objects in some comprehensions#151908ZeroIntensity wants to merge 13 commits into
Conversation
|
I think this might change semantics for the dict case - if a key is not hashable. |
|
Yeah, good catch. This breaks {x for x in [[]]} # No error!I'll just remove |
This was a breaking change because hash() was no longer called on the elements.
Maybe also add tests for these cases. |
| ] | ||
| self.codegen_test(snippet, expected) | ||
|
|
||
| def test_no_target_comp_optimization(self): |
There was a problem hiding this comment.
I believe you need to add a test where the loop body has a side effect. IIUC, the body with thread.join() should be turned into a method call, rather than just pass.
|
The async case is not handled correctly here. This segfaults: |
|
Fixed. |
Avoids an "env-changed" error with regrtest.
| if (value->kind == ListComp_kind) { | ||
| /* Optimization: Don't bother creating structures if they won't be | ||
| * used. */ | ||
| return codegen_listcomp_impl(c, value, /*avoid_creation=*/true); |
There was a problem hiding this comment.
It might be nicer if we were not calling codegen_listcomp_impl here but rather setting a boolean on the compiler_unit struct to track whether we are in the topmost expression or not. What do you think?
There was a problem hiding this comment.
Not opposed to the idea, but it feels slightly weird in cases where the thing being compiled isn't an expression. A compiler->has_no_target flag in a FunctionDef, for example, has no real meaning (because compiler->has_no_target == 0 would imply "has target", but that doesn't make sense for a statement). I do think it'll make future optimizations like this easier, though.
There was a problem hiding this comment.
I was thinking of a flag like is_subexpression.
There was a problem hiding this comment.
How would I implement that? I can't do like METADATA(c)->u_is_subexpression = 1 in codegen_stmt_expr because that would also affect listcomps inside the one we're optimizing (like [x for x in [y for y in z]]). I could add a stack or something like that, but that would significantly increase the complexity of this PR.
There was a problem hiding this comment.
How about something like this:
static int
codegen_visit_expr(compiler *c, expr_ty e)
{
if (Py_EnterRecursiveCall(" during compilation")) {
return ERROR;
}
return codegen_visit_expr_impl(c, e, false);
}
static int
codegen_visit_unused_expr(compiler *c, expr_ty e)
{
return codegen_visit_expr_impl(c, e, true);
}
static int
codegen_visit_expr_impl(compiler *c, expr_ty e, bool result_is_unused)
{
#define VISIT_UNUSED(C, TYPE, V) \
RETURN_IF_ERROR(codegen_visit_unused_ ## TYPE((C), (V)))
and then in codegen_stmt_expr use VISIT_UNUSED instead of VISIT?
Then codegen_stmt_expr doesn't need to do anything special for ListComp_kind.
There was a problem hiding this comment.
I like that idea. I just pushed it, let me know what you think.
Co-authored-by: Irit Katriel <1055913+iritkatriel@users.noreply.github.com>
| ADDOP_I(c, loc, SWAP, 2); | ||
| } | ||
| } else { | ||
| ADDOP_I(c, loc, COPY, 1); |
There was a problem hiding this comment.
can is_inlined be True here?
There was a problem hiding this comment.
Yes, do I need to do anything special for it?
| VISIT(c, expr, elt); | ||
| ADDOP(c, elt_loc, POP_TOP); | ||
| break; | ||
| } |
There was a problem hiding this comment.
I think we need to apply VISIT to the starred case even when !avoid_creation, in case unpacking errors. No?
| case COMP_LISTCOMP: | ||
| if (avoid_creation) { | ||
| if (elt->kind == Starred_kind) { | ||
| RETURN_IF_ERROR(codegen_starred_comprehension(c, elt_loc, elt->v.Starred.value, /*discard=*/true)); |
There was a problem hiding this comment.
I don't follow. Why don't we just do VISIT(c, expr, elt->v.Starred.value); like below for the !avoid_creation case?
There was a problem hiding this comment.
elt->v.Starred.value only creates an iterator object, but we need to exhaust the whole thing in order to get all the side effects. It wasn't necessary before because LIST_EXTEND does the unpacking for us.
| } | ||
|
|
||
| static int | ||
| codegen_starred_comprehension(compiler *c, location loc, expr_ty value, bool discard) |
There was a problem hiding this comment.
This name doesn't seem correct. This function is not actually about comprehensions, right?
The following code:
is essentially turned into: