-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
gh-126835: Move constant unaryop & binop folding to CFG #129550
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
Changes from 1 commit
04b4a84
a6babab
0fa7c4c
27b1a09
d3df8c8
e82841a
cd6fbd7
300976e
3240d8e
74d2275
8358e28
c1a1be5
ef9221a
598adce
bb60d4c
eb99870
c93627e
f0f044a
a02ac66
2739fa0
258a5b6
59ee897
91ea2fa
2c5ee86
0399fce
3c53923
a6a06c8
2ea8425
7c9c69b
099afba
0dad7c1
1e8b552
f4e9a42
2dba23f
cafbc61
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -136,129 +136,6 @@ fold_unaryop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state) | |
return make_const(node, newval, arena); | ||
} | ||
|
||
/* Check whether a collection doesn't containing too much items (including | ||
subcollections). This protects from creating a constant that needs | ||
too much time for calculating a hash. | ||
"limit" is the maximal number of items. | ||
Returns the negative number if the total number of items exceeds the | ||
limit. Otherwise returns the limit minus the total number of items. | ||
*/ | ||
|
||
static Py_ssize_t | ||
check_complexity(PyObject *obj, Py_ssize_t limit) | ||
{ | ||
if (PyTuple_Check(obj)) { | ||
Py_ssize_t i; | ||
limit -= PyTuple_GET_SIZE(obj); | ||
for (i = 0; limit >= 0 && i < PyTuple_GET_SIZE(obj); i++) { | ||
limit = check_complexity(PyTuple_GET_ITEM(obj, i), limit); | ||
} | ||
return limit; | ||
} | ||
return limit; | ||
} | ||
|
||
#define MAX_INT_SIZE 128 /* bits */ | ||
#define MAX_COLLECTION_SIZE 256 /* items */ | ||
#define MAX_STR_SIZE 4096 /* characters */ | ||
#define MAX_TOTAL_ITEMS 1024 /* including nested collections */ | ||
|
||
static PyObject * | ||
safe_multiply(PyObject *v, PyObject *w) | ||
{ | ||
if (PyLong_Check(v) && PyLong_Check(w) && | ||
!_PyLong_IsZero((PyLongObject *)v) && !_PyLong_IsZero((PyLongObject *)w) | ||
) { | ||
int64_t vbits = _PyLong_NumBits(v); | ||
int64_t wbits = _PyLong_NumBits(w); | ||
assert(vbits >= 0); | ||
assert(wbits >= 0); | ||
if (vbits + wbits > MAX_INT_SIZE) { | ||
return NULL; | ||
} | ||
} | ||
else if (PyLong_Check(v) && PyTuple_Check(w)) { | ||
Py_ssize_t size = PyTuple_GET_SIZE(w); | ||
if (size) { | ||
long n = PyLong_AsLong(v); | ||
if (n < 0 || n > MAX_COLLECTION_SIZE / size) { | ||
return NULL; | ||
} | ||
if (n && check_complexity(w, MAX_TOTAL_ITEMS / n) < 0) { | ||
return NULL; | ||
} | ||
} | ||
} | ||
else if (PyLong_Check(v) && (PyUnicode_Check(w) || PyBytes_Check(w))) { | ||
Py_ssize_t size = PyUnicode_Check(w) ? PyUnicode_GET_LENGTH(w) : | ||
PyBytes_GET_SIZE(w); | ||
if (size) { | ||
long n = PyLong_AsLong(v); | ||
if (n < 0 || n > MAX_STR_SIZE / size) { | ||
return NULL; | ||
} | ||
} | ||
} | ||
else if (PyLong_Check(w) && | ||
(PyTuple_Check(v) || PyUnicode_Check(v) || PyBytes_Check(v))) | ||
{ | ||
return safe_multiply(w, v); | ||
} | ||
|
||
return PyNumber_Multiply(v, w); | ||
} | ||
|
||
static PyObject * | ||
safe_power(PyObject *v, PyObject *w) | ||
{ | ||
if (PyLong_Check(v) && PyLong_Check(w) && | ||
!_PyLong_IsZero((PyLongObject *)v) && _PyLong_IsPositive((PyLongObject *)w) | ||
) { | ||
int64_t vbits = _PyLong_NumBits(v); | ||
size_t wbits = PyLong_AsSize_t(w); | ||
assert(vbits >= 0); | ||
if (wbits == (size_t)-1) { | ||
return NULL; | ||
} | ||
if ((uint64_t)vbits > MAX_INT_SIZE / wbits) { | ||
return NULL; | ||
} | ||
} | ||
|
||
return PyNumber_Power(v, w, Py_None); | ||
} | ||
|
||
static PyObject * | ||
safe_lshift(PyObject *v, PyObject *w) | ||
{ | ||
if (PyLong_Check(v) && PyLong_Check(w) && | ||
!_PyLong_IsZero((PyLongObject *)v) && !_PyLong_IsZero((PyLongObject *)w) | ||
) { | ||
int64_t vbits = _PyLong_NumBits(v); | ||
size_t wbits = PyLong_AsSize_t(w); | ||
assert(vbits >= 0); | ||
if (wbits == (size_t)-1) { | ||
return NULL; | ||
} | ||
if (wbits > MAX_INT_SIZE || (uint64_t)vbits > MAX_INT_SIZE - wbits) { | ||
return NULL; | ||
} | ||
} | ||
|
||
return PyNumber_Lshift(v, w); | ||
} | ||
|
||
static PyObject * | ||
safe_mod(PyObject *v, PyObject *w) | ||
{ | ||
if (PyUnicode_Check(v) || PyBytes_Check(v)) { | ||
return NULL; | ||
} | ||
|
||
return PyNumber_Remainder(v, w); | ||
} | ||
|
||
|
||
static expr_ty | ||
parse_literal(PyObject *fmt, Py_ssize_t *ppos, PyArena *arena) | ||
{ | ||
|
@@ -478,58 +355,7 @@ fold_binop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state) | |
return optimize_format(node, lv, rhs->v.Tuple.elts, arena); | ||
} | ||
|
||
if (rhs->kind != Constant_kind) { | ||
return 1; | ||
} | ||
|
||
PyObject *rv = rhs->v.Constant.value; | ||
PyObject *newval = NULL; | ||
|
||
switch (node->v.BinOp.op) { | ||
case Add: | ||
newval = PyNumber_Add(lv, rv); | ||
break; | ||
case Sub: | ||
newval = PyNumber_Subtract(lv, rv); | ||
break; | ||
case Mult: | ||
newval = safe_multiply(lv, rv); | ||
break; | ||
case Div: | ||
newval = PyNumber_TrueDivide(lv, rv); | ||
break; | ||
case FloorDiv: | ||
newval = PyNumber_FloorDivide(lv, rv); | ||
break; | ||
case Mod: | ||
newval = safe_mod(lv, rv); | ||
break; | ||
case Pow: | ||
newval = safe_power(lv, rv); | ||
break; | ||
case LShift: | ||
newval = safe_lshift(lv, rv); | ||
break; | ||
case RShift: | ||
newval = PyNumber_Rshift(lv, rv); | ||
break; | ||
case BitOr: | ||
newval = PyNumber_Or(lv, rv); | ||
break; | ||
case BitXor: | ||
newval = PyNumber_Xor(lv, rv); | ||
break; | ||
case BitAnd: | ||
newval = PyNumber_And(lv, rv); | ||
break; | ||
// No builtin constants implement the following operators | ||
case MatMult: | ||
return 1; | ||
// No default case, so the compiler will emit a warning if new binary | ||
// operators are added without being handled here | ||
} | ||
|
||
return make_const(node, newval, arena); | ||
return 1; | ||
} | ||
|
||
static PyObject* | ||
|
@@ -971,6 +797,115 @@ astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state) | |
return 1; | ||
} | ||
|
||
#define IS_CONST_EXPR(N) \ | ||
((N)->kind == Constant_kind) | ||
|
||
#define CONST_EXPR_VALUE(N) \ | ||
((N)->v.Constant.value) | ||
|
||
#define IS_COMPLEX_CONST_EXPR(N) \ | ||
(IS_CONST_EXPR(N) && PyComplex_CheckExact(CONST_EXPR_VALUE(N))) | ||
|
||
#define IS_NUMERIC_CONST_EXPR(N) \ | ||
(IS_CONST_EXPR(N) && (PyLong_CheckExact(CONST_EXPR_VALUE(N)) || PyFloat_CheckExact(CONST_EXPR_VALUE(N)))) | ||
|
||
#define IS_UNARY_EXPR(N) \ | ||
((N)->kind == UnaryOp_kind) | ||
|
||
#define UNARY_EXPR_OP(N) \ | ||
((N)->v.UnaryOp.op) | ||
|
||
#define UNARY_EXPR_OPERAND(N) \ | ||
((N)->v.UnaryOp.operand) | ||
|
||
#define UNARY_EXPR_OPERAND_CONST_VALUE(N) \ | ||
(CONST_EXPR_VALUE(UNARY_EXPR_OPERAND(N))) | ||
|
||
#define IS_UNARY_SUB_EXPR(N) \ | ||
(IS_UNARY_EXPR(N) && UNARY_EXPR_OP(N) == USub) | ||
|
||
#define IS_NUMERIC_UNARY_CONST_EXPR(N) \ | ||
(IS_UNARY_SUB_EXPR(N) && IS_NUMERIC_CONST_EXPR(UNARY_EXPR_OPERAND(N))) | ||
|
||
#define IS_COMPLEX_UNARY_CONST_EXPR(N) \ | ||
(IS_UNARY_SUB_EXPR(N) && IS_COMPLEX_CONST_EXPR(UNARY_EXPR_OPERAND(N))) | ||
|
||
#define BINARY_EXPR(N) \ | ||
((N)->v.BinOp) | ||
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. I'm not sure macros like this are helpful - the name of the macro doesn't reflect what it is (it's too cryptic) so you need to look at the definition to understand the code. 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. Would it read better if we were to access nodes attributes directly everywhere? 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. In this case I think it would. 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. It would be quite tedious to write assertions that are used for example in 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. That's how the rest of this file is written. There is a lot of 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. Okay, let me try to write it in that way and let's see if it looks better that way. 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. Cool. Note that my point is not that it would necessarily look better, but that it would be consistent with the style of the rest of the code. 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. I see. No problem. |
||
|
||
#define BINARY_EXPR_OP(N) \ | ||
(BINARY_EXPR(N).op) | ||
|
||
#define BINARY_EXPR_LEFT(N) \ | ||
(BINARY_EXPR(N).left) | ||
|
||
#define BINARY_EXPR_RIGHT(N) \ | ||
(BINARY_EXPR(N).right) | ||
|
||
#define IS_BINARY_EXPR(N) \ | ||
((N)->kind == BinOp_kind) | ||
|
||
#define IS_BINARY_ADD_EXPR(N) \ | ||
(IS_BINARY_EXPR(N) && BINARY_EXPR_OP(N) == Add) | ||
|
||
#define IS_BINARY_SUB_EXPR(N) \ | ||
(IS_BINARY_EXPR(N) && BINARY_EXPR_OP(N) == Sub) | ||
|
||
#define IS_MATCH_NUMERIC_OR_COMPLEX_UNARY_CONST_EXPR(N) \ | ||
(IS_NUMERIC_UNARY_CONST_EXPR(N) || IS_COMPLEX_UNARY_CONST_EXPR(N)) | ||
|
||
#define IS_MATCH_COMPLEX_BINARY_CONST_EXPR(N) \ | ||
( \ | ||
(IS_BINARY_ADD_EXPR(N) || IS_BINARY_SUB_EXPR(N)) \ | ||
&& (IS_NUMERIC_UNARY_CONST_EXPR(BINARY_EXPR_LEFT(N)) || IS_CONST_EXPR(BINARY_EXPR_LEFT(N))) \ | ||
&& IS_COMPLEX_CONST_EXPR(BINARY_EXPR_RIGHT(N)) \ | ||
) | ||
|
||
|
||
static int | ||
fold_const_unary_or_complex_expr(expr_ty e, PyArena *arena) | ||
{ | ||
assert(IS_MATCH_NUMERIC_OR_COMPLEX_UNARY_CONST_EXPR(e)); | ||
PyObject *constant = UNARY_EXPR_OPERAND_CONST_VALUE(e); | ||
assert(UNARY_EXPR_OP(e) == USub); | ||
PyObject* folded = PyNumber_Negative(constant); | ||
return make_const(e, folded, arena); | ||
} | ||
|
||
static int | ||
fold_const_binary_complex_expr(expr_ty e, PyArena *arena) | ||
{ | ||
assert(IS_MATCH_COMPLEX_BINARY_CONST_EXPR(e)); | ||
expr_ty left_expr = BINARY_EXPR_LEFT(e); | ||
if (IS_NUMERIC_UNARY_CONST_EXPR(left_expr)) { | ||
if (!fold_const_unary_or_complex_expr(left_expr, arena)) { | ||
return 0; | ||
} | ||
} | ||
assert(IS_CONST_EXPR(BINARY_EXPR_LEFT(e))); | ||
operator_ty op = BINARY_EXPR_OP(e); | ||
PyObject *left = CONST_EXPR_VALUE(BINARY_EXPR_LEFT(e)); | ||
PyObject *right = CONST_EXPR_VALUE(BINARY_EXPR_RIGHT(e)); | ||
assert(op == Add || op == Sub); | ||
PyObject *folded = op == Add ? PyNumber_Add(left, right) : PyNumber_Subtract(left, right); | ||
return make_const(e, folded, arena); | ||
} | ||
|
||
static int | ||
fold_pattern_match_value(expr_ty node, PyArena *arena, _PyASTOptimizeState *Py_UNUSED(state)) | ||
{ | ||
switch (node->kind) | ||
{ | ||
case UnaryOp_kind: | ||
return fold_const_unary_or_complex_expr(node, arena); | ||
case BinOp_kind: | ||
return fold_const_binary_complex_expr(node, arena); | ||
default: | ||
break; | ||
} | ||
return 1; | ||
} | ||
|
||
static int | ||
astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTOptimizeState *state) | ||
{ | ||
|
@@ -980,15 +915,15 @@ astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTOptimizeState *state) | |
ENTER_RECURSIVE(state); | ||
switch (node_->kind) { | ||
case MatchValue_kind: | ||
CALL(astfold_expr, expr_ty, node_->v.MatchValue.value); | ||
CALL(fold_pattern_match_value, expr_ty, node_->v.MatchValue.value); | ||
break; | ||
case MatchSingleton_kind: | ||
break; | ||
case MatchSequence_kind: | ||
CALL_SEQ(astfold_pattern, pattern, node_->v.MatchSequence.patterns); | ||
break; | ||
case MatchMapping_kind: | ||
CALL_SEQ(astfold_expr, expr, node_->v.MatchMapping.keys); | ||
CALL_SEQ(fold_pattern_match_value, expr, node_->v.MatchMapping.keys); | ||
CALL_SEQ(astfold_pattern, pattern, node_->v.MatchMapping.patterns); | ||
break; | ||
case MatchClass_kind: | ||
|
Uh oh!
There was an error while loading. Please reload this page.