/* * Determines range (the min/max value) of integral expressions. * * Copyright © 2025 Samuel Lidén Borell * * SPDX-License-Identifier: EUPL-1.2+ OR LGPL-2.1-or-later */ #include #include "compiler.h" bool is_const(const struct Range *range) { return range->min == range->max; } static SlulInt checked_add(SlulInt a, SlulInt b) { SlulInt x = a + b; if (x < a) { error("Addition might overflow"); } return x; } static SlulInt checked_sub(SlulInt a, SlulInt b) { if (b > a) { error("Subtraction might underflow"); } return a - b; } static SlulInt checked_mul(SlulInt a, SlulInt b) { SlulInt x; x = a * b; if (b != 0 && x/b != a) { error("Multiplication might overflow"); } return x; } /** Computes minimum and maximum bounds of a modulus operations. Takes some common cases into account, but not all. */ static struct Range range_mod( const struct Range *a, const struct Range *b) { /* The range is never larger than 0..(b-1) */ struct Range r; r.min = 0; r.max = b->max - 1; /* If `abs(a) < b`, then we can narrow down the range further */ if (is_const(b) && a->min < b->max && a->max < b->max) { r.min = a->min; r.max = a->max; } return r; } static void widen_range( struct Range *r, SlulInt n) { if (n < r->min) { r->min = n; } if (n > r->max) { r->max = n; } } static struct Range sort_nums( SlulInt a, SlulInt b, SlulInt c, SlulInt d) { struct Range r = { SLUL_INT_MAX, 0 }; widen_range(&r, a); widen_range(&r, b); widen_range(&r, c); widen_range(&r, d); return r; } struct Range arithmetic_op_range( enum ExprKind operation, const struct Range *a, const struct Range *b) { struct Range r; switch ((int)operation) { case E_ADD: r.min = checked_add(a->min, b->min); r.max = checked_add(a->max, b->max); break; case E_SUB: r.min = checked_sub(a->min, b->max); r.max = checked_sub(a->max, b->min); break; case E_MUL: { SlulInt ll, lh, hl, hh; ll = checked_mul(a->min, b->min); lh = checked_mul(a->min, b->max); hl = checked_mul(a->max, b->min); hh = checked_mul(a->max, b->max); r = sort_nums(ll, lh, hl, hh); break; } case E_DIV: { SlulInt ll, lh, hl, hh; if (b->min == 0) { error("Possibility of division by zero"); } ll = a->min / b->min; lh = a->min / b->max; hl = a->max / b->min; hh = a->max / b->max; r = sort_nums(ll, lh, hl, hh); break; } case E_MOD: /* `b` must be positive (the bootstrap compiler doens't support negative numbers, so a check for zero is sufficient here) */ if (b->min == 0) { error("Possibility of modulus by zero"); } assert(b->max != 0); if (is_const(a) && is_const(b)) { r.min = r.max = a->min % b->min; } else if (is_const(a) && a->min == 0) { r.min = r.max = 0; } else { r = range_mod(a, b); } break; default: unreachable(); } return r; }