[PATCH v2] mm: folio_zero_user: open code range computation in folio_zero_user()

Ankur Arora posted 1 patch 1 week, 2 days ago
There is a newer version of this series
mm/memory.c | 17 ++++++++---------
1 file changed, 8 insertions(+), 9 deletions(-)
[PATCH v2] mm: folio_zero_user: open code range computation in folio_zero_user()
Posted by Ankur Arora 1 week, 2 days ago
riscv64-gcc-linux-gnu (v8.5) reports a compile time assert in:

   r[2] = DEFINE_RANGE(clamp_t(s64, fault_idx - radius, pg.start, pg.end),
 		       clamp_t(s64, fault_idx + radius, pg.start, pg.end));

where it decides that pg.start > pg.end in:
  clamp_t(s64, fault_idx + radius, pg.start, pg.end));

where pg comes from:
  const struct range pg = DEFINE_RANGE(0, folio_nr_pages(folio) - 1);

That does not seem like it could be true. Even for pg.start == pg.end,
we would need folio_test_large() to evaluate to false at compile time:

  static inline unsigned long folio_nr_pages(const struct folio *folio)
  {
	if (!folio_test_large(folio))
		return 1;
	return folio_large_nr_pages(folio);
  }

Workaround by open coding the range computation. Also, simplify the type
declarations for the relevant variables.

Reported-by: kernel test robot <lkp@intel.com>
Closes: https://lore.kernel.org/oe-kbuild-all/202601240453.QCjgGdJa-lkp@intel.com/
Fixes: 93552c9a3350 ("mm: folio_zero_user: cache neighbouring pages")
Signed-off-by: Ankur Arora <ankur.a.arora@oracle.com>
---

Hi Andrew

As David pointed out, the previous open coded version makes a few
unnecessary changes. Could you queue this one instead?

Thanks
Ankur


 mm/memory.c | 17 ++++++++---------
 1 file changed, 8 insertions(+), 9 deletions(-)

diff --git a/mm/memory.c b/mm/memory.c
index ce933ee4a3dd..f5bfc082ab61 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -7284,7 +7284,7 @@ void folio_zero_user(struct folio *folio, unsigned long addr_hint)
 	const unsigned long base_addr = ALIGN_DOWN(addr_hint, folio_size(folio));
 	const long fault_idx = (addr_hint - base_addr) / PAGE_SIZE;
 	const struct range pg = DEFINE_RANGE(0, folio_nr_pages(folio) - 1);
-	const int radius = FOLIO_ZERO_LOCALITY_RADIUS;
+	const long radius = FOLIO_ZERO_LOCALITY_RADIUS;
 	struct range r[3];
 	int i;
 
@@ -7292,24 +7292,23 @@ void folio_zero_user(struct folio *folio, unsigned long addr_hint)
 	 * Faulting page and its immediate neighbourhood. Will be cleared at the
 	 * end to keep its cachelines hot.
 	 */
-	r[2] = DEFINE_RANGE(clamp_t(s64, fault_idx - radius, pg.start, pg.end),
-			    clamp_t(s64, fault_idx + radius, pg.start, pg.end));
+	r[2] = DEFINE_RANGE(fault_idx - radius < (long)pg.start ? pg.start : fault_idx - radius,
+			    fault_idx + radius > (long)pg.end   ? pg.end   : fault_idx + radius);
+
 
 	/* Region to the left of the fault */
-	r[1] = DEFINE_RANGE(pg.start,
-			    clamp_t(s64, r[2].start - 1, pg.start - 1, r[2].start));
+	r[1] = DEFINE_RANGE(pg.start, r[2].start - 1);
 
 	/* Region to the right of the fault: always valid for the common fault_idx=0 case. */
