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
updates for handling Python3.7 #29
Conversation
Note that tests do not pass yet.
BREAKING CHANGE. This turns Instr.stack_effect() into a method. It used to be a property. This commit has no functional changes other than removing the @Property and fixing all the uses. The commit is just prep work for the next commit that will be functional.
Instr.stack_effect now takes an optional parameter "jump", much like compile.c:stack_effect(). This allows the caller to request information about taken jumps vs non-taken jumps. Note that the compile.c behavior is not exposed: this is done entirely within instr.py. New _compute_stack_size() logic now leans on Instr.stack_effect instead of having lots of tests for different structions. This commit was intended to only structural changes that would pass tests on 3.6 and get things in place for 3.7 work in a later commit. But tests pass on 3.7 as well :-)
Codecov Report
@@ Coverage Diff @@
## master #29 +/- ##
==========================================
+ Coverage 93.48% 94.04% +0.55%
==========================================
Files 17 17
Lines 2564 2819 +255
==========================================
+ Hits 2397 2651 +254
- Misses 167 168 +1
Continue to review full report at Codecov.
|
bytecode/cfg.py
Outdated
if instr.has_jump(): | ||
# first compute the taken-jump path | ||
before_size = size | ||
update_size(instr.stack_effect(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.
It would be better to use boolean value True
. Or even with keyword argument: jump=True
.
bytecode/instr.py
Outdated
} | ||
|
||
# More stack effect values that are unique to the version of Python. | ||
if not (sys.version_info >= (3, 7)): |
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.
Why not just sys.version_info < (3, 7)
?
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 originally wrote it as "sys.version_info <= (3,6)", which was the problem. Although I would prefer "<= (3,6)", it is certainly cleaner to say "< (3,7)" than "not ( >= (3,7))"
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.version_info
sys.version_info(major=3, minor=7, micro=0, releaselevel='beta', serial=3)
>>> sys.version_info < (3,8)
True
>>> sys.version_info < (3,7)
False
>>> sys.version_info <= (3,7)
False
Ahhh, I see. That was a noob mistake. Apparently "<= (3,7)" is short for "<= (3,7,0,0,0)" (or something like that) and I'm running (3,7,0,'beta',3) which is greater. So I've just convinced myself why my preference for "<= (3,6)" is just wrong. Thanks for making me think that through.
doc/api.rst
Outdated
@@ -158,6 +153,15 @@ Instr | |||
.. versionchanged:: 0.3 | |||
The *lineno* parameter has been removed. | |||
|
|||
.. method:: stack_effect(jump:int = -1) -> int |
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.
Needed versionchanged
.
There are plans of exposing the |
This isn't needed since EXTENDED_ARG has an entry in _stack_effects.
Review feedback from Serhiy Storchaka. Change stack_effect's 'jump' argument to True/False/None instead of 1/0/-1.
Review feedback from Serhiy Storchaka. Note to self: "<= (3, 6)" didn't work because running version 3.6.4 is greater than that.
bytecode/cfg.py
Outdated
|
||
else: # no jump | ||
update_size(instr.stack_effect()) | ||
update_size(instr.stack_effect(jump=None)) |
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 it is better to call without arguments for the default behavior.
doc/api.rst
Outdated
@@ -153,7 +153,7 @@ Instr | |||
.. versionchanged:: 0.3 | |||
The *lineno* parameter has been removed. | |||
|
|||
.. method:: stack_effect(jump:int = -1) -> int | |||
.. method:: stack_effect(jump: bool = None) -> int |
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.
None
as the default conflicts with the description below.
bytecode/instr.py
Outdated
@@ -146,12 +146,10 @@ def _check_arg_int(name, arg): | |||
|
|||
|
|||
_stack_effects = { |
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.
You can simplify the definition of _stack_effects
if add a simple code after it.
_stack_effects = {
'EXTENDED_ARG': (0, 0),
'JUMP_IF_TRUE_OR_POP': (-1, 0),
...
}
...
_stack_effects = {_opcode.opmap[name]: (a, b, max(a, b))
for name, (a, b) in _stack_effects.items()}
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've done something different that what you suggested. Since converting to True/False/None already trashed the jump-free indexing of the 3-tuple, I just converted the whole thing to a 2-tuple and using max() at runtime.
-- don't use explicit arg value when default was sufficient -- _stack_effect values are now 2-tuples -- doc fix
bytecode/cfg.py
Outdated
if instr.has_jump(): | ||
# first compute the taken-jump path | ||
before_size = size | ||
update_size(instr.stack_effect(jump=True)) | ||
target_size = size |
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.
Since we do not need anymore to update target_size to handle some special cases, I think we can completely remove it.
If we get rid off nonlocal
, we could remove before_size
and do
target_size, maxsize = update_size(instr.stack_effect(jump=True))```
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.
indeed. i've made some other simplifications too
bytecode/cfg.py
Outdated
@@ -53,6 +53,15 @@ def _compute_stack_size(block, size, maxsize): | |||
if block.seen or block.startsize >= size: | |||
return maxsize | |||
|
|||
def update_size(delta): | |||
nonlocal size |
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 must say I am not a big fan on nonlocal. It seems to me it makes the code harder to follow. Couldn't we simply return the new size and maxsize ?
bytecode/instr.py
Outdated
@@ -144,6 +145,33 @@ def _check_arg_int(name, arg): | |||
% name) | |||
|
|||
|
|||
_stack_effects = { | |||
# NOTE: the entries are all 3-tuples. Entry[0/False] is non-taken jumps. |
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 is not true anymore.
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.
Yikes! Fixed
@@ -269,15 +297,19 @@ def lineno(self): | |||
def lineno(self, lineno): | |||
self._set(self._name, self._arg, lineno) | |||
|
|||
@property | |||
def stack_effect(self): | |||
def stack_effect(self, jump=None): |
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 do agree this is necessary, but I do not like to break backward compatibility and I tend to prefer verbs for method. But I cannot find a nice one (eval_stack_effect
could work). WDYT ? If we change the method name we could have a deprecation warning (FutureWarning for visibility).
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.
Well you asked what I think so... Shortly after Stuart Feldman invented "make" somebody pointed out that the tab syntax was horrible. He said that he agreed, but he didn't want to change because 12 people were already using it.
More seriously: if we're fans of properties then we could add stack_effect_with_jump and stack_effect_without_jump. But I dislike that: the work done in stack_effect is beyond what I think a property ought to do.
We could have a new method with a different name, as you suggested. And also deprecate stack_effect. And then once stack_effect is gone we put it back as a method.
I'm not a fan of "eval_stack_effect" in particular, the name sounds like I might pass it evaluates a stack effect object ("eval" is a verb, "stackeffect" the verb's object). I'd need to go read the docs to understand the difference between the two.
Let me think on this for a few hours, maybe I'll come up with something more constructive.
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.
BTW, +1 for your comment "I tend to prefer verbs for method".
How about "get_stack_method()" ? I like "get" better than "eval", but the irony is that "get" screams "make this a property" :-).
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 breaking backward compatibility is not a problem while the version < 1.0.
Based on review feedback from Matthieu Dartiailh. _compute_stack_size simplied to not use nonlocals, and non-jump instructions share code with non-taken path of jumps. Fix doc string on _stack_effects.
I just pushed changes for your comments except about stack_size back-compat. If you think back compat is more important then I've got that change ready to go as well. However, I think have two ways to get stack_effect, one of which promotes ignorance, is not ideal. And as Serhiy mentions, this isn't 1.0. |
Since we do not have a good alternative name for the method, and since this part of the API is not meant to be used very often so the backward incompatibility should be a minor annoyance, I will merge as is. Thanks @SnoopyLane for your work and @serhiy-storchaka for your reviews. |
These changes make the tests pass with Python 3.7.0b3. I made a few enhancements to tests, but the changes were mostly functional. In particular, related to changes in dis.stack_effect and compile.c.
In particular, compile.c seems to do a better job in 3.7 of computing stacksize, by leveraging new knowledge of separate stack_effect for taken vs untaken branches.
I have modeled this in bytecode by having Instr.stack_effect() take a "jump" argument as in compile.c. Note that this is supported for all versions, not just 3.7. Note also that this is a breaking change because stack_effect used to be a property.
I also rewrote most of the logic in _compute_stack_effect(). The previous code tested or several instructions, embedding taken/not-taken knowledge in this function. Since that knowledge is now exposed in Instr.stack_effect the logic here can be simpler.
I haven't yet tried running any of my own code which uses bytecode, as I'm only just starting to test under 3.7.