Re: [PATCH v2] minmax: clamp more efficiently by avoiding extra comparison
From: Jason A. Donenfeld
Date: Fri Sep 23 2022 - 12:44:00 EST
On Fri, Sep 23, 2022 at 6:41 PM Kees Cook <keescook@xxxxxxxxxxxx> wrote:
>
> On Fri, Sep 23, 2022 at 05:40:01PM +0200, Jason A. Donenfeld wrote:
> > Currently the clamp algorithm does:
> >
> > if (val > hi)
> > val = hi;
> > if (val < lo)
> > val = lo;
> >
> > But since hi > lo by definition, this can be made more efficient with:
> >
> > if (val > hi)
> > val = hi;
> > else if (val < lo)
> > val = lo;
> >
> > So fix up the clamp and clamp_t functions to do this, adding the same
> > argument checking as for min and min_t.
> >
> > Cc: Andy Shevchenko <andriy.shevchenko@xxxxxxxxxxxxxxx>
> > Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx>
> > Cc: Kees Cook <keescook@xxxxxxxxxxxx>
> > Signed-off-by: Jason A. Donenfeld <Jason@xxxxxxxxx>
> > ---
> > include/linux/minmax.h | 25 +++++++++++++++++++++++--
> > 1 file changed, 23 insertions(+), 2 deletions(-)
> >
> > diff --git a/include/linux/minmax.h b/include/linux/minmax.h
> > index 5433c08fcc68..30e2e2cd0f44 100644
> > --- a/include/linux/minmax.h
> > +++ b/include/linux/minmax.h
> > @@ -37,6 +37,27 @@
> > __cmp(x, y, op), \
> > __cmp_once(x, y, __UNIQUE_ID(__x), __UNIQUE_ID(__y), op))
> >
> > +#define __clamp(val, lo, hi) \
> > + ((val) >= (hi) ? (hi) : ((val) <= (lo) ? (lo) : (val)))
> > +
> > +#define __clamp_once(val, lo, hi, unique_val, unique_lo, unique_hi) ({ \
> > + typeof(val) unique_val = (val); \
> > + typeof(lo) unique_lo = (lo); \
> > + typeof(hi) unique_hi = (hi); \
> > + __clamp(unique_val, unique_lo, unique_hi); })
> > +
> > +#define __clamp_input_check(lo, hi) \
> > + (BUILD_BUG_ON_ZERO(__builtin_choose_expr( \
> > + __is_constexpr((lo) > (hi)), (lo) > (hi), false)))
>
> Nice. :)
>
> > +
> > +#define __careful_clamp(val, lo, hi) ({ \
> > + __clamp_input_check(lo, hi) + \
> > + __builtin_choose_expr(__typecheck(val, lo) && __typecheck(val, hi) && \
> > + __typecheck(hi, lo) && __is_constexpr(val) && \
> > + __is_constexpr(lo) && __is_constexpr(hi), \
>
> I really like it! I might have used:
>
> __safe_cmp(val, lo) && __safe_cmp(val, hi)
>
> instead of the "open coded" __typecheck()s and __is_constexpr()s, but
> it's the same result.
>
> > + __clamp(val, lo, hi), \
> > + __clamp_once(val, lo, hi, __UNIQUE_ID(__val), __UNIQUE_ID(__lo), __UNIQUE_ID(__hi))); })
>
> *complaint about line being >100 characters, but I don't really care* If
> anyone is really bothered, this looks fine, too:
>
> __clamp_once(val, lo, hi, \
> __UNIQUE_ID(__val), __UNIQUE_ID(__lo), __UNIQUE_ID(__hi))); })
>
> *shrug*
>
> > +
> > /**
> > * min - return minimum of two values of the same or compatible types
> > * @x: first value
> > @@ -86,7 +107,7 @@
> > * This macro does strict typechecking of @lo/@hi to make sure they are of the
> > * same type as @val. See the unnecessary pointer comparisons.
> > */
> > -#define clamp(val, lo, hi) min((typeof(val))max(val, lo), hi)
> > +#define clamp(val, lo, hi) __careful_clamp(val, lo, hi)
> >
> > /*
> > * ..and if you can't take the strict
> > @@ -121,7 +142,7 @@
> > * This macro does no typechecking and uses temporary variables of type
> > * @type to make all the comparisons.
> > */
> > -#define clamp_t(type, val, lo, hi) min_t(type, max_t(type, val, lo), hi)
> > +#define clamp_t(type, val, lo, hi) __careful_clamp((type)(val), (type)(lo), (type)(hi))
> >
> > /**
> > * clamp_val - return a value clamped to a given range using val's type
> > --
> > 2.37.3
> >
>
> Reviewed-by: Kees Cook <keescook@xxxxxxxxxxxx>
>
> I can take this unless akpm wants it?
Fine by me.