-	r[0] = DEFINE_RANGE(clamp_t(s64, r[2].end + 1, r[2].end, pg.end + 1),
-			    pg.end);
+	r[0] = DEFINE_RANGE(r[2].end + 1, pg.end);
 
 	for (i = 0; i < ARRAY_SIZE(r); i++) {
 		const unsigned long addr = base_addr + r[i].start * PAGE_SIZE;
-		const unsigned int nr_pages = range_len(&r[i]);
+		const long nr_pages = (long)range_len(&r[i]);
 		struct page *page = folio_page(folio, r[i].start);
 
 		if (nr_pages > 0)
-			clear_contig_highpages(page, addr, nr_pages);
+			clear_contig_highpages(page, addr, (unsigned int)nr_pages);
 	}
 }
 
-- 
2.31.1
Re: [PATCH v2] mm: folio_zero_user: open code range computation in folio_zero_user()
Posted by David Hildenbrand (arm) 2 days, 15 hours ago
On 1/28/26 19:59, Ankur Arora wrote:
> riscv64-gcc-linux-gnu (v8.5) reports a compile time assert in:
> 
>     r[2] = DEFINE_RANGE(clamp_t(s64, fault_idx - radius, pg.start, pg.end),
>   		       clamp_t(s64, fault_idx + radius, pg.start, pg.end));
> 
> where it decides that pg.start > pg.end in:
>    clamp_t(s64, fault_idx + radius, pg.start, pg.end));
> 
> where pg comes from:
>    const struct range pg = DEFINE_RANGE(0, folio_nr_pages(folio) - 1);
> 
> That does not seem like it could be true. Even for pg.start == pg.end,
> we would need folio_test_large() to evaluate to false at compile time:
> 
>    static inline unsigned long folio_nr_pages(const struct folio *folio)
>    {
> 	if (!folio_test_large(folio))
> 		return 1;
> 	return folio_large_nr_pages(folio);
>    }
> 
> Workaround by open coding the range computation. Also, simplify the type
> declarations for the relevant variables.
> 
> Reported-by: kernel test robot <lkp@intel.com>
> Closes: https://lore.kernel.org/oe-kbuild-all/202601240453.QCjgGdJa-lkp@intel.com/
> Fixes: 93552c9a3350 ("mm: folio_zero_user: cache neighbouring pages")
> Signed-off-by: Ankur Arora <ankur.a.arora@oracle.com>
> ---
> 
> Hi Andrew
> 
> As David pointed out, the previous open coded version makes a few
> unnecessary changes. Could you queue this one instead?
> 

I'm late, maybe this is already upstream.

> Thanks
> Ankur
> 
> 
>   mm/memory.c | 17 ++++++++---------
>   1 file changed, 8 insertions(+), 9 deletions(-)
> 
> diff --git a/mm/memory.c b/mm/memory.c
> index ce933ee4a3dd..f5bfc082ab61 100644
> --- a/mm/memory.c
> +++ b/mm/memory.c
> @@ -7284,7 +7284,7 @@ void folio_zero_user(struct folio *folio, unsigned long addr_hint)
>   	const unsigned long base_addr = ALIGN_DOWN(addr_hint, folio_size(folio));
>   	const long fault_idx = (addr_hint - base_addr) / PAGE_SIZE;
>   	const struct range pg = DEFINE_RANGE(0, folio_nr_pages(folio) - 1);
> -	const int radius = FOLIO_ZERO_LOCALITY_RADIUS;
> +	const long radius = FOLIO_ZERO_LOCALITY_RADIUS;
>   	struct range r[3];
>   	int i;
>   
> @@ -7292,24 +7292,23 @@ void folio_zero_user(struct folio *folio, unsigned long addr_hint)
>   	 * Faulting page and its immediate neighbourhood. Will be cleared at the
>   	 * end to keep its cachelines hot.
>   	 */
> -	r[2] = DEFINE_RANGE(clamp_t(s64, fault_idx - radius, pg.start, pg.end),
> -			    clamp_t(s64, fault_idx + radius, pg.start, pg.end));
> +	r[2] = DEFINE_RANGE(fault_idx - radius < (long)pg.start ? pg.start : fault_idx - radius,
> +			    fault_idx + radius > (long)pg.end   ? pg.end   : fault_idx + radius);
> +

LGTM, although it could likely be made a bit more readable by using some temporary variables.


const long fault_idx_low = fault_idx - radius;
const long fault_idx_high = fault_idx + radius;

r[2] = DEFINE_RANGE(fault_idx_low < (long)pg.start ? pg.start : fault_idx_low,
		    fault_idx_high > (long)pg.end ? pg.end : fault_idx_high);

Well, still a bit unreadable, so ... :)


>   
>   	/* Region to the left of the fault */
> -	r[1] = DEFINE_RANGE(pg.start,
> -			    clamp_t(s64, r[2].start - 1, pg.start - 1, r[2].start));
> +	r[1] = DEFINE_RANGE(pg.start, r[2].start - 1);
>   
>   	/* Region to the right of the fault: always valid for the common fault_idx=0 case. */
> -	r[0] = DEFINE_RANGE(clamp_t(s64, r[2].end + 1, r[2].end, pg.end + 1),
> -			    pg.end);
> +	r[0] = DEFINE_RANGE(r[2].end + 1, pg.end);

TBH, without the clamp that looks much more readable here.

>   
>   	for (i = 0; i < ARRAY_SIZE(r); i++) {
>   		const unsigned long addr = base_addr + r[i].start * PAGE_SIZE;
> -		const unsigned int nr_pages = range_len(&r[i]);
> +		const long nr_pages = (long)range_len(&r[i]);
>   		struct page *page = folio_page(folio, r[i].start);
>   
>   		if (nr_pages > 0)
> -			clear_contig_highpages(page, addr, nr_pages);
> +			clear_contig_highpages(page, addr, (unsigned int)nr_pages);

Is that cast really required?

-- 
Cheers,

David
Re: [PATCH v2] mm: folio_zero_user: open code range computation in folio_zero_user()
Posted by Andrew Morton 2 days, 14 hours ago
On Wed, 4 Feb 2026 22:01:42 +0100 "David Hildenbrand (arm)" <david@kernel.org> wrote:

> > As David pointed out, the previous open coded version makes a few
> > unnecessary changes. Could you queue this one instead?
> > 
> 
> I'm late, maybe this is already upstream.

It's in mm-unstable.  The second round of MM upstreaming is two weeks hence.

> >   
> >   	/* Region to the left of the fault */
> > -	r[1] = DEFINE_RANGE(pg.start,
> > -			    clamp_t(s64, r[2].start - 1, pg.start - 1, r[2].start));
> > +	r[1] = DEFINE_RANGE(pg.start, r[2].start - 1);
> >   
> >   	/* Region to the right of the fault: always valid for the common fault_idx=0 case. */
> > -	r[0] = DEFINE_RANGE(clamp_t(s64, r[2].end + 1, r[2].end, pg.end + 1),
> > -			    pg.end);
> > +	r[0] = DEFINE_RANGE(r[2].end + 1, pg.end);
> 
> TBH, without the clamp that looks much more readable here.

me too.

> >   
> >   	for (i = 0; i < ARRAY_SIZE(r); i++) {
> >   		const unsigned long addr = base_addr + r[i].start * PAGE_SIZE;
> > -		const unsigned int nr_pages = range_len(&r[i]);
> > +		const long nr_pages = (long)range_len(&r[i]);
> >   		struct page *page = folio_page(folio, r[i].start);
> >   
> >   		if (nr_pages > 0)
> > -			clear_contig_highpages(page, addr, nr_pages);
> > +			clear_contig_highpages(page, addr, (unsigned int)nr_pages);
> 
> Is that cast really required?

Seems not.  The types for nr_pages are a bit chaotic - u64->long->uint.
Re: [PATCH v2] mm: folio_zero_user: open code range computation in folio_zero_user()
Posted by Ankur Arora 2 days, 6 hours ago
Andrew Morton <akpm@linux-foundation.org> writes:

> On Wed, 4 Feb 2026 22:01:42 +0100 "David Hildenbrand (arm)" <david@kernel.org> wrote:
>
>> > As David pointed out, the previous open coded version makes a few
>> > unnecessary changes. Could you queue this one instead?
>> >
>>
>> I'm late, maybe this is already upstream.
>
> It's in mm-unstable.  The second round of MM upstreaming is two weeks hence.
>
>> >
>> >   	/* Region to the left of the fault */
>> > -	r[1] = DEFINE_RANGE(pg.start,
>> > -			    clamp_t(s64, r[2].start - 1, pg.start - 1, r[2].start));
>> > +	r[1] = DEFINE_RANGE(pg.start, r[2].start - 1);
>> >
>> >   	/* Region to the right of the fault: always valid for the common fault_idx=0 case. */
>> > -	r[0] = DEFINE_RANGE(clamp_t(s64, r[2].end + 1, r[2].end, pg.end + 1),
>> > -			    pg.end);
>> > +	r[0] = DEFINE_RANGE(r[2].end + 1, pg.end);
>>
>> TBH, without the clamp that looks much more readable here.
>
> me too.
>
>> >
>> >   	for (i = 0; i < ARRAY_SIZE(r); i++) {
>> >   		const unsigned long addr = base_addr + r[i].start * PAGE_SIZE;
>> > -		const unsigned int nr_pages = range_len(&r[i]);
>> > +		const long nr_pages = (long)range_len(&r[i]);
>> >   		struct page *page = folio_page(folio, r[i].start);
>> >
>> >   		if (nr_pages > 0)
>> > -			clear_contig_highpages(page, addr, nr_pages);
>> > +			clear_contig_highpages(page, addr, (unsigned int)nr_pages);
>>
>> Is that cast really required?
>
> Seems not.  The types for nr_pages are a bit chaotic - u64->long->uint.

Yes agreed.

The first u64 is because currently struct range only supports that.
Then the cast to signed long is because the range can be negative
and the clear_contig_highpages() is only done if nr_pages > 0.

And, the third one is almost certainly unnecessary for any realistic
hugepage size but since nr_pages is being truncating, I wanted that
to be explicit.

--
ankur
Re: [PATCH v2] mm: folio_zero_user: open code range computation in folio_zero_user()
Posted by David Hildenbrand (Arm) 1 day, 23 hours ago
On 2/5/26 06:48, Ankur Arora wrote:
> 
> Andrew Morton <akpm@linux-foundation.org> writes:
> 
>> On Wed, 4 Feb 2026 22:01:42 +0100 "David Hildenbrand (arm)" <david@kernel.org> wrote:
>>
>>>
>>> I'm late, maybe this is already upstream.
>>
>> It's in mm-unstable.  The second round of MM upstreaming is two weeks hence.
>>
>>>
>>> TBH, without the clamp that looks much more readable here.
>>
>> me too.
>>
>>>
>>> Is that cast really required?
>>
>> Seems not.  The types for nr_pages are a bit chaotic - u64->long->uint.
> 
> Yes agreed.
> 
> The first u64 is because currently struct range only supports that.
> Then the cast to signed long is because the range can be negative
> and the clear_contig_highpages() is only done if nr_pages > 0.

That makes sense to me.

> 
> And, the third one is almost certainly unnecessary for any realistic
> hugepage size but since nr_pages is being truncating, I wanted that
> to be explicit.

But the non-silent truncation is no better? IOW, it doesn't matter.

You could just make clear_contig_highpages() consume an unsigned long ...

-- 
Cheers,

David
Re: [PATCH v2] mm: folio_zero_user: open code range computation in folio_zero_user()
Posted by Ankur Arora 1 day, 6 hours ago
David Hildenbrand (Arm) <david@kernel.org> writes:

> On 2/5/26 06:48, Ankur Arora wrote:
>> Andrew Morton <akpm@linux-foundation.org> writes:
>>
>>> On Wed, 4 Feb 2026 22:01:42 +0100 "David Hildenbrand (arm)" <david@kernel.org> wrote:
>>>
>>>>
>>>> I'm late, maybe this is already upstream.
>>>
>>> It's in mm-unstable.  The second round of MM upstreaming is two weeks hence.
>>>
>>>>
>>>> TBH, without the clamp that looks much more readable here.
>>>
>>> me too.
>>>
>>>>
>>>> Is that cast really required?
>>>
>>> Seems not.  The types for nr_pages are a bit chaotic - u64->long->uint.
>> Yes agreed.
>> The first u64 is because currently struct range only supports that.
>> Then the cast to signed long is because the range can be negative
>> and the clear_contig_highpages() is only done if nr_pages > 0.
>
> That makes sense to me.
>
>> And, the third one is almost certainly unnecessary for any realistic
>> hugepage size but since nr_pages is being truncating, I wanted that
>> to be explicit.
>
> But the non-silent truncation is no better? IOW, it doesn't matter.

I never seem to get them but I thought we had some kconfig option that
makes gcc give a warning to that effect.

I can update this patch to just implicitly truncate.

> You could just make clear_contig_highpages() consume an unsigned long ...

Unfortunately that'll be an even bigger mess. The clear_contig_highpages()
version in mm-stable uses the unsigned intness of nr_pages all over:

  static void clear_contig_highpages(struct page *page, unsigned long addr,
				   unsigned int nr_pages)
  {
	unsigned int i, count;
	/*
	 * When clearing we want to operate on the largest extent possible to
	 * allow for architecture specific extent based optimizations.
	 *
	 * However, since clear_user_highpages() (and primitives clear_user_pages(),
	 * clear_pages()), do not call cond_resched(), limit the unit size when
	 * running under non-preemptible scheduling models.
	 */
	const unsigned int unit = preempt_model_preemptible() ?
				   nr_pages : PROCESS_PAGES_NON_PREEMPT_BATCH;

	might_sleep();

	for (i = 0; i < nr_pages; i += count) {
		cond_resched();

		count = min(unit, nr_pages - i);
		clear_user_highpages(page + i, addr + i * PAGE_SIZE, count);
	}
  }

Thanks
--
ankur
Re: [PATCH v2] mm: folio_zero_user: open code range computation in folio_zero_user()
Posted by David Hildenbrand (Arm) 1 day, 3 hours ago
On 2/6/26 06:42, Ankur Arora wrote:
> 
> David Hildenbrand (Arm) <david@kernel.org> writes:
> 
>> On 2/5/26 06:48, Ankur Arora wrote:
>>> Andrew Morton <akpm@linux-foundation.org> writes:
>>>
>>> Yes agreed.
>>> The first u64 is because currently struct range only supports that.
>>> Then the cast to signed long is because the range can be negative
>>> and the clear_contig_highpages() is only done if nr_pages > 0.
>>
>> That makes sense to me.
>>
>>> And, the third one is almost certainly unnecessary for any realistic
>>> hugepage size but since nr_pages is being truncating, I wanted that
>>> to be explicit.
>>
>> But the non-silent truncation is no better? IOW, it doesn't matter.
> 
> I never seem to get them but I thought we had some kconfig option that
> makes gcc give a warning to that effect.

I think we do it all the time :)

> 
> I can update this patch to just implicitly truncate.
> 

Yeah, I think the implicit once can just be dropped.

>> You could just make clear_contig_highpages() consume an unsigned long ...
> 
> Unfortunately that'll be an even bigger mess. The clear_contig_highpages()
> version in mm-stable uses the unsigned intness of nr_pages all over:

Right.

In any case, thanks and feel free to add

Acked-by: David Hildenbrand (Arm) <david@kernel.org>

-- 
Cheers,

David