-
-
Notifications
You must be signed in to change notification settings - Fork 31.7k
gh-98831: Support conditional effects; use for LOAD_ATTR #101333
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
Conversation
(Semantics aren't there yet, and the test fails.)
First use of conditional stack effects.
Python/bytecodes.c
Outdated
@@ -1438,13 +1438,11 @@ dummy_func( | |||
PREDICT(JUMP_BACKWARD); | |||
} | |||
|
|||
// error: LOAD_ATTR has irregular stack effect | |||
inst(LOAD_ATTR) { | |||
inst(LOAD_ATTR, (unused/9, owner -- res, res2 if (oparg & 1))) { |
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.
Check out my comment in the generated code about 'res2'.
Python/generated_cases.c.h
Outdated
@@ -1745,11 +1745,13 @@ | |||
|
|||
TARGET(LOAD_ATTR) { | |||
PREDICTED(LOAD_ATTR); | |||
PyObject *owner = PEEK(1); | |||
PyObject *res; | |||
PyObject *res2; |
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.
Should this initialize res2 = NULL
? Right now I get compiler warnings (in GitHub) about a possibly uninitialized variable:
‘res2’ may be used uninitialized in this function [-Wmaybe-uninitialized]
presumably from the PUSH()
on L1798.
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.
I think the issue is that the compiler can't see that if (oparg & 1)
is the same as if (_cond_res2)
, so it can't tell that res2 is always initialised when it is used. Not sure what to do about it, add a syntax in the DSL to access the condition from within the bytecode definition?
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.
I'll just initialize it to NULL. Perhaps some later phase of optimization will recognize that for the dead value it is.
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.
I think if we rename _cond_res2
to res2_exists
and then in the body of the bytecode we do if(res2_exists) /* use res2 */
instead of if(oparg &1) /* use res2 */
then it would make the code clearer. It's also safer, because if you change the args to the instr then oparg&1
might not be correct anymore, but res2_exists
will be automatically updated.
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.
Okay, let's try that. I had a commit in the queue that initializes the variable to NULL, which I think would also get rid of the problem (if not I'd be very surprised) but since I didn't push that let's first try it with your idea.
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.
Hm, looks like it doesn't help, the error is still there. So instead let me try this with the (much simpler) "= NULL" solution, which has to work. In fact, I may also drop the flag variable idea, and solve the warning about bool by writing X ? 1 : 0
instead of X != 0
.
Python/generated_cases.c.h
Outdated
} | ||
JUMPBY(INLINE_CACHE_ENTRIES_LOAD_ATTR); | ||
POKE(1, res); | ||
if (oparg & 1) { PUSH(res2); } |
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.
Here (I think).
- UndefinedLocalError when generating metadata for an 'op' - Accidental newline inserted in test_generator.py
POKE(1, xx); | ||
if (oparg & 2) { PUSH(output); } | ||
PUSH(zz); |
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.
Could we stick with PEEK and POKE and here have something like this?
POKE(1, xx); | |
if (oparg & 2) { PUSH(output); } | |
PUSH(zz); | |
POKE((oparg & 2) + 2, xx); | |
if (oparg & 2) { POKE((oparg & 2) + 1, output); } | |
POKE(1, zz); |
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.
Interesting. This would favor cases where the conditional is at the bottom of the inputs (e.g. a if (...), b, c
). But I think it would become quite horrible if you have several conditions, like we have in MAKE_FUNCTION.
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.
If there are several conditions then the concatenation of the conditions (and the unconditional 1's) gives you the index.
The generator will be simpler, the generated code will have these expressions for indices, but I think that's probably easier to follow than a mixture of pushes and pokes.
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.
I'll play around with that.
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.
Have a look. The generator sure is a lot simpler, I think I can live with the more complicated generated code. What made this solution so nice is that we already had the symbolic size -- the condition just gets added into that. Thanks for the nudge!
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.
Notes to self.
@@ -59,7 +61,10 @@ def effect_size(effect: StackEffect) -> tuple[int, str]: | |||
At most one of these will be non-zero / non-empty. | |||
""" | |||
if effect.size: | |||
assert not effect.cond, "Manual effects should be conditional" |
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.
This message is incorrect.
if "boerenkool" in dst.name or "boerenkool" in src.name: | ||
breakpoint() |
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.
Debug output accidentally left in.
# elif m := re.match(r"^POP\(\)$", dst.name): | ||
# if src.cond: | ||
# self.emit(f"if ({src.cond}) {{ PUSH({cast}{src.name}); }}") | ||
# else: | ||
# self.emit(f"PUSH({cast}{src.name});") |
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.
Delete commented-out block.
@@ -320,6 +354,7 @@ def write(self, out: Formatter) -> None: | |||
else: | |||
# Write output register assignments | |||
for oeffect, reg in zip(self.output_effects, self.output_registers): | |||
src = StackEffect(reg, "") |
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.
Don't need this.
Python/generated_cases.c.h
Outdated
STACK_GROW(((oparg & 1) != 0)); | ||
POKE(1, res); | ||
if (oparg & 1) { POKE(1 + ((oparg & 1) != 0), res2); } |
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.
It's unfortunate that all new versions test for oparg & 1
several times, whereas the original version tested that condition exactly once.
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.
I assumed the compiler would do this, but we could assign ((oparg& i) != 0) to something. I think it’s only used as a condition in one place, in other places it’s just int arithmetic.
I don't understand what this warning is about:
|
I assume it's about this line (L1797):
The compiler probably reasons that That's silly since it doesn't warn us about the equally trivial I'm not sure what to do about it. PS. I'm still looking into your suggestion to assign the condition to a flag variable, it's a bit messy. |
Here's the new version. Let me know if you like it enough to go ahead -- if not, I can easily roll it back. Note that I also fixed a bunch of typing issues (which caught one genuine bug). |
I think this is easier to read. The warning about the bool comparison is gone too. There's just the one about res being potentially unassigned. |
@@ -51,7 +51,7 @@ | |||
|
|||
// Dummy variables for stack effects. | |||
static PyObject *value, *value1, *value2, *left, *right, *res, *sum, *prod, *sub; | |||
static PyObject *container, *start, *stop, *v, *lhs, *rhs; | |||
static PyObject *container, *start, *stop, *v, *lhs, *rhs, *res2; |
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.
We will also need static int res2_exists
, and a bunch of other things, before I land this.
This hopefully avoids a silly compiler warning.
8f4219a
to
81e5236
Compare
Finally this has no compiler warnings on GitHub. I prefer this version, the changes to generate_cases.py are simpler than the version with flags. |
We can now write
which pops
foo
off the stack ifoparg & 1
, otherwise setting it toNULL
, and pushesbar
onto the stack ifoparg & 2
(otherwise ignoring it).This syntax cannot be combined with an array size on the same effect, but it can be combined with a type (untested). In addition, it is incompatible with an array size closer to the stack top, e.g.
foo if (oparg&1), bar[oparg>>1]
is not supported (I don't think it's used, and generating code for it would be slightly awkward, but could be done if needed).The implementation switches to using
POP()
andPUSH()
(fromPEEK()
andPOKE()
) for those variables that are either conditional or whose position relative to the bottom (!) of the stack depends on a condition. (See the added test for details.)To demonstrate this, I converted
LOAD_ATTR
(but not its specializations).