| /* SPDX-License-Identifier: GPL-2.0-only */ | |
| /* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */ | |
| #ifndef T | |
| #error "Define T (bit width: 32, 64) before including cnum_defs.h" | |
| #endif | |
| #include <linux/cnum.h> | |
| #include <linux/kernel.h> | |
| #include <linux/limits.h> | |
| #include <linux/minmax.h> | |
| #include <linux/compiler_types.h> | |
| #define cnum_t __PASTE(cnum, T) | |
| #define ut __PASTE(u, T) | |
| #define st __PASTE(s, T) | |
| #define UT_MAX __PASTE(__PASTE(U, T), _MAX) | |
| #define ST_MAX __PASTE(__PASTE(S, T), _MAX) | |
| #define ST_MIN __PASTE(__PASTE(S, T), _MIN) | |
| #define EMPTY __PASTE(__PASTE(CNUM, T), _EMPTY) | |
| #define FN(name) __PASTE(__PASTE(cnum, T), __PASTE(_, name)) | |
| struct cnum_t FN(from_urange)(ut min, ut max) | |
| { | |
| return (struct cnum_t){ .base = min, .size = (ut)max - min }; | |
| } | |
| struct cnum_t FN(from_srange)(st min, st max) | |
| { | |
| ut size = (ut)max - (ut)min; | |
| ut base = size == UT_MAX ? 0 : (ut)min; | |
| return (struct cnum_t){ .base = base, .size = size }; | |
| } | |
| /* True if this cnum represents two unsigned ranges. */ | |
| static inline bool FN(urange_overflow)(struct cnum_t cnum) | |
| { | |
| /* Same as cnum.base + cnum.size > UT_MAX but avoids overflow */ | |
| return cnum.size > UT_MAX - (ut)cnum.base; | |
| } | |
| /* | |
| * cnum{T}_umin / cnum{T}_umax query an unsigned range represented by this cnum. | |
| * If cnum represents a range crossing the UT_MAX/0 boundary, the unbound range | |
| * [0..UT_MAX] is returned. | |
| */ | |
| ut FN(umin)(struct cnum_t cnum) | |
| { | |
| return FN(urange_overflow)(cnum) ? 0 : cnum.base; | |
| } | |
| EXPORT_SYMBOL_GPL(FN(umin)); | |
| ut FN(umax)(struct cnum_t cnum) | |
| { | |
| return FN(urange_overflow)(cnum) ? UT_MAX : cnum.base + cnum.size; | |
| } | |
| EXPORT_SYMBOL_GPL(FN(umax)); | |
| /* True if this cnum represents two signed ranges. */ | |
| static inline bool FN(srange_overflow)(struct cnum_t cnum) | |
| { | |
| return FN(contains)(cnum, (ut)ST_MAX) && FN(contains)(cnum, (ut)ST_MIN); | |
| } | |
| /* | |
| * cnum{T}_smin / cnum{T}_smax query a signed range represented by this cnum. | |
| * If cnum represents a range crossing the ST_MAX/ST_MIN boundary, the unbound range | |
| * [ST_MIN..ST_MAX] is returned. | |
| */ | |
| st FN(smin)(struct cnum_t cnum) | |
| { | |
| return FN(srange_overflow)(cnum) | |
| ? ST_MIN | |
| : min((st)cnum.base, (st)(cnum.base + cnum.size)); | |
| } | |
| st FN(smax)(struct cnum_t cnum) | |
| { | |
| return FN(srange_overflow)(cnum) | |
| ? ST_MAX | |
| : max((st)cnum.base, (st)(cnum.base + cnum.size)); | |
| } | |
| /* | |
| * Returns a possibly empty intersection of cnums 'a' and 'b'. | |
| * If 'a' and 'b' intersect in two sub-arcs, the function over-approximates | |
| * and returns either 'a' or 'b', whichever is smaller. | |
| */ | |
| struct cnum_t FN(intersect)(struct cnum_t a, struct cnum_t b) | |
| { | |
| struct cnum_t b1; | |
| ut dbase; | |
| if (FN(is_empty)(a) || FN(is_empty)(b)) | |
| return EMPTY; | |
| if (a.base > b.base) | |
| swap(a, b); | |
| /* | |
| * Rotate frame of reference such that a.base is 0. | |
| * 'b1' is 'b' in this frame of reference. | |
| */ | |
| dbase = b.base - a.base; | |
| b1 = (struct cnum_t){ dbase, b.size }; | |
| if (FN(urange_overflow)(b1)) { | |
| if (b1.base <= a.size) { | |
| /* | |
| * Rotated frame (a.base at origin): | |
| * | |
| * 0 UT_MAX | |
| * |--------------------------------------------| | |
| * [=== a ==========================] | | |
| * [= b1 tail =] [========= b1 main ==========>] | |
| * ^-- b1.base <= a.size | |
| * | |
| * 'a' and 'b' intersect in two disjoint arcs, | |
| * can't represent as single cnum, over-approximate | |
| * the result. | |
| */ | |
| return a.size <= b.size ? a : b; | |
| } else { | |
| /* | |
| * Rotated frame (a.base at origin): | |
| * | |
| * 0 UT_MAX | |
| * |--------------------------------------------| | |
| * [=== a =============] | | | |
| * [= b1 tail =] [======= b1 main ====>] | |
| * ^-- b1.base > a.size | |
| * | |
| * Only 'b' tail intersects 'a'. | |
| */ | |
| return (struct cnum_t) { | |
| .base = a.base, | |
| .size = min(a.size, (ut)(b1.base + b1.size)), | |
| }; | |
| } | |
| } else if (a.size >= b1.base) { | |
| /* | |
| * Rotated frame (a.base at origin): | |
| * | |
| * 0 UT_MAX | |
| * |--------------------------------------------------| | |
| * [=== a ==================================] | | |
| * [== b1 =====================] | |
| * | |
| * 0 UT_MAX | |
| * |--------------------------------------------------| | |
| * [=== a ==================================] | | |
| * [== b1 ====] | |
| * ^-- b1.base <= a.size | |
| * |<-- a.size - dbase -->| | |
| * | |
| * 'a' and 'b' intersect as one cnum. | |
| */ | |
| return (struct cnum_t) { | |
| .base = b.base, | |
| .size = min((ut)(a.size - dbase), b.size), | |
| }; | |
| } else { | |
| return EMPTY; | |
| } | |
| } | |
| void FN(intersect_with)(struct cnum_t *dst, struct cnum_t src) | |
| { | |
| *dst = FN(intersect)(*dst, src); | |
| } | |
| void FN(intersect_with_urange)(struct cnum_t *dst, ut min, ut max) | |
| { | |
| FN(intersect_with)(dst, FN(from_urange)(min, max)); | |
| } | |
| void FN(intersect_with_srange)(struct cnum_t *dst, st min, st max) | |
| { | |
| FN(intersect_with)(dst, FN(from_srange)(min, max)); | |
| } | |
| static inline struct cnum_t FN(normalize)(struct cnum_t cnum) | |
| { | |
| if (cnum.size == UT_MAX && cnum.base != 0 && cnum.base != (ut)ST_MAX) | |
| cnum.base = 0; | |
| return cnum; | |
| } | |
| struct cnum_t FN(add)(struct cnum_t a, struct cnum_t b) | |
| { | |
| if (FN(is_empty)(a) || FN(is_empty)(b)) | |
| return EMPTY; | |
| if (a.size > UT_MAX - b.size) | |
| return (struct cnum_t){ 0, (ut)UT_MAX }; | |
| else | |
| return FN(normalize)((struct cnum_t){ a.base + b.base, a.size + b.size }); | |
| } | |
| struct cnum_t FN(negate)(struct cnum_t a) | |
| { | |
| if (FN(is_empty)(a)) | |
| return EMPTY; | |
| return FN(normalize)((struct cnum_t){ -((ut)a.base + a.size), a.size }); | |
| } | |
| bool FN(is_empty)(struct cnum_t cnum) | |
| { | |
| return cnum.base == EMPTY.base && cnum.size == EMPTY.size; | |
| } | |
| bool FN(contains)(struct cnum_t cnum, ut v) | |
| { | |
| if (FN(is_empty)(cnum)) | |
| return false; | |
| if (FN(urange_overflow)(cnum)) | |
| return v >= cnum.base || v <= (ut)cnum.base + cnum.size; | |
| else | |
| return v >= cnum.base && v <= (ut)cnum.base + cnum.size; | |
| } | |
| bool FN(is_const)(struct cnum_t cnum) | |
| { | |
| return cnum.size == 0; | |
| } | |
| bool FN(is_subset)(struct cnum_t bigger, struct cnum_t smaller) | |
| { | |
| if (FN(is_empty(smaller))) | |
| return true; | |
| if (FN(is_empty(bigger))) | |
| return false; | |
| /* rotate both arcs such that 'bigger' starts at origin, hence does not overflow */ | |
| smaller.base -= bigger.base; | |
| bigger.base = 0; | |
| if (FN(urange_overflow)(smaller) && bigger.size < UT_MAX) | |
| return false; | |
| return smaller.base + smaller.size <= bigger.size; | |
| } | |
| #undef EMPTY | |
| #undef cnum_t | |
| #undef ut | |
| #undef st | |
| #undef UT_MAX | |
| #undef ST_MAX | |
| #undef ST_MIN | |
| #undef FN |