-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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
GH-106008: Make implicit boolean conversions explicit #106003
Conversation
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.
A few minor comments, otherwise LGTM.
Python/bytecodes.c
Outdated
} | ||
|
||
inst(TO_BOOL_BOOL, (unused/1, unused/2, value -- value)) { | ||
// Coolest (and dumbest-named) specialization ever: |
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.
True, but the not the most useful comment for someone trying to understand the code 🙂
Python/bytecodes.c
Outdated
|
||
inst(TO_BOOL_NONE, (unused/1, unused/2, value -- res)) { | ||
// This one is a bit weird, because we expect *some* failures... | ||
// it might be worth combining with TO_BOOL_ALWAYS_TRUE somehow: |
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 we decided it wasn't, as it reflects the underlying type instability when doing if x:
as a stand in for if x is None
.
Python/bytecodes.c
Outdated
inst(TO_BOOL_STR, (unused/1, unused/2, value -- res)) { | ||
DEOPT_IF(!PyUnicode_CheckExact(value), TO_BOOL); | ||
STAT_INC(TO_BOOL, hit); | ||
if (Py_Is(value, &_Py_STR(empty))) { |
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.
Use value == &_Py_STR(empty)
. The semantics is value == ""
, not value is ""
.
In general I wouldn't use Py_Is
except when you want the exact semantics of Python's x is y
.
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, I mean, we are checking for the identity of the singleton string here. I'll just change it, though.
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.
Hypothetically we could have more than one ""
object. I don't think that " ".strip() is ""
is part of the language spec, so Py_Is
doesn't add any safety, just obfuscation.
We can also potentially have tagged ints, in which case Py_Is
would need to become a lot more complex, but value == &_Py_STR(empty)
would remain efficient.
Python/bytecodes.c
Outdated
|
||
inst(TO_BOOL_ALWAYS_TRUE, (unused/1, version/2, value -- res)) { | ||
// This one is a bit weird, because we expect *some* failures... | ||
// it might be worth combining with TO_BOOL_NONE somehow: |
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.
See comment above.
} | ||
} | ||
assert(PyBool_Check(cond)); | ||
JUMPBY(oparg * Py_IsFalse(cond)); |
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 so much more pleasing 🙂
@@ -107,6 +107,8 @@ _Py_GetSpecializationStats(void) { | |||
err += add_stat_dict(stats, COMPARE_OP, "compare_op"); | |||
err += add_stat_dict(stats, UNPACK_SEQUENCE, "unpack_sequence"); | |||
err += add_stat_dict(stats, FOR_ITER, "for_iter"); | |||
err += add_stat_dict(stats, TO_BOOL, "to_bool"); | |||
err += add_stat_dict(stats, SEND, "send"); |
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.
Thanks, I tend to forget about the stats dict .
Note for possible future PR: We could, at the cost of two bits in Rather than check the version number, check the For abi4, we could add a per-object bit to handle immutable objects like ints and strings. |
Python/flowgraph.c
Outdated
} | ||
} | ||
Py_DECREF(newconst); | ||
return index; |
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.
windows compiler warning here, implicit cast from Py_ssize_t to int
Merging is currently blocked on #106250. |
Hey @brandtbucher, I have a question about this PR. In #106393 I had to change the code in
to
The reason is that the uop executor currently exits whenever it jumps, and your original code from this PR always jumps. Did you (or @markshannon) have a reason to prefer the If there's no deep reason I'll keep it the way I coded it up; but if there is (maybe it came out faster in a micro-benchmark?) then I suppose I could fix it another way in the uop interpreter (e.g. only exiting if the jump offset is nonzero). |
That the implementation of branches is itself branchless has a certain aesthetic appeal 🙂. TBH, that's the main reason. The multiplication form will be quicker for unpredictable branches, and slower for predictable ones in the tier 1 interpreter. |
...and specialize them!
This adds a new
TO_BOOL
bytecode that prefixes allUNARY_NOT
,POP_JUMP_IF_TRUE
, andPOP_JUMP_IF_FALSE
instructions, which now require an exact boolean. We also use a spare bit inCOMPARE_OP
's oparg to indicate whether the result should be converted to bool (this saves aTO_BOOL
for most branches, and is a no-op for allCOMPARE_OP
specializations)."0% faster". Stats show a 93.5% hit rate for the new instructions.
📚 Documentation preview 📚: https://cpython-previews--106003.org.readthedocs.build/