mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	gh-126835: Move constant unaryop & binop folding to CFG (#129550)
This commit is contained in:
		
							parent
							
								
									d88677ac20
								
							
						
					
					
						commit
						38642bff13
					
				
					 6 changed files with 1057 additions and 443 deletions
				
			
		
							
								
								
									
										289
									
								
								Python/ast_opt.c
									
										
									
									
									
								
							
							
						
						
									
										289
									
								
								Python/ast_opt.c
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -56,199 +56,6 @@ has_starred(asdl_expr_seq *elts)
 | 
			
		|||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
static PyObject*
 | 
			
		||||
unary_not(PyObject *v)
 | 
			
		||||
{
 | 
			
		||||
    int r = PyObject_IsTrue(v);
 | 
			
		||||
    if (r < 0)
 | 
			
		||||
        return NULL;
 | 
			
		||||
    return PyBool_FromLong(!r);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
fold_unaryop(expr_ty node, PyArena *arena, _PyASTOptimizeState *state)
 | 
			
		||||
{
 | 
			
		||||
    expr_ty arg = node->v.UnaryOp.operand;
 | 
			
		||||
 | 
			
		||||
    if (arg->kind != Constant_kind) {
 | 
			
		||||
        /* Fold not into comparison */
 | 
			
		||||
        if (node->v.UnaryOp.op == Not && arg->kind == Compare_kind &&
 | 
			
		||||
                asdl_seq_LEN(arg->v.Compare.ops) == 1) {
 | 
			
		||||
            /* Eq and NotEq are often implemented in terms of one another, so
 | 
			
		||||
               folding not (self == other) into self != other breaks implementation
 | 
			
		||||
               of !=. Detecting such cases doesn't seem worthwhile.
 | 
			
		||||
               Python uses </> for 'is subset'/'is superset' operations on sets.
 | 
			
		||||
               They don't satisfy not folding laws. */
 | 
			
		||||
            cmpop_ty op = asdl_seq_GET(arg->v.Compare.ops, 0);
 | 
			
		||||
            switch (op) {
 | 
			
		||||
            case Is:
 | 
			
		||||
                op = IsNot;
 | 
			
		||||
                break;
 | 
			
		||||
            case IsNot:
 | 
			
		||||
                op = Is;
 | 
			
		||||
                break;
 | 
			
		||||
            case In:
 | 
			
		||||
                op = NotIn;
 | 
			
		||||
                break;
 | 
			
		||||
            case NotIn:
 | 
			
		||||
                op = In;
 | 
			
		||||
                break;
 | 
			
		||||
            // The remaining comparison operators can't be safely inverted
 | 
			
		||||
            case Eq:
 | 
			
		||||
            case NotEq:
 | 
			
		||||
            case Lt:
 | 
			
		||||
            case LtE:
 | 
			
		||||
            case Gt:
 | 
			
		||||
            case GtE:
 | 
			
		||||
                op = 0; // The AST enums leave "0" free as an "unused" marker
 | 
			
		||||
                break;
 | 
			
		||||
            // No default case, so the compiler will emit a warning if new
 | 
			
		||||
            // comparison operators are added without being handled here
 | 
			
		||||
            }
 | 
			
		||||
            if (op) {
 | 
			
		||||
                asdl_seq_SET(arg->v.Compare.ops, 0, op);
 | 
			
		||||
                COPY_NODE(node, arg);
 | 
			
		||||
                return 1;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    typedef PyObject *(*unary_op)(PyObject*);
 | 
			
		||||
    static const unary_op ops[] = {
 | 
			
		||||
        [Invert] = PyNumber_Invert,
 | 
			
		||||
        [Not] = unary_not,
 | 
			
		||||
        [UAdd] = PyNumber_Positive,
 | 
			
		||||
        [USub] = PyNumber_Negative,
 | 
			
		||||
    };
 | 
			
		||||
    PyObject *newval = ops[node->v.UnaryOp.op](arg->v.Constant.value);
 | 
			
		||||
    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)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -468,58 +275,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*
 | 
			
		||||
| 
						 | 
				
			
			@ -670,7 +426,6 @@ astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
 | 
			
		|||
        break;
 | 
			
		||||
    case UnaryOp_kind:
 | 
			
		||||
        CALL(astfold_expr, expr_ty, node_->v.UnaryOp.operand);
 | 
			
		||||
        CALL(fold_unaryop, expr_ty, node_);
 | 
			
		||||
        break;
 | 
			
		||||
    case Lambda_kind:
 | 
			
		||||
        CALL(astfold_arguments, arguments_ty, node_->v.Lambda.args);
 | 
			
		||||
| 
						 | 
				
			
			@ -961,6 +716,44 @@ astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
 | 
			
		|||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
fold_const_match_patterns(expr_ty node, PyArena *ctx_, _PyASTOptimizeState *state)
 | 
			
		||||
{
 | 
			
		||||
    switch (node->kind)
 | 
			
		||||
    {
 | 
			
		||||
        case UnaryOp_kind:
 | 
			
		||||
        {
 | 
			
		||||
            if (node->v.UnaryOp.op == USub &&
 | 
			
		||||
                node->v.UnaryOp.operand->kind == Constant_kind)
 | 
			
		||||
            {
 | 
			
		||||
                PyObject *operand = node->v.UnaryOp.operand->v.Constant.value;
 | 
			
		||||
                PyObject *folded = PyNumber_Negative(operand);
 | 
			
		||||
                return make_const(node, folded, ctx_);
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case BinOp_kind:
 | 
			
		||||
        {
 | 
			
		||||
            operator_ty op = node->v.BinOp.op;
 | 
			
		||||
            if ((op == Add || op == Sub) &&
 | 
			
		||||
                node->v.BinOp.right->kind == Constant_kind)
 | 
			
		||||
            {
 | 
			
		||||
                CALL(fold_const_match_patterns, expr_ty, node->v.BinOp.left);
 | 
			
		||||
                if (node->v.BinOp.left->kind == Constant_kind) {
 | 
			
		||||
                    PyObject *left = node->v.BinOp.left->v.Constant.value;
 | 
			
		||||
                    PyObject *right = node->v.BinOp.right->v.Constant.value;
 | 
			
		||||
                    PyObject *folded = op == Add ? PyNumber_Add(left, right) : PyNumber_Subtract(left, right);
 | 
			
		||||
                    return make_const(node, folded, ctx_);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        default:
 | 
			
		||||
            break;
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -970,7 +763,7 @@ astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
 | 
			
		|||
    ENTER_RECURSIVE();
 | 
			
		||||
    switch (node_->kind) {
 | 
			
		||||
        case MatchValue_kind:
 | 
			
		||||
            CALL(astfold_expr, expr_ty, node_->v.MatchValue.value);
 | 
			
		||||
            CALL(fold_const_match_patterns, expr_ty, node_->v.MatchValue.value);
 | 
			
		||||
            break;
 | 
			
		||||
        case MatchSingleton_kind:
 | 
			
		||||
            break;
 | 
			
		||||
| 
						 | 
				
			
			@ -978,7 +771,7 @@ astfold_pattern(pattern_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
 | 
			
		|||
            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_const_match_patterns, expr, node_->v.MatchMapping.keys);
 | 
			
		||||
            CALL_SEQ(astfold_pattern, pattern, node_->v.MatchMapping.patterns);
 | 
			
		||||
            break;
 | 
			
		||||
        case MatchClass_kind:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1406,6 +1406,26 @@ nop_out(basicblock *bb, int start, int count)
 | 
			
		|||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Steals reference to "newconst" */
 | 
			
		||||
static int
 | 
			
		||||
instr_make_load_const(cfg_instr *instr, PyObject *newconst,
 | 
			
		||||
                      PyObject *consts, PyObject *const_cache)
 | 
			
		||||
{
 | 
			
		||||
    if (PyLong_CheckExact(newconst)) {
 | 
			
		||||
        int overflow;
 | 
			
		||||
        long val = PyLong_AsLongAndOverflow(newconst, &overflow);
 | 
			
		||||
        if (!overflow && _PY_IS_SMALL_INT(val)) {
 | 
			
		||||
            assert(_Py_IsImmortal(newconst));
 | 
			
		||||
            INSTR_SET_OP1(instr, LOAD_SMALL_INT, (int)val);
 | 
			
		||||
            return SUCCESS;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    int oparg = add_const(newconst, consts, const_cache);
 | 
			
		||||
    RETURN_IF_ERROR(oparg);
 | 
			
		||||
    INSTR_SET_OP1(instr, LOAD_CONST, oparg);
 | 
			
		||||
    return SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Replace LOAD_CONST c1, LOAD_CONST c2 ... LOAD_CONST cn, BUILD_TUPLE n
 | 
			
		||||
   with    LOAD_CONST (c1, c2, ... cn).
 | 
			
		||||
   The consts table must still be in list form so that the
 | 
			
		||||
| 
						 | 
				
			
			@ -1413,25 +1433,23 @@ nop_out(basicblock *bb, int start, int count)
 | 
			
		|||
   Called with codestr pointing to the first LOAD_CONST.
 | 
			
		||||
*/
 | 
			
		||||
static int
 | 
			
		||||
fold_tuple_of_constants(basicblock *bb, int n, PyObject *consts, PyObject *const_cache)
 | 
			
		||||
fold_tuple_of_constants(basicblock *bb, int i, PyObject *consts, PyObject *const_cache)
 | 
			
		||||
{
 | 
			
		||||
    /* Pre-conditions */
 | 
			
		||||
    assert(PyDict_CheckExact(const_cache));
 | 
			
		||||
    assert(PyList_CheckExact(consts));
 | 
			
		||||
    cfg_instr *instr = &bb->b_instr[n];
 | 
			
		||||
    cfg_instr *instr = &bb->b_instr[i];
 | 
			
		||||
    assert(instr->i_opcode == BUILD_TUPLE);
 | 
			
		||||
    int seq_size = instr->i_oparg;
 | 
			
		||||
    PyObject *newconst;
 | 
			
		||||
    RETURN_IF_ERROR(get_constant_sequence(bb, n-1, seq_size, consts, &newconst));
 | 
			
		||||
    RETURN_IF_ERROR(get_constant_sequence(bb, i-1, seq_size, consts, &newconst));
 | 
			
		||||
    if (newconst == NULL) {
 | 
			
		||||
        /* not a const sequence */
 | 
			
		||||
        return SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
    assert(PyTuple_CheckExact(newconst) && PyTuple_GET_SIZE(newconst) == seq_size);
 | 
			
		||||
    int index = add_const(newconst, consts, const_cache);
 | 
			
		||||
    RETURN_IF_ERROR(index);
 | 
			
		||||
    nop_out(bb, n-1, seq_size);
 | 
			
		||||
    INSTR_SET_OP1(instr, LOAD_CONST, index);
 | 
			
		||||
    assert(PyTuple_Size(newconst) == seq_size);
 | 
			
		||||
    RETURN_IF_ERROR(instr_make_load_const(instr, newconst, consts, const_cache));
 | 
			
		||||
    nop_out(bb, i-1, seq_size);
 | 
			
		||||
    return SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1469,7 +1487,7 @@ optimize_lists_and_sets(basicblock *bb, int i, int nextop,
 | 
			
		|||
        }
 | 
			
		||||
        return SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
    assert(PyTuple_CheckExact(newconst) && PyTuple_GET_SIZE(newconst) == seq_size);
 | 
			
		||||
    assert(PyTuple_Size(newconst) == seq_size);
 | 
			
		||||
    if (instr->i_opcode == BUILD_SET) {
 | 
			
		||||
        PyObject *frozenset = PyFrozenSet_New(newconst);
 | 
			
		||||
        if (frozenset == NULL) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1497,45 +1515,200 @@ optimize_lists_and_sets(basicblock *bb, int i, int nextop,
 | 
			
		|||
    return SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* Determine opcode & oparg for freshly folded constant. */
 | 
			
		||||
static int
 | 
			
		||||
newop_from_folded(PyObject *newconst, PyObject *consts,
 | 
			
		||||
                  PyObject *const_cache, int *newopcode, int *newoparg)
 | 
			
		||||
/* Check whether the total number of items in the (possibly nested) collection obj exceeds
 | 
			
		||||
 * limit. Return a negative number if it does, and a non-negative number otherwise.
 | 
			
		||||
 * Used to avoid creating constants which are slow to hash.
 | 
			
		||||
 */
 | 
			
		||||
static Py_ssize_t
 | 
			
		||||
const_folding_check_complexity(PyObject *obj, Py_ssize_t limit)
 | 
			
		||||
{
 | 
			
		||||
    if (PyLong_CheckExact(newconst)) {
 | 
			
		||||
        int overflow;
 | 
			
		||||
        long val = PyLong_AsLongAndOverflow(newconst, &overflow);
 | 
			
		||||
        if (!overflow && _PY_IS_SMALL_INT(val)) {
 | 
			
		||||
            *newopcode = LOAD_SMALL_INT;
 | 
			
		||||
            *newoparg = val;
 | 
			
		||||
            return SUCCESS;
 | 
			
		||||
    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 = const_folding_check_complexity(PyTuple_GET_ITEM(obj, i), limit);
 | 
			
		||||
            if (limit < 0) {
 | 
			
		||||
                return limit;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    *newopcode = LOAD_CONST;
 | 
			
		||||
    *newoparg = add_const(newconst, consts, const_cache);
 | 
			
		||||
    RETURN_IF_ERROR(*newoparg);
 | 
			
		||||
    return SUCCESS;
 | 
			
		||||
    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 *
 | 
			
		||||
const_folding_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 && const_folding_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 const_folding_safe_multiply(w, v);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return PyNumber_Multiply(v, w);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static PyObject *
 | 
			
		||||
const_folding_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 *
 | 
			
		||||
const_folding_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 *
 | 
			
		||||
const_folding_safe_mod(PyObject *v, PyObject *w)
 | 
			
		||||
{
 | 
			
		||||
    if (PyUnicode_Check(v) || PyBytes_Check(v)) {
 | 
			
		||||
        return NULL;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return PyNumber_Remainder(v, w);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static PyObject *
 | 
			
		||||
eval_const_binop(PyObject *left, int op, PyObject *right)
 | 
			
		||||
{
 | 
			
		||||
    assert(left != NULL && right != NULL);
 | 
			
		||||
    assert(op >= 0 && op <= NB_OPARG_LAST);
 | 
			
		||||
 | 
			
		||||
    PyObject *result = NULL;
 | 
			
		||||
    switch (op) {
 | 
			
		||||
        case NB_ADD:
 | 
			
		||||
            result = PyNumber_Add(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_SUBTRACT:
 | 
			
		||||
            result = PyNumber_Subtract(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_MULTIPLY:
 | 
			
		||||
            result = const_folding_safe_multiply(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_TRUE_DIVIDE:
 | 
			
		||||
            result = PyNumber_TrueDivide(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_FLOOR_DIVIDE:
 | 
			
		||||
            result = PyNumber_FloorDivide(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_REMAINDER:
 | 
			
		||||
            result = const_folding_safe_mod(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_POWER:
 | 
			
		||||
            result = const_folding_safe_power(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_LSHIFT:
 | 
			
		||||
            result = const_folding_safe_lshift(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_RSHIFT:
 | 
			
		||||
            result = PyNumber_Rshift(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_OR:
 | 
			
		||||
            result = PyNumber_Or(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_XOR:
 | 
			
		||||
            result = PyNumber_Xor(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_AND:
 | 
			
		||||
            result = PyNumber_And(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_SUBSCR:
 | 
			
		||||
            result = PyObject_GetItem(left, right);
 | 
			
		||||
            break;
 | 
			
		||||
        case NB_MATRIX_MULTIPLY:
 | 
			
		||||
            // No builtin constants implement matrix multiplication
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            Py_UNREACHABLE();
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
optimize_if_const_binop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache)
 | 
			
		||||
fold_const_binop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache)
 | 
			
		||||
{
 | 
			
		||||
    #define BINOP_OPERAND_COUNT 2
 | 
			
		||||
    assert(PyDict_CheckExact(const_cache));
 | 
			
		||||
    assert(PyList_CheckExact(consts));
 | 
			
		||||
    cfg_instr *binop = &bb->b_instr[i];
 | 
			
		||||
    assert(binop->i_opcode == BINARY_OP);
 | 
			
		||||
    if (binop->i_oparg != NB_SUBSCR) {
 | 
			
		||||
        /* TODO: support other binary ops */
 | 
			
		||||
        return SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
    PyObject *pair;
 | 
			
		||||
    RETURN_IF_ERROR(get_constant_sequence(bb, i-1, 2, consts, &pair));
 | 
			
		||||
    RETURN_IF_ERROR(get_constant_sequence(bb, i-1, BINOP_OPERAND_COUNT, consts, &pair));
 | 
			
		||||
    if (pair == NULL) {
 | 
			
		||||
        /* not a const sequence */
 | 
			
		||||
        return SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
    assert(PyTuple_CheckExact(pair) && PyTuple_Size(pair) == 2);
 | 
			
		||||
    assert(PyTuple_Size(pair) == BINOP_OPERAND_COUNT);
 | 
			
		||||
    PyObject *left = PyTuple_GET_ITEM(pair, 0);
 | 
			
		||||
    PyObject *right = PyTuple_GET_ITEM(pair, 1);
 | 
			
		||||
    assert(left != NULL && right != NULL);
 | 
			
		||||
    PyObject *newconst = PyObject_GetItem(left, right);
 | 
			
		||||
    PyObject *newconst = eval_const_binop(left, binop->i_oparg, right);
 | 
			
		||||
    Py_DECREF(pair);
 | 
			
		||||
    if (newconst == NULL) {
 | 
			
		||||
        if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
 | 
			
		||||
| 
						 | 
				
			
			@ -1544,10 +1717,78 @@ optimize_if_const_binop(basicblock *bb, int i, PyObject *consts, PyObject *const
 | 
			
		|||
        PyErr_Clear();
 | 
			
		||||
        return SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
    int newopcode, newoparg;
 | 
			
		||||
    RETURN_IF_ERROR(newop_from_folded(newconst, consts, const_cache, &newopcode, &newoparg));
 | 
			
		||||
    nop_out(bb, i-1, 2);
 | 
			
		||||
    INSTR_SET_OP1(binop, newopcode, newoparg);
 | 
			
		||||
    RETURN_IF_ERROR(instr_make_load_const(binop, newconst, consts, const_cache));
 | 
			
		||||
    nop_out(bb, i-1, BINOP_OPERAND_COUNT);
 | 
			
		||||
    return SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static PyObject *
 | 
			
		||||
eval_const_unaryop(PyObject *operand, int opcode, int oparg)
 | 
			
		||||
{
 | 
			
		||||
    assert(operand != NULL);
 | 
			
		||||
    assert(
 | 
			
		||||
        opcode == UNARY_NEGATIVE ||
 | 
			
		||||
        opcode == UNARY_INVERT ||
 | 
			
		||||
        opcode == UNARY_NOT ||
 | 
			
		||||
        (opcode == CALL_INTRINSIC_1 && oparg == INTRINSIC_UNARY_POSITIVE)
 | 
			
		||||
    );
 | 
			
		||||
    PyObject *result;
 | 
			
		||||
    switch (opcode) {
 | 
			
		||||
        case UNARY_NEGATIVE:
 | 
			
		||||
            result = PyNumber_Negative(operand);
 | 
			
		||||
            break;
 | 
			
		||||
        case UNARY_INVERT:
 | 
			
		||||
            result = PyNumber_Invert(operand);
 | 
			
		||||
            break;
 | 
			
		||||
        case UNARY_NOT: {
 | 
			
		||||
            int r = PyObject_IsTrue(operand);
 | 
			
		||||
            if (r < 0) {
 | 
			
		||||
                return NULL;
 | 
			
		||||
            }
 | 
			
		||||
            result = PyBool_FromLong(!r);
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        case CALL_INTRINSIC_1:
 | 
			
		||||
            if (oparg != INTRINSIC_UNARY_POSITIVE) {
 | 
			
		||||
                Py_UNREACHABLE();
 | 
			
		||||
            }
 | 
			
		||||
            result = PyNumber_Positive(operand);
 | 
			
		||||
            break;
 | 
			
		||||
        default:
 | 
			
		||||
            Py_UNREACHABLE();
 | 
			
		||||
    }
 | 
			
		||||
    return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int
 | 
			
		||||
fold_const_unaryop(basicblock *bb, int i, PyObject *consts, PyObject *const_cache)
 | 
			
		||||
{
 | 
			
		||||
    #define UNARYOP_OPERAND_COUNT 1
 | 
			
		||||
    assert(PyDict_CheckExact(const_cache));
 | 
			
		||||
    assert(PyList_CheckExact(consts));
 | 
			
		||||
    cfg_instr *instr = &bb->b_instr[i];
 | 
			
		||||
    PyObject *seq;
 | 
			
		||||
    RETURN_IF_ERROR(get_constant_sequence(bb, i-1, UNARYOP_OPERAND_COUNT, consts, &seq));
 | 
			
		||||
    if (seq == NULL) {
 | 
			
		||||
        /* not a const */
 | 
			
		||||
        return SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
    assert(PyTuple_Size(seq) == UNARYOP_OPERAND_COUNT);
 | 
			
		||||
    PyObject *operand = PyTuple_GET_ITEM(seq, 0);
 | 
			
		||||
    PyObject *newconst = eval_const_unaryop(operand, instr->i_opcode, instr->i_oparg);
 | 
			
		||||
    Py_DECREF(seq);
 | 
			
		||||
    if (newconst == NULL) {
 | 
			
		||||
        if (PyErr_ExceptionMatches(PyExc_KeyboardInterrupt)) {
 | 
			
		||||
            return ERROR;
 | 
			
		||||
        }
 | 
			
		||||
        PyErr_Clear();
 | 
			
		||||
        return SUCCESS;
 | 
			
		||||
    }
 | 
			
		||||
    if (instr->i_opcode == UNARY_NOT) {
 | 
			
		||||
        assert(PyBool_Check(newconst));
 | 
			
		||||
    }
 | 
			
		||||
    RETURN_IF_ERROR(instr_make_load_const(instr, newconst, consts, const_cache));
 | 
			
		||||
    nop_out(bb, i-1, UNARYOP_OPERAND_COUNT);
 | 
			
		||||
    return SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2023,6 +2264,13 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
 | 
			
		|||
                    INSTR_SET_OP1(&bb->b_instr[i + 1], opcode, oparg);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                if (nextop == UNARY_NOT) {
 | 
			
		||||
                    INSTR_SET_OP0(inst, NOP);
 | 
			
		||||
                    int inverted = oparg ^ 1;
 | 
			
		||||
                    assert(inverted == 0 || inverted == 1);
 | 
			
		||||
                    INSTR_SET_OP1(&bb->b_instr[i + 1], opcode, inverted);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case TO_BOOL:
 | 
			
		||||
                if (nextop == TO_BOOL) {
 | 
			
		||||
| 
						 | 
				
			
			@ -2041,15 +2289,22 @@ optimize_basic_block(PyObject *const_cache, basicblock *bb, PyObject *consts)
 | 
			
		|||
                    INSTR_SET_OP0(&bb->b_instr[i + 1], NOP);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
                _Py_FALLTHROUGH;
 | 
			
		||||
            case UNARY_INVERT:
 | 
			
		||||
            case UNARY_NEGATIVE:
 | 
			
		||||
                RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
 | 
			
		||||
                break;
 | 
			
		||||
            case CALL_INTRINSIC_1:
 | 
			
		||||
                // for _ in (*foo, *bar) -> for _ in [*foo, *bar]
 | 
			
		||||
                if (oparg == INTRINSIC_LIST_TO_TUPLE && nextop == GET_ITER) {
 | 
			
		||||
                    INSTR_SET_OP0(inst, NOP);
 | 
			
		||||
                }
 | 
			
		||||
                else if (oparg == INTRINSIC_UNARY_POSITIVE) {
 | 
			
		||||
                    RETURN_IF_ERROR(fold_const_unaryop(bb, i, consts, const_cache));
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
            case BINARY_OP:
 | 
			
		||||
                RETURN_IF_ERROR(optimize_if_const_binop(bb, i, consts, const_cache));
 | 
			
		||||
                RETURN_IF_ERROR(fold_const_binop(bb, i, consts, const_cache));
 | 
			
		||||
                break;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue