[Rd] [External] Re: Ncell GC trigger reaches SIZE_MAX after grow/shrink/regrow in AdjustHeapSize

iuke-tier@ey m@iii@g oii uiow@@edu iuke-tier@ey m@iii@g oii uiow@@edu
Sat Oct 31 21:31:13 CET 2026


Thanks Tyler and Ivan. Fix committed to the trunk in r90820.

Best,

luke

On Mon, 19 Oct 2026, Ivan Krylov wrote:

> В Mon, 5 Oct 2026 01:43:31 -0400
> Tyler <tylermw using gmail.com> пишет:
>
>> I found a reproducible node-heap sizing bug in R 4.6.0 on arm64 macOS.
>> After allocating and releasing a list, shrinking the heap with gc(),
>> and allocating a smaller list, the Ncell GC trigger becomes
>> approximately 1.844674e19, although fewer than one million Ncells are
>> live.
>
> Thank you for the report! I can also reproduce the R_NSize overflow on
> Linux/amd64 when R is compiled using Clang, but not GCC. As you
> mention, R builds with -fsanitize=undefined using both GCC and Clang
> show a runtime error when the double -> size_t conversion overflows,
> even if R_NSize stays fine.
>
> R_size_t next_in_use = R_NodesInUse + (R_NodesInUse - last_in_use);
> (gdb) p R_NodesInUse
> $6 = 992417
> (gdb) p last_in_use
> $7 = 2332966
> (gdb) p /x (R_NodesInUse - last_in_use)
> $8 = 0xffffffffffeb8b7b
>
> R_size_t next_nsize = (R_size_t) (next_in_use / R_NGrowFrac);
> // ../../../src/main/memory.c:1204:52: runtime error: 2.63525e+19 is
> // outside the range of representable values of type 'long unsigned int'
>
> GCC compiles this as:
>                                R_NGrowFrac -> xmm5
>                        (double)PTRDIFF_MAX -> xmm1
> ; convert next_in_use to double accounting for the sign flag set
> mov      %rdx,%rdi
> and      $0x1,%edx
> pxor     %xmm0,%xmm0
> shr      %rdi
> or       %rdx,%rdi
> cvtsi2sd %rdi,%xmm0
> addsd    %xmm0,%xmm0          ; (double)next_in_use -> xmm0
> divsd    %xmm5,%xmm0          ; xmm0 / xmm5 -> xmm0
> (gdb) p $xmm0.v2_double[0]
> $35 = 2.6352491533870297e+19
> comisd   %xmm1,%xmm0          ; result exceeds PTRDIFF_MAX? (yes)
> ...
> subsd    %xmm1,%xmm0          ; xmm0 - xmm1 -> xmm0
> cvttsd2si %xmm0,%rdi          ; failed conversion stores (1<<63) in rdi
> btc      $0x3f,%rdi           ; flips bit 63 in rdi, leaving 0
>                              ; rdi -> next_nsize
> (gdb) p /x next_nsize
> $51 = 0x0
>
> Clang compiles the same code as:
>                                R_NGrowFrac -> xmm3
>                                next_in_use -> r9
> movq      %r9,%xmm1           ; (double)r9 -> xmm1
> punpckldq 0x101164(%rip),%xmm1
> subpd     0x10116c(%rip),%xmm1
> movapd    %xmm1,%xmm2         ; xmm1 -> xmm2
> unpckhpd  %xmm1,%xmm2
> addsd     %xmm1,%xmm2
> divsd     %xmm3,%xmm2         ; xmm2 / xmm3 -> xmm2
> (gdb) p $xmm2.v2_double[0]
> $32 = 2.6352491533870293e+19
> cvttsd2si %xmm2,%rsi          ; failed conversion stores (1<<63) in rsi
> mov       %rsi,%r9
> sar       $0x3f,%r9           ; account for the sign bit?
> subsd     0x1011f0(%rip),%xmm2
> cvttsd2si %xmm2,%r10          ; stores PTRDIFF_MAX in r10
> and       %r9,%r10
> or        %rsi,%r10           ; r10 -> next_nsize
> (gdb) p /x next_nsize
> $46 = 0x8000000000000000
>
> Thus the branch
>
>    if (next_nsize > R_NSize + change)
>        change = next_nsize - R_NSize;
>
> is taken on Clang but not on GCC.
>
> I think that your patch is sufficient to avoid the next_in_use overflow
> and the resulting invalid double -> size_t conversion.
>
>

-- 
Luke Tierney
Professor Emeritus
Department of Statistics and
    Actuarial Science
University of Iowa
241 Schaeffer Hall                  email:   luke-tierney using uiowa.edu
Iowa City, IA 52242                 WWW:  http://stat.uiowa.edu


More information about the R-devel mailing